diff --git a/.github/workflows/Release.yaml b/.github/workflows/Release.yaml index 4319387ab5..c23cc09575 100644 --- a/.github/workflows/Release.yaml +++ b/.github/workflows/Release.yaml @@ -22,9 +22,9 @@ jobs: java-version: ${{ vars.JAVA_VERSION || 19 }} distribution: "adopt" - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@v2 + uses: gradle/wrapper-validation-action@v3 - name: Set up Gradle - uses: gradle/gradle-build-action@v3.2.1 + uses: gradle/gradle-build-action@v3.3.2 - name: Build run: ./gradlew clean build - uses: actions/upload-artifact@v4 diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 9fd7814e0d..8a97159263 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -89,9 +89,9 @@ jobs: java-version: ${{ vars.JAVA_VERSION }} distribution: "adopt" - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@v2 + uses: gradle/wrapper-validation-action@v3 - name: Set up Gradle - uses: gradle/gradle-build-action@v3.2.1 + uses: gradle/gradle-build-action@v3.3.2 - name: Test and check run: ./gradlew clean check - name: Build @@ -118,9 +118,9 @@ jobs: java-version: ${{ vars.JAVA_VERSION }} distribution: "adopt" - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@v2 + uses: gradle/wrapper-validation-action@v3 - name: Set up Gradle - uses: gradle/gradle-build-action@v3.2.1 + uses: gradle/gradle-build-action@v3.3.2 - name: License check run: ./gradlew clean checkLicense - uses: actions/upload-artifact@v4 @@ -446,31 +446,29 @@ jobs: # export SPRING_PROFILES_ACTIVE="e2e" # docker-compose up -d frontend - e2e: - runs-on: ubuntu-latest + buildkite-check: + runs-on: macos-latest needs: - deploy-e2e - container: - image: mcr.microsoft.com/playwright:latest steps: - name: Checkout repo uses: actions/checkout@v4 - with: - node-version: ${{ vars.NODE_VERSION }} - - name: Install - run: | - npm install -g pnpm - - name: Set env - run: echo "HOME=/root" >> $GITHUB_ENV - - name: Install shell deps - run: | - apt-get update && apt-get install -y jq - jq --version - name: Check e2e deployment env: BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }} COMMIT_SHA: ${{ github.sha }} run: ./ops/check.sh buildkite-e2e-deployed + + e2e-google-chrome: + runs-on: macos-latest + needs: + - buildkite-check + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Install + run: | + npm install -g pnpm - name: Run E2E env: APP_ORIGIN: ${{ vars.APP_HTTP_SCHEDULE }}://${{ secrets.AWS_EC2_IP_E2E }}:${{ secrets.AWS_EC2_IP_E2E_FRONTEND_PORT }} @@ -478,27 +476,142 @@ jobs: E2E_TOKEN_BUILD_KITE: ${{ secrets.E2E_TOKEN_BUILD_KITE }} E2E_TOKEN_GITHUB: ${{ secrets.E2E_TOKEN_GITHUB }} E2E_TOKEN_PIPELINE_NO_ORG_CONFIG_BUILDKITE: ${{ secrets.E2E_TOKEN_PIPELINE_NO_ORG_CONFIG_BUILDKITE }} + E2E_PROJECT: "Google Chrome" shell: bash {0} run: ./ops/check.sh e2e - uses: actions/upload-artifact@v4 if: always() with: - name: playwright-report + name: playwright-report-google-chrome path: frontend/e2e/reports/ retention-days: 30 - - name: Slack Notification - uses: rtCamp/action-slack-notify@v2 + + e2e-microsoft-edge: + runs-on: macos-latest + env: + E2E_PLATFORM: "Microsoft Edge" + needs: + - buildkite-check + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Install + run: | + npm install -g pnpm + - name: Run E2E + env: + APP_ORIGIN: ${{ vars.APP_HTTP_SCHEDULE }}://${{ secrets.AWS_EC2_IP_E2E }}:${{ secrets.AWS_EC2_IP_E2E_FRONTEND_PORT }} + E2E_TOKEN_JIRA: ${{ secrets.E2E_TOKEN_JIRA }} + E2E_TOKEN_BUILD_KITE: ${{ secrets.E2E_TOKEN_BUILD_KITE }} + E2E_TOKEN_GITHUB: ${{ secrets.E2E_TOKEN_GITHUB }} + E2E_TOKEN_PIPELINE_NO_ORG_CONFIG_BUILDKITE: ${{ secrets.E2E_TOKEN_PIPELINE_NO_ORG_CONFIG_BUILDKITE }} + E2E_PROJECT: ${{ env.E2E_PLATFORM }} + shell: bash {0} + run: ./ops/check.sh e2e + - uses: actions/upload-artifact@v4 if: always() + with: + name: playwright-report-${{ env.E2E_PLATFORM }} + path: frontend/e2e/reports/ + retention-days: 30 + + e2e-webkit: + runs-on: macos-latest + env: + E2E_PLATFORM: webkit + needs: + - buildkite-check + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Install + run: | + npm install -g pnpm + + - name: Run E2E env: - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} - SLACK_ICON_EMOJI: ":heart-beat:" - SLACK_COLOR: ${{ job.status }} - SLACK_USERNAME: "Heartbeat E2E Status" + APP_ORIGIN: ${{ vars.APP_HTTP_SCHEDULE }}://${{ secrets.AWS_EC2_IP_E2E }}:${{ secrets.AWS_EC2_IP_E2E_FRONTEND_PORT }} + E2E_TOKEN_JIRA: ${{ secrets.E2E_TOKEN_JIRA }} + E2E_TOKEN_BUILD_KITE: ${{ secrets.E2E_TOKEN_BUILD_KITE }} + E2E_TOKEN_GITHUB: ${{ secrets.E2E_TOKEN_GITHUB }} + E2E_TOKEN_PIPELINE_NO_ORG_CONFIG_BUILDKITE: ${{ secrets.E2E_TOKEN_PIPELINE_NO_ORG_CONFIG_BUILDKITE }} + E2E_PROJECT: ${{ env.E2E_PLATFORM }} + shell: bash {0} + run: ./ops/check.sh e2e + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report-${{ env.E2E_PLATFORM }} + path: frontend/e2e/reports/ + retention-days: 30 + + e2e-firefox: + runs-on: macos-latest + env: + E2E_PLATFORM: firefox + needs: + - buildkite-check + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Install + run: | + npm install -g pnpm + + - name: Run E2E + env: + APP_ORIGIN: ${{ vars.APP_HTTP_SCHEDULE }}://${{ secrets.AWS_EC2_IP_E2E }}:${{ secrets.AWS_EC2_IP_E2E_FRONTEND_PORT }} + E2E_TOKEN_JIRA: ${{ secrets.E2E_TOKEN_JIRA }} + E2E_TOKEN_BUILD_KITE: ${{ secrets.E2E_TOKEN_BUILD_KITE }} + E2E_TOKEN_GITHUB: ${{ secrets.E2E_TOKEN_GITHUB }} + E2E_TOKEN_PIPELINE_NO_ORG_CONFIG_BUILDKITE: ${{ secrets.E2E_TOKEN_PIPELINE_NO_ORG_CONFIG_BUILDKITE }} + E2E_PROJECT: ${{ env.E2E_PLATFORM }} + shell: bash {0} + run: ./ops/check.sh e2e + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report-${{ env.E2E_PLATFORM }} + path: frontend/e2e/reports/ + retention-days: 30 + + e2e-chromium: + runs-on: macos-latest + env: + E2E_PLATFORM: chromium + needs: + - buildkite-check + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Install + run: | + npm install -g pnpm + - name: Run E2E + env: + APP_ORIGIN: ${{ vars.APP_HTTP_SCHEDULE }}://${{ secrets.AWS_EC2_IP_E2E }}:${{ secrets.AWS_EC2_IP_E2E_FRONTEND_PORT }} + E2E_TOKEN_JIRA: ${{ secrets.E2E_TOKEN_JIRA }} + E2E_TOKEN_BUILD_KITE: ${{ secrets.E2E_TOKEN_BUILD_KITE }} + E2E_TOKEN_GITHUB: ${{ secrets.E2E_TOKEN_GITHUB }} + E2E_TOKEN_PIPELINE_NO_ORG_CONFIG_BUILDKITE: ${{ secrets.E2E_TOKEN_PIPELINE_NO_ORG_CONFIG_BUILDKITE }} + E2E_PROJECT: ${{ env.E2E_PLATFORM }} + shell: bash {0} + run: ./ops/check.sh e2e + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report-${{ env.E2E_PLATFORM }} + path: frontend/e2e/reports/ + retention-days: 30 deploy: runs-on: ubuntu-latest needs: - - e2e + - e2e-chromium + - e2e-webkit + - e2e-firefox + - e2e-microsoft-edge + - e2e-google-chrome steps: - name: Checkout repo # uses: actions/checkout@v4 diff --git a/.trivyignore b/.trivyignore index b693001686..1c8434795d 100644 --- a/.trivyignore +++ b/.trivyignore @@ -14,3 +14,8 @@ CVE-2024-0567 CVE-2024-22201 CVE-2024-22259 CVE-2024-28085 +CVE-2024-22262 +CVE-2024-2961 +CVE-2024-33599 +CVE-2019-10744 +CVE-2024-33599 \ No newline at end of file diff --git a/README.md b/README.md index 4dc2aa139a..643707211d 100644 --- a/README.md +++ b/README.md @@ -294,10 +294,12 @@ _Image 3-19,Settings for Pipeline_ They are sharing the similar settings which you need to specify the pipeline step so that Heartbeat will know in which pipeline and step, team consider it as deploy to PROD. So that we could use it to calculate metrics. | Items | Description | -| ------------- --|----------------------------------------------------------------- | +|---------------|---------------------------------------------------| | Organization | The organization for your pipelines | | Pipeline Name | Your pipeline name | -| Steps | The pipeline step that consider as deploy to PROD || Branches | Your selected branches | +| Steps | The pipeline step that consider as deploy to PROD | +| Branches | Your selected branches | +| Crew setting | Your selected author from github | ## 3.3 Export and import config info diff --git a/README.zh-CN.md b/README.zh-CN.md index 495e3388f4..ceb40bdf96 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -127,11 +127,12 @@ _Image 3-6,Classification Settings_ ![Image 3-7](https://user-images.githubusercontent.com/995849/89784260-f6a08800-db4a-11ea-8ce2-87983363aa18.png)\ _Image 3-7,Settings for Pipeline_ -| Items | Description | -| ------------- | ------------------------ | -| Organization | 您的部署流水线所属的组织 | -| Pipeline Name | 您的流水线名 | -| Steps | 流水线步骤名 | +| Items | Description | +| ------------- |--------------| +| Organization | 你的部署流水线所属的组织 | +| Pipeline Name | 你的流水线名 | +| Steps | 流水线步骤名 | +| Crew setting | 代码提交者 | ## 3.2 导入导出配置信息 diff --git a/backend/build.gradle b/backend/build.gradle index 25fe3a9b70..6a8ba90194 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -5,8 +5,8 @@ plugins { id 'org.springframework.boot' version '3.1.9' id 'io.spring.dependency-management' version '1.1.4' id "io.spring.javaformat" version "0.0.41" - id 'com.github.jk1.dependency-license-report' version '2.6' - id "org.sonarqube" version "4.4.1.3373" + id 'com.github.jk1.dependency-license-report' version '2.7' + id "org.sonarqube" version "5.0.0.4638" } group = 'com.tw' @@ -29,15 +29,15 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-log4j2' implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework:spring-core:6.1.5' + implementation 'org.springframework:spring-core:6.1.6' implementation("org.springframework.cloud:spring-cloud-starter-openfeign:4.0.2") { exclude group: 'commons-fileupload', module: 'commons-fileupload' } - implementation ("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4") { + implementation ("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0") { exclude group: 'org.yaml', module: 'snakeyaml' } implementation 'commons-fileupload:commons-fileupload:1.5' - implementation 'org.yaml:snakeyaml:2.0' + implementation 'org.yaml:snakeyaml:2.2' implementation 'org.springframework.boot:spring-boot-starter-cache' implementation 'org.ehcache:ehcache:3.10.8' implementation 'javax.annotation:javax.annotation-api:1.3.2' @@ -51,7 +51,7 @@ dependencies { testAnnotationProcessor 'org.projectlombok:lombok:1.18.32' implementation 'com.opencsv:opencsv:5.9' implementation 'org.apache.commons:commons-text:1.11.0' - implementation 'org.awaitility:awaitility:3.1.6' + implementation 'org.awaitility:awaitility:4.2.1' } tasks.named('test') { @@ -63,7 +63,7 @@ tasks.named('test') { } pmd { - toolVersion = '6.55.0' + toolVersion = '7.0.0' ruleSets = ['java-basic'] ruleSetConfig = resources.text.fromFile("src/main/resources/pmd.xml") } diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/LeadTime.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/LeadTime.java index 92a45843b2..167d6b48c4 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/LeadTime.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/LeadTime.java @@ -25,8 +25,19 @@ public class LeadTime { private long jobFinishTime; + private long jobStartTime; + + @Nullable + private Long noPRCommitTime; + + @Nullable + private Long firstCommitTime; + private long pipelineCreateTime; + @Nullable + private Boolean isRevert; + @Nullable private Long prLeadTime; diff --git a/backend/src/main/java/heartbeat/controller/report/ReportController.java b/backend/src/main/java/heartbeat/controller/report/ReportController.java index cfb7f506cc..64446c477f 100644 --- a/backend/src/main/java/heartbeat/controller/report/ReportController.java +++ b/backend/src/main/java/heartbeat/controller/report/ReportController.java @@ -6,6 +6,8 @@ import heartbeat.controller.report.dto.response.ReportResponse; import heartbeat.service.report.GenerateReporterService; import heartbeat.service.report.ReportService; +import heartbeat.util.TimeUtil; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -15,12 +17,12 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.bind.annotation.PostMapping; @RestController @RequiredArgsConstructor @@ -37,22 +39,27 @@ public class ReportController { @Value("${callback.interval}") private Integer interval; - @GetMapping("/{reportType}/{filename}") + @GetMapping("/{reportType}/{timeStamp}") public InputStreamResource exportCSV( @Schema(type = "string", allowableValues = { "metric", "pipeline", "board" }, - accessMode = Schema.AccessMode.READ_ONLY) @PathVariable() ReportType reportType, - @PathVariable String filename) { - log.info("Start to export CSV file_reportType: {}, filename: {}", reportType.getValue(), filename); - InputStreamResource result = reportService.exportCsv(reportType, Long.parseLong(filename)); - log.info("Successfully get CSV file_reportType: {}, filename: {}, _result: {}", reportType.getValue(), filename, - result); + accessMode = Schema.AccessMode.READ_ONLY) @PathVariable ReportType reportType, + @PathVariable String timeStamp, + @Schema(type = "string", example = "20240310", pattern = "^[0-9]{8}$") @Parameter String startTime, + @Schema(type = "string", example = "20240409", pattern = "^[0-9]{8}$") @Parameter String endTime) { + log.info("Start to export CSV file_reportType: {}, filename: {}", reportType.getValue(), timeStamp); + InputStreamResource result = reportService.exportCsv(reportType, timeStamp, startTime, endTime); + log.info("Successfully get CSV file_reportType: {}, filename: {}, _result: {}", reportType.getValue(), + timeStamp, result); return result; } - @GetMapping("/{reportId}") - public ResponseEntity generateReport(@PathVariable String reportId) { - log.info("Start to generate report_reportId: {}", reportId); - ReportResponse reportResponse = generateReporterService.getComposedReportResponse(reportId); + @GetMapping("/{timeStamp}") + public ResponseEntity generateReport(@PathVariable String timeStamp, + @Schema(type = "string", example = "20240310", pattern = "^[0-9]{8}$") @Parameter String startTime, + @Schema(type = "string", example = "20240409", pattern = "^[0-9]{8}$") @Parameter String endTime) { + log.info("Start to generate report_reportId: {}", timeStamp); + ReportResponse reportResponse = generateReporterService.getComposedReportResponse(timeStamp, startTime, + endTime); return ResponseEntity.status(HttpStatus.OK).body(reportResponse); } @@ -60,7 +67,9 @@ public ResponseEntity generateReport(@PathVariable String report public ResponseEntity generateReport(@RequestBody GenerateReportRequest request) { log.info("Start to generate report"); reportService.generateReport(request); - String callbackUrl = "/reports/" + request.getCsvTimeStamp(); + String callbackUrl = "/reports/" + request.getCsvTimeStamp() + "?startTime=" + + TimeUtil.convertToChinaSimpleISOFormat(Long.parseLong(request.getStartTime())) + "&endTime=" + + TimeUtil.convertToChinaSimpleISOFormat(Long.parseLong(request.getEndTime())); log.info("Successfully generate report"); return ResponseEntity.status(HttpStatus.ACCEPTED) .body(CallbackResponse.builder().callbackUrl(callbackUrl).interval(interval).build()); diff --git a/backend/src/main/java/heartbeat/controller/report/dto/request/GenerateReportRequest.java b/backend/src/main/java/heartbeat/controller/report/dto/request/GenerateReportRequest.java index 5444a78e7e..6431c875f0 100644 --- a/backend/src/main/java/heartbeat/controller/report/dto/request/GenerateReportRequest.java +++ b/backend/src/main/java/heartbeat/controller/report/dto/request/GenerateReportRequest.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import heartbeat.util.IdUtil; import heartbeat.util.MetricsUtil; +import heartbeat.util.TimeUtil; import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Builder; @@ -69,18 +70,25 @@ public List getBoardMetrics() { } @JsonIgnore - public String getPipelineReportId() { - return IdUtil.getPipelineReportId(this.csvTimeStamp); + public String getTimeRangeAndTimeStamp() { + return TimeUtil.convertToChinaSimpleISOFormat(Long.parseLong(this.startTime)) + "-" + + TimeUtil.convertToChinaSimpleISOFormat(Long.parseLong(this.endTime)) + "-" + this.csvTimeStamp; + + } + + @JsonIgnore + public String getPipelineReportFileId() { + return IdUtil.getPipelineReportFileId(this.getTimeRangeAndTimeStamp()); } @JsonIgnore - public String getSourceControlReportId() { - return IdUtil.getSourceControlReportId(this.csvTimeStamp); + public String getSourceControlReportFileId() { + return IdUtil.getSourceControlReportFileId(this.getTimeRangeAndTimeStamp()); } @JsonIgnore - public String getBoardReportId() { - return IdUtil.getBoardReportId(this.csvTimeStamp); + public String getBoardReportFileId() { + return IdUtil.getBoardReportFileId(this.getTimeRangeAndTimeStamp()); } @JsonIgnore diff --git a/backend/src/main/java/heartbeat/controller/report/dto/response/LeadTimeInfo.java b/backend/src/main/java/heartbeat/controller/report/dto/response/LeadTimeInfo.java index e1572895b8..d88154e880 100644 --- a/backend/src/main/java/heartbeat/controller/report/dto/response/LeadTimeInfo.java +++ b/backend/src/main/java/heartbeat/controller/report/dto/response/LeadTimeInfo.java @@ -25,6 +25,12 @@ public class LeadTimeInfo { private String jobFinishTime; + private String jobStartTime; + + private String noPRCommitTime; + + private String firstCommitTime; + @Nullable private String prLeadTime; @@ -33,33 +39,34 @@ public class LeadTimeInfo { @Nullable private String totalTime; - public LeadTimeInfo(LeadTime leadTime) { - if (leadTime != null) { - if (leadTime.getFirstCommitTimeInPr() != null) { - this.firstCommitTimeInPr = TimeUtil - .convertToISOFormat(String.valueOf(leadTime.getFirstCommitTimeInPr())); - } - - if (leadTime.getPrCreatedTime() != null) { - this.prCreatedTime = TimeUtil.convertToISOFormat(String.valueOf(leadTime.getPrCreatedTime())); - } - - if (leadTime.getPrMergedTime() != null) { - this.prMergedTime = TimeUtil.convertToISOFormat(String.valueOf(leadTime.getPrMergedTime())); - } - - this.jobFinishTime = TimeUtil.convertToISOFormat(String.valueOf(leadTime.getJobFinishTime())); + private Boolean isRevert; - this.pipelineLeadTime = TimeUtil.msToHMS(leadTime.getPipelineLeadTime()); - - if (leadTime.getPrLeadTime() != null) { - this.prLeadTime = TimeUtil.msToHMS(leadTime.getPrLeadTime()); - } + public LeadTimeInfo(LeadTime leadTime) { + if (leadTime == null) { + return; + } + this.firstCommitTimeInPr = convertToISOFormat(leadTime.getFirstCommitTimeInPr()); + this.prCreatedTime = convertToISOFormat(leadTime.getPrCreatedTime()); + this.prMergedTime = convertToISOFormat(leadTime.getPrMergedTime()); + this.jobFinishTime = convertToISOFormat(leadTime.getJobFinishTime()); + this.jobStartTime = convertToISOFormat(leadTime.getJobStartTime()); + this.firstCommitTime = convertToISOFormat(leadTime.getFirstCommitTime()); + this.noPRCommitTime = convertToISOFormat(leadTime.getNoPRCommitTime()); + + this.pipelineLeadTime = TimeUtil.msToHMS(leadTime.getPipelineLeadTime()); + this.isRevert = leadTime.getIsRevert(); + + if (leadTime.getPrLeadTime() != null) { + this.prLeadTime = TimeUtil.msToHMS(leadTime.getPrLeadTime()); + } - if (leadTime.getTotalTime() != 0) { - this.totalTime = TimeUtil.msToHMS(leadTime.getTotalTime()); - } + if (leadTime.getTotalTime() != 0) { + this.totalTime = TimeUtil.msToHMS(leadTime.getTotalTime()); } } + private String convertToISOFormat(Long time) { + return time != null ? TimeUtil.convertToISOFormat(String.valueOf(time)) : null; + } + } diff --git a/backend/src/main/java/heartbeat/handler/AsyncExceptionHandler.java b/backend/src/main/java/heartbeat/handler/AsyncExceptionHandler.java index 3cb5c4e8c8..945c68d402 100644 --- a/backend/src/main/java/heartbeat/handler/AsyncExceptionHandler.java +++ b/backend/src/main/java/heartbeat/handler/AsyncExceptionHandler.java @@ -8,6 +8,7 @@ import org.springframework.stereotype.Component; import java.io.File; +import java.nio.file.Path; import static heartbeat.handler.base.FIleType.ERROR; @@ -15,12 +16,18 @@ @RequiredArgsConstructor public class AsyncExceptionHandler extends AsyncDataBaseHandler { + private static final String OUTPUT_FILE_PATH = "./app/output/"; + + private static final String SLASH = "/"; + public void put(String reportId, BaseException e) { createFileByType(ERROR, reportId, new Gson().toJson(new AsyncExceptionDTO(e))); } public AsyncExceptionDTO get(String reportId) { - return readFileByType(ERROR, reportId, AsyncExceptionDTO.class); + Path targetPath = new File(OUTPUT_FILE_PATH).toPath().normalize(); + String fileName = targetPath + SLASH + ERROR.getPath() + reportId; + return readFileByType(new File(fileName), ERROR, reportId, AsyncExceptionDTO.class); } public AsyncExceptionDTO remove(String reportId) { diff --git a/backend/src/main/java/heartbeat/handler/AsyncMetricsDataHandler.java b/backend/src/main/java/heartbeat/handler/AsyncMetricsDataHandler.java index 0cfc91a082..4e619ac6c9 100644 --- a/backend/src/main/java/heartbeat/handler/AsyncMetricsDataHandler.java +++ b/backend/src/main/java/heartbeat/handler/AsyncMetricsDataHandler.java @@ -11,6 +11,7 @@ import org.springframework.stereotype.Component; import java.io.File; +import java.nio.file.Path; import static heartbeat.handler.base.FIleType.METRICS_DATA_COMPLETED; @@ -21,6 +22,12 @@ public class AsyncMetricsDataHandler extends AsyncDataBaseHandler { private static final String GENERATE_REPORT_ERROR = "Failed to update metrics data completed through this timestamp."; + private static final String OUTPUT_FILE_PATH = "./app/output/"; + + private static final String SLASH = "/"; + + private final Object readWriteLock = new Object(); + public void putMetricsDataCompleted(String timeStamp, MetricsDataCompleted metricsDataCompleted) { try { acquireLock(METRICS_DATA_COMPLETED, timeStamp); @@ -32,19 +39,22 @@ public void putMetricsDataCompleted(String timeStamp, MetricsDataCompleted metri } public MetricsDataCompleted getMetricsDataCompleted(String timeStamp) { - return readFileByType(METRICS_DATA_COMPLETED, timeStamp, MetricsDataCompleted.class); + Path targetPath = new File(OUTPUT_FILE_PATH).toPath().normalize(); + String fileName = targetPath + SLASH + METRICS_DATA_COMPLETED.getPath() + timeStamp; + return readFileByType(new File(fileName), METRICS_DATA_COMPLETED, timeStamp, MetricsDataCompleted.class); } public void deleteExpireMetricsDataCompletedFile(long currentTimeStamp, File directory) { deleteExpireFileByType(METRICS_DATA_COMPLETED, currentTimeStamp, directory); } - @Synchronized + @Synchronized("readWriteLock") public void updateMetricsDataCompletedInHandler(String metricDataFileId, MetricType metricType, boolean isCreateCsvSuccess) { MetricsDataCompleted previousMetricsCompleted = getMetricsDataCompleted(metricDataFileId); if (previousMetricsCompleted == null) { - log.error(GENERATE_REPORT_ERROR); + String filename = OUTPUT_FILE_PATH + METRICS_DATA_COMPLETED.getPath() + metricDataFileId; + log.error(GENERATE_REPORT_ERROR + "; filename: " + filename); throw new GenerateReportException(GENERATE_REPORT_ERROR); } if (isCreateCsvSuccess) { @@ -59,10 +69,12 @@ public void updateMetricsDataCompletedInHandler(String metricDataFileId, MetricT putMetricsDataCompleted(metricDataFileId, previousMetricsCompleted); } + @Synchronized("readWriteLock") public void updateOverallMetricsCompletedInHandler(String metricDataFileId) { MetricsDataCompleted previousMetricsCompleted = getMetricsDataCompleted(metricDataFileId); if (previousMetricsCompleted == null) { - log.error(GENERATE_REPORT_ERROR); + String filename = OUTPUT_FILE_PATH + METRICS_DATA_COMPLETED.getPath() + metricDataFileId; + log.error(GENERATE_REPORT_ERROR + "; filename: " + filename); throw new GenerateReportException(GENERATE_REPORT_ERROR); } previousMetricsCompleted.setOverallMetricCompleted(true); diff --git a/backend/src/main/java/heartbeat/handler/AsyncReportRequestHandler.java b/backend/src/main/java/heartbeat/handler/AsyncReportRequestHandler.java index f6173ac8b2..e920620214 100644 --- a/backend/src/main/java/heartbeat/handler/AsyncReportRequestHandler.java +++ b/backend/src/main/java/heartbeat/handler/AsyncReportRequestHandler.java @@ -7,6 +7,7 @@ import org.springframework.stereotype.Component; import java.io.File; +import java.nio.file.Path; import static heartbeat.handler.base.FIleType.REPORT; @@ -14,12 +15,18 @@ @RequiredArgsConstructor public class AsyncReportRequestHandler extends AsyncDataBaseHandler { + private static final String OUTPUT_FILE_PATH = "./app/output/"; + + private static final String SLASH = "/"; + public void putReport(String reportId, ReportResponse e) { createFileByType(REPORT, reportId, new Gson().toJson(e)); } public ReportResponse getReport(String reportId) { - return readFileByType(REPORT, reportId, ReportResponse.class); + Path targetPath = new File(OUTPUT_FILE_PATH).toPath().normalize(); + String fileName = targetPath + SLASH + REPORT.getPath() + reportId; + return readFileByType(new File(fileName), REPORT, reportId, ReportResponse.class); } public void deleteExpireReportFile(long currentTimeStamp, File directory) { diff --git a/backend/src/main/java/heartbeat/handler/base/AsyncDataBaseHandler.java b/backend/src/main/java/heartbeat/handler/base/AsyncDataBaseHandler.java index 9d382729af..71c998c2d0 100644 --- a/backend/src/main/java/heartbeat/handler/base/AsyncDataBaseHandler.java +++ b/backend/src/main/java/heartbeat/handler/base/AsyncDataBaseHandler.java @@ -60,11 +60,10 @@ private void createDirToConvertData(FIleType fIleType) { log.info(message); } - protected T readFileByType(FIleType fIleType, String fileId, Class classType) { - String fileName = OUTPUT_FILE_PATH + fIleType.getPath() + fileId; - if (!fileName.contains("..") && fileName.startsWith(OUTPUT_FILE_PATH + fIleType.getPath())) { - if (Files.exists(Path.of(fileName))) { - try (JsonReader reader = new JsonReader(new FileReader(fileName))) { + public T readFileByType(File file, FIleType fIleType, String fileId, Class classType) { + if (file.toPath().normalize().startsWith(new File(OUTPUT_FILE_PATH).toPath().normalize())) { + if (file.exists()) { + try (JsonReader reader = new JsonReader(new FileReader(file))) { return new Gson().fromJson(reader, classType); } catch (IOException | RuntimeException e) { @@ -85,7 +84,7 @@ protected T readAndRemoveFileByType(FIleType fIleType, String fileId, Class< if (!fileName.contains("..") && fileName.startsWith(OUTPUT_FILE_PATH + fIleType.getPath())) { log.info("Start to remove file type: {}, file name: {}", fIleType.getType(), fileId); try { - T t = readFileByType(fIleType, fileId, classType); + T t = readFileByType(new File(fileName), fIleType, fileId, classType); if (Objects.nonNull(t)) { Files.delete(Path.of(fileName)); } @@ -157,7 +156,7 @@ private void deleteOldFiles(FIleType fIleType, long currentTimeStamp, File direc for (File file : files) { String fileName = file.getName(); String[] splitResult = fileName.split(FILENAME_SPLIT_PATTERN); - String timeStamp = splitResult[1]; + String timeStamp = splitResult[3]; if (validateExpire(currentTimeStamp, Long.parseLong(timeStamp)) && !file.delete() && file.exists()) { log.error("Failed to deleted expired fIleType: {} file, file name: {}", fIleType.getType(), fileName); diff --git a/backend/src/main/java/heartbeat/service/board/jira/JiraService.java b/backend/src/main/java/heartbeat/service/board/jira/JiraService.java index b22b29a3dd..27e77de786 100644 --- a/backend/src/main/java/heartbeat/service/board/jira/JiraService.java +++ b/backend/src/main/java/heartbeat/service/board/jira/JiraService.java @@ -844,7 +844,8 @@ private CycleTimeInfoDTO getCycleTime(CardHistoryResponseDTO cardHistoryResponse keyFlagged); List cycleTimeInfos = boardUtil.getCycleTimeInfos(statusChangedArray, realDoneStatus, treatFlagCardAsBlock); - List originCycleTimeInfos = boardUtil.getOriginCycleTimeInfos(statusChangedArray); + List originCycleTimeInfos = boardUtil.getOriginCycleTimeInfos(statusChangedArray, + treatFlagCardAsBlock); return CycleTimeInfoDTO.builder() .cycleTimeInfos(cycleTimeInfos) diff --git a/backend/src/main/java/heartbeat/service/pipeline/buildkite/BuildKiteService.java b/backend/src/main/java/heartbeat/service/pipeline/buildkite/BuildKiteService.java index 7d4ba73e58..b2c1ce576f 100644 --- a/backend/src/main/java/heartbeat/service/pipeline/buildkite/BuildKiteService.java +++ b/backend/src/main/java/heartbeat/service/pipeline/buildkite/BuildKiteService.java @@ -80,8 +80,8 @@ public List getPipelineStepNames(List buildKiteBuild public List getPipelineCrewNames(List buildKiteBuildInfos) { List buildInfoList = new ArrayList<>(buildKiteBuildInfos.stream() - .filter(buildKiteBuildInfo -> Objects.nonNull(buildKiteBuildInfo.getCreator())) - .map(buildKiteBuildInfo -> buildKiteBuildInfo.getCreator().getName()) + .filter(buildKiteBuildInfo -> Objects.nonNull(buildKiteBuildInfo.getAuthor())) + .map(buildKiteBuildInfo -> buildKiteBuildInfo.getAuthor().getName()) .distinct() .sorted() .toList()); diff --git a/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java b/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java index cf7788abec..5e54225b37 100644 --- a/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java +++ b/backend/src/main/java/heartbeat/service/report/CSVFileGenerator.java @@ -43,8 +43,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; -import java.math.BigDecimal; -import java.math.RoundingMode; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -67,14 +66,18 @@ public class CSVFileGenerator { public static final String FILE_LOCAL_PATH = "./app/output/csv"; + private static final Path FILE_PATH = new File(FILE_LOCAL_PATH).toPath().normalize(); + private static final String CANCELED_STATUS = "canceled"; private static final String REWORK_FIELD = "Rework"; - private static InputStreamResource readStringFromCsvFile(String fileName) { + public static InputStreamResource readStringFromCsvFile(File file) { + if (!file.toPath().normalize().startsWith(FILE_PATH)) { + throw new IllegalArgumentException("Invalid file path"); + } try { - InputStream inputStream = new FileInputStream(fileName); - + InputStream inputStream = new FileInputStream(file); return new InputStreamResource(inputStream); } catch (IOException e) { @@ -101,9 +104,10 @@ public void convertPipelineDataToCSV(List leadTimeData, String File file = new File(fileName); try (CSVWriter csvWriter = new CSVWriter(new FileWriter(file))) { String[] headers = { "Organization", "Pipeline Name", "Pipeline Step", "Valid", "Build Number", - "Code Committer", "Pipeline Creator", "First Code Committed Time In PR", "Code Committed Time", - "PR Created Time", "PR Merged Time", "Deployment Completed Time", "Total Lead Time (HH:mm:ss)", - "PR Lead Time (HH:mm:ss)", "Pipeline Lead Time (HH:mm:ss)", "Status", "Branch" }; + "Code Committer", "Build Creator", "First Code Committed Time In PR", "PR Created Time", + "PR Merged Time", "No PR Committed Time", "Job Start Time", "Pipeline Start Time", + "Pipeline Finish Time", "Total Lead Time (HH:mm:ss)", "PR Lead Time (HH:mm:ss)", + "Pipeline Lead Time (HH:mm:ss)", "Status", "Branch", "Revert" }; csvWriter.writeNext(headers); @@ -121,10 +125,8 @@ public void convertPipelineDataToCSV(List leadTimeData, String private String[] getRowData(PipelineCSVInfo csvInfo) { String committerName = null; - String commitDate = null; - if (csvInfo.getCommitInfo() != null) { - committerName = csvInfo.getCommitInfo().getCommit().getAuthor().getName(); - commitDate = csvInfo.getCommitInfo().getCommit().getAuthor().getDate(); + if (csvInfo.getBuildInfo().getAuthor() != null && csvInfo.getBuildInfo().getAuthor().getName() != null) { + committerName = String.valueOf(csvInfo.getBuildInfo().getAuthor().getName()); } String creatorName = null; @@ -137,6 +139,7 @@ private String[] getRowData(PipelineCSVInfo csvInfo) { String stepName = csvInfo.getStepName(); String valid = String.valueOf(csvInfo.getValid()).toLowerCase(); String buildNumber = String.valueOf(csvInfo.getBuildInfo().getNumber()); + String state = csvInfo.getPiplineStatus().equals(CANCELED_STATUS) ? CANCELED_STATUS : csvInfo.getDeployInfo().getState(); String branch = csvInfo.getBuildInfo().getBranch(); @@ -145,24 +148,32 @@ private String[] getRowData(PipelineCSVInfo csvInfo) { String firstCommitTimeInPr = leadTimeInfo.getFirstCommitTimeInPr(); String prCreatedTime = leadTimeInfo.getPrCreatedTime(); String prMergedTime = leadTimeInfo.getPrMergedTime(); - String jobFinishTime = csvInfo.getDeployInfo().getJobFinishTime(); + String noPRCommitTime = leadTimeInfo.getNoPRCommitTime(); + String jobStartTime = leadTimeInfo.getJobStartTime(); + String pipelineStartTime = leadTimeInfo.getFirstCommitTime(); + String pipelineFinishTime = csvInfo.getDeployInfo().getJobFinishTime(); String totalTime = leadTimeInfo.getTotalTime(); String prLeadTime = leadTimeInfo.getPrLeadTime(); String pipelineLeadTime = leadTimeInfo.getPipelineLeadTime(); + String isRevert = leadTimeInfo.getIsRevert() == null ? "" : String.valueOf(leadTimeInfo.getIsRevert()); return new String[] { organization, pipelineName, stepName, valid, buildNumber, committerName, creatorName, - firstCommitTimeInPr, commitDate, prCreatedTime, prMergedTime, jobFinishTime, totalTime, prLeadTime, - pipelineLeadTime, state, branch }; + firstCommitTimeInPr, prCreatedTime, prMergedTime, noPRCommitTime, jobStartTime, pipelineStartTime, + pipelineFinishTime, totalTime, prLeadTime, pipelineLeadTime, state, branch, isRevert }; } - public InputStreamResource getDataFromCSV(ReportType reportDataType, long csvTimeStamp) { + public InputStreamResource getDataFromCSV(ReportType reportDataType, String timeRangeAndTimeStamp) { + if (timeRangeAndTimeStamp.contains("..") || timeRangeAndTimeStamp.contains("/") + || timeRangeAndTimeStamp.contains("\\")) { + throw new IllegalArgumentException("Invalid time range time stamp"); + } return switch (reportDataType) { - case METRIC -> readStringFromCsvFile( - CSVFileNameEnum.METRIC.getValue() + FILENAME_SEPARATOR + csvTimeStamp + CSV_EXTENSION); - case PIPELINE -> readStringFromCsvFile( - CSVFileNameEnum.PIPELINE.getValue() + FILENAME_SEPARATOR + csvTimeStamp + CSV_EXTENSION); - default -> readStringFromCsvFile( - CSVFileNameEnum.BOARD.getValue() + FILENAME_SEPARATOR + csvTimeStamp + CSV_EXTENSION); + case METRIC -> readStringFromCsvFile(new File(FILE_LOCAL_PATH, + ReportType.METRIC.getValue() + FILENAME_SEPARATOR + timeRangeAndTimeStamp + CSV_EXTENSION)); + case PIPELINE -> readStringFromCsvFile(new File(FILE_LOCAL_PATH, + ReportType.PIPELINE.getValue() + FILENAME_SEPARATOR + timeRangeAndTimeStamp + CSV_EXTENSION)); + default -> readStringFromCsvFile(new File(FILE_LOCAL_PATH, + ReportType.BOARD.getValue() + FILENAME_SEPARATOR + timeRangeAndTimeStamp + CSV_EXTENSION)); }; } @@ -180,10 +191,10 @@ public void convertBoardDataToCSV(List cardDTOList, List generateCSVForPipeline(request, fetchedData.getBuildKiteData())); } } private void generatePipelineReport(GenerateReportRequest request, FetchedData fetchedData) { - String pipelineReportId = request.getPipelineReportId(); + String pipelineReportId = request.getPipelineReportFileId(); log.info( "Start to generate pipeline report, _metrics: {}, _considerHoliday: {}, _startTime: {}, _endTime: {}, _pipelineReportId: {}", request.getPipelineMetrics(), request.getConsiderHoliday(), request.getStartTime(), @@ -145,12 +147,12 @@ private void generatePipelineReport(GenerateReportRequest request, FetchedData f asyncExceptionHandler.put(pipelineReportId, e); if (List.of(401, 403, 404).contains(e.getStatus())) asyncMetricsDataHandler.updateMetricsDataCompletedInHandler( - IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp()), DORA, false); + IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp()), DORA, false); } } private void generateSourceControlReport(GenerateReportRequest request, FetchedData fetchedData) { - String sourceControlReportId = request.getSourceControlReportId(); + String sourceControlReportId = request.getSourceControlReportFileId(); log.info( "Start to generate source control report, _metrics: {}, _considerHoliday: {}, _startTime: {}, _endTime: {}, _sourceControlReportId: {}", request.getSourceControlMetrics(), request.getConsiderHoliday(), request.getStartTime(), @@ -167,7 +169,7 @@ private void generateSourceControlReport(GenerateReportRequest request, FetchedD asyncExceptionHandler.put(sourceControlReportId, e); if (List.of(401, 403, 404).contains(e.getStatus())) asyncMetricsDataHandler.updateMetricsDataCompletedInHandler( - IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp()), DORA, false); + IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp()), DORA, false); } } @@ -225,8 +227,8 @@ private synchronized ReportResponse generateBoardReporter(GenerateReportRequest private void generateCsvForBoard(GenerateReportRequest request, FetchedData fetchedData) { kanbanCsvService.generateCsvInfo(request, fetchedData.getCardCollectionInfo().getRealDoneCardCollection(), fetchedData.getCardCollectionInfo().getNonDoneCardCollection()); - asyncMetricsDataHandler - .updateMetricsDataCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp()), BOARD, true); + asyncMetricsDataHandler.updateMetricsDataCompletedInHandler( + IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp()), BOARD, true); } private void assembleVelocity(FetchedData fetchedData, ReportResponse reportResponse) { @@ -297,17 +299,16 @@ private FetchedData fetchJiraBoardData(GenerateReportRequest request, FetchedDat } private void generateCSVForPipeline(GenerateReportRequest request, BuildKiteData buildKiteData) { - List pipelineData = pipelineService.generateCSVForPipelineWithCodebase( - request.getCodebaseSetting(), request.getStartTime(), request.getEndTime(), buildKiteData, - request.getBuildKiteSetting().getDeploymentEnvList()); + List pipelineData = pipelineService.generateCSVForPipeline(request.getStartTime(), + request.getEndTime(), buildKiteData, request.getBuildKiteSetting().getDeploymentEnvList()); - csvFileGenerator.convertPipelineDataToCSV(pipelineData, request.getCsvTimeStamp()); - asyncMetricsDataHandler - .updateMetricsDataCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp()), DORA, true); + csvFileGenerator.convertPipelineDataToCSV(pipelineData, request.getTimeRangeAndTimeStamp()); + asyncMetricsDataHandler.updateMetricsDataCompletedInHandler( + IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp()), DORA, true); } - public void generateCSVForMetric(ReportResponse reportContent, String csvTimeStamp) { - csvFileGenerator.convertMetricDataToCSV(reportContent, csvTimeStamp); + public void generateCSVForMetric(ReportResponse reportContent, String csvTimeRangeTimeStamp) { + csvFileGenerator.convertMetricDataToCSV(reportContent, csvTimeRangeTimeStamp); } private void saveReporterInHandler(ReportResponse reportContent, String reportId) { @@ -336,7 +337,7 @@ private void deleteOldCSV(long currentTimeStamp, File directory) { for (File file : files) { String fileName = file.getName(); String[] splitResult = fileName.split("[-.]"); - String timeStamp = splitResult[1]; + String timeStamp = splitResult[3]; if (validateExpire(currentTimeStamp, Long.parseLong(timeStamp)) && !file.delete() && file.exists()) { log.error("Failed to deleted expired CSV file, file name: {}", fileName); } @@ -366,30 +367,34 @@ private ReportResponse getReportFromHandler(String reportId) { return asyncReportRequestHandler.getReport(reportId); } - public MetricsDataCompleted checkReportReadyStatus(String reportTimeStamp) { - if (validateExpire(System.currentTimeMillis(), Long.parseLong(reportTimeStamp))) { + public MetricsDataCompleted checkReportReadyStatus(String timeRangeAndTimeStamp) { + String timeStamp = timeRangeAndTimeStamp.substring(timeRangeAndTimeStamp.lastIndexOf(FILENAME_SEPARATOR) + 1); + if (validateExpire(System.currentTimeMillis(), Long.parseLong(timeStamp))) { throw new GenerateReportException("Failed to get report due to report time expires"); } - return asyncMetricsDataHandler.getMetricsDataCompleted(IdUtil.getDataCompletedPrefix(reportTimeStamp)); + return asyncMetricsDataHandler.getMetricsDataCompleted(IdUtil.getDataCompletedPrefix(timeRangeAndTimeStamp)); } - public ReportResponse getComposedReportResponse(String reportId) { - MetricsDataCompleted reportReadyStatus = checkReportReadyStatus(reportId); + public ReportResponse getComposedReportResponse(String timeStamp, String startTime, String endTime) { + String timeRangeAndTimeStamp = startTime + FILENAME_SEPARATOR + endTime + FILENAME_SEPARATOR + timeStamp; + MetricsDataCompleted reportReadyStatus = checkReportReadyStatus(timeRangeAndTimeStamp); - ReportResponse boardReportResponse = getReportFromHandler(IdUtil.getBoardReportId(reportId)); - ReportResponse pipleineReportResponse = getReportFromHandler(IdUtil.getPipelineReportId(reportId)); - ReportResponse sourceControlReportResponse = getReportFromHandler(IdUtil.getSourceControlReportId(reportId)); + ReportResponse boardReportResponse = getReportFromHandler(IdUtil.getBoardReportFileId(timeRangeAndTimeStamp)); + ReportResponse pipelineReportResponse = getReportFromHandler( + IdUtil.getPipelineReportFileId(timeRangeAndTimeStamp)); + ReportResponse sourceControlReportResponse = getReportFromHandler( + IdUtil.getSourceControlReportFileId(timeRangeAndTimeStamp)); - ReportMetricsError reportMetricsError = getReportErrorAndHandleAsyncException(reportId); + ReportMetricsError reportMetricsError = getReportErrorAndHandleAsyncException(timeRangeAndTimeStamp); return ReportResponse.builder() .velocity(getValueOrNull(boardReportResponse, ReportResponse::getVelocity)) .classificationList(getValueOrNull(boardReportResponse, ReportResponse::getClassificationList)) .cycleTime(getValueOrNull(boardReportResponse, ReportResponse::getCycleTime)) .rework(getValueOrNull(boardReportResponse, ReportResponse::getRework)) .exportValidityTime(EXPORT_CSV_VALIDITY_TIME) - .deploymentFrequency(getValueOrNull(pipleineReportResponse, ReportResponse::getDeploymentFrequency)) - .devChangeFailureRate(getValueOrNull(pipleineReportResponse, ReportResponse::getDevChangeFailureRate)) - .devMeanTimeToRecovery(getValueOrNull(pipleineReportResponse, ReportResponse::getDevMeanTimeToRecovery)) + .deploymentFrequency(getValueOrNull(pipelineReportResponse, ReportResponse::getDeploymentFrequency)) + .devChangeFailureRate(getValueOrNull(pipelineReportResponse, ReportResponse::getDevChangeFailureRate)) + .devMeanTimeToRecovery(getValueOrNull(pipelineReportResponse, ReportResponse::getDevMeanTimeToRecovery)) .leadTimeForChanges(getValueOrNull(sourceControlReportResponse, ReportResponse::getLeadTimeForChanges)) .boardMetricsCompleted(reportReadyStatus.boardMetricsCompleted()) .doraMetricsCompleted(reportReadyStatus.doraMetricsCompleted()) @@ -401,9 +406,10 @@ public ReportResponse getComposedReportResponse(String reportId) { } private ReportMetricsError getReportErrorAndHandleAsyncException(String reportId) { - AsyncExceptionDTO boardException = asyncExceptionHandler.get(IdUtil.getBoardReportId(reportId)); - AsyncExceptionDTO pipelineException = asyncExceptionHandler.get(IdUtil.getPipelineReportId(reportId)); - AsyncExceptionDTO sourceControlException = asyncExceptionHandler.get(IdUtil.getSourceControlReportId(reportId)); + AsyncExceptionDTO boardException = asyncExceptionHandler.get(IdUtil.getBoardReportFileId(reportId)); + AsyncExceptionDTO pipelineException = asyncExceptionHandler.get(IdUtil.getPipelineReportFileId(reportId)); + AsyncExceptionDTO sourceControlException = asyncExceptionHandler + .get(IdUtil.getSourceControlReportFileId(reportId)); return ReportMetricsError.builder() .boardMetricsError(handleAsyncExceptionAndGetErrorInfo(boardException)) .pipelineMetricsError(handleAsyncExceptionAndGetErrorInfo(pipelineException)) diff --git a/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java b/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java index c9a0d3e6b2..15022ff53a 100644 --- a/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java +++ b/backend/src/main/java/heartbeat/service/report/KanbanCsvService.java @@ -25,14 +25,12 @@ import heartbeat.service.board.jira.JiraService; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Service; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -41,7 +39,6 @@ import java.util.stream.Stream; import static heartbeat.controller.board.dto.request.CardStepsEnum.BLOCK; -import static heartbeat.controller.board.dto.request.CardStepsEnum.FLAG; import static heartbeat.controller.board.dto.request.CardStepsEnum.reworkJudgmentMap; @Service @@ -104,11 +101,11 @@ public void generateCsvInfo(GenerateReportRequest request, CardCollection realDo } this.generateCSVForBoard(realDoneCardCollection.getJiraCardDTOList(), nonDoneCardCollection.getJiraCardDTOList(), jiraColumns.getJiraColumnResponse(), - jiraBoardSetting.getTargetFields(), request.getCsvTimeStamp(), reworkState, reworkFromStates); + jiraBoardSetting.getTargetFields(), request.getTimeRangeAndTimeStamp(), reworkState, reworkFromStates); } private void generateCSVForBoard(List allDoneCards, List nonDoneCards, - List jiraColumns, List targetFields, String csvTimeStamp, + List jiraColumns, List targetFields, String csvTimeRangeTimeStamp, CardStepsEnum reworkState, List reworkFromStates) { List cardDTOList = new ArrayList<>(); List emptyJiraCard = List.of(JiraCardDTO.builder().build()); @@ -181,7 +178,7 @@ private void generateCSVForBoard(List allDoneCards, List nonDoneCards, List jiraColumns) { diff --git a/backend/src/main/java/heartbeat/service/report/PipelineService.java b/backend/src/main/java/heartbeat/service/report/PipelineService.java index 7ba6350cbe..df25ec0812 100644 --- a/backend/src/main/java/heartbeat/service/report/PipelineService.java +++ b/backend/src/main/java/heartbeat/service/report/PipelineService.java @@ -1,6 +1,5 @@ package heartbeat.service.report; -import heartbeat.client.dto.codebase.github.CommitInfo; import heartbeat.client.dto.codebase.github.LeadTime; import heartbeat.client.dto.codebase.github.PipelineLeadTime; import heartbeat.client.dto.pipeline.buildkite.BuildKiteBuildInfo; @@ -8,14 +7,12 @@ import heartbeat.client.dto.pipeline.buildkite.DeployInfo; import heartbeat.client.dto.pipeline.buildkite.DeployTimes; import heartbeat.controller.pipeline.dto.request.DeploymentEnvironment; -import heartbeat.controller.report.dto.request.CodebaseSetting; import heartbeat.controller.report.dto.request.GenerateReportRequest; import heartbeat.controller.report.dto.response.LeadTimeInfo; import heartbeat.controller.report.dto.response.PipelineCSVInfo; import heartbeat.service.pipeline.buildkite.BuildKiteService; import heartbeat.service.report.calculator.model.FetchedData; import heartbeat.service.source.github.GitHubService; -import heartbeat.util.GithubUtil; import lombok.AllArgsConstructor; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Service; @@ -56,7 +53,7 @@ public FetchedData.BuildKiteData fetchBuildKiteInfo(GenerateReportRequest reques String endTime = request.getEndTime(); FetchedData.BuildKiteData result = new FetchedData.BuildKiteData(); - request.getBuildKiteSetting().getDeploymentEnvList().stream().forEach(deploymentEnvironment -> { + request.getBuildKiteSetting().getDeploymentEnvList().forEach(deploymentEnvironment -> { List buildKiteBuildInfo = getBuildKiteBuildInfo(startTime, endTime, deploymentEnvironment, request.getBuildKiteSetting().getToken(), request.getBuildKiteSetting().getPipelineCrews()); @@ -68,9 +65,8 @@ public FetchedData.BuildKiteData fetchBuildKiteInfo(GenerateReportRequest reques return result; } - public List generateCSVForPipelineWithCodebase(CodebaseSetting codebaseSetting, String startTime, - String endTime, FetchedData.BuildKiteData buildKiteData, - List deploymentEnvironments) { + public List generateCSVForPipeline(String startTime, String endTime, + FetchedData.BuildKiteData buildKiteData, List deploymentEnvironments) { List pipelineCSVInfos = new ArrayList<>(); deploymentEnvironments.parallelStream().forEach(deploymentEnvironment -> { List buildInfos = getBuildInfos(buildKiteData.getBuildInfosList(), @@ -82,8 +78,8 @@ public List generateCSVForPipelineWithCodebase(CodebaseSetting pipelineSteps); List pipelineCSVInfoList = buildInfos.stream() .filter(buildInfo -> isValidBuildInfo(buildInfo, validSteps, startTime, endTime)) - .map(buildInfo -> getPipelineCSVInfo(codebaseSetting, startTime, endTime, buildKiteData, - deploymentEnvironment, buildInfo, validSteps)) + .map(buildInfo -> getPipelineCSVInfo(startTime, endTime, buildKiteData, deploymentEnvironment, + buildInfo, validSteps)) .toList(); pipelineCSVInfos.addAll(pipelineCSVInfoList); } @@ -92,17 +88,12 @@ public List generateCSVForPipelineWithCodebase(CodebaseSetting return pipelineCSVInfos; } - private PipelineCSVInfo getPipelineCSVInfo(CodebaseSetting codebaseSetting, String startTime, String endTime, + private PipelineCSVInfo getPipelineCSVInfo(String startTime, String endTime, FetchedData.BuildKiteData buildKiteData, DeploymentEnvironment deploymentEnvironment, BuildKiteBuildInfo buildInfo, List pipelineSteps) { DeployInfo deployInfo = buildKiteService.mapToDeployInfo(buildInfo, pipelineSteps, REQUIRED_STATES, startTime, endTime); - CommitInfo commitInfo = null; - if (Objects.nonNull(codebaseSetting) && StringUtils.hasLength(codebaseSetting.getToken()) - && Objects.nonNull(deployInfo.getCommitId())) { - commitInfo = gitHubService.fetchCommitInfo(deployInfo.getCommitId(), - GithubUtil.getGithubUrlFullName(deploymentEnvironment.getRepository()), codebaseSetting.getToken()); - } + return PipelineCSVInfo.builder() .organizationName(deploymentEnvironment.getOrgName()) .pipeLineName(deploymentEnvironment.getName()) @@ -111,7 +102,6 @@ private PipelineCSVInfo getPipelineCSVInfo(CodebaseSetting codebaseSetting, Stri .piplineStatus(buildInfo.getState()) .buildInfo(buildInfo) .deployInfo(deployInfo) - .commitInfo(commitInfo) .leadTimeInfo(new LeadTimeInfo(filterLeadTime(buildKiteData, deploymentEnvironment, deployInfo))) .build(); } @@ -160,8 +150,8 @@ private List getBuildKiteBuildInfo(String startTime, String if (!CollectionUtils.isEmpty(pipelineCrews)) { buildKiteBuildInfo = buildKiteBuildInfo.stream() - .filter(info -> ((pipelineCrews.contains("Unknown") && info.getCreator() == null)) - || (info.getCreator() != null && pipelineCrews.contains(info.getCreator().getName()))) + .filter(info -> ((pipelineCrews.contains("Unknown") && info.getAuthor() == null)) + || (info.getAuthor() != null && pipelineCrews.contains(info.getAuthor().getName()))) .toList(); } return buildKiteBuildInfo; diff --git a/backend/src/main/java/heartbeat/service/report/ReportService.java b/backend/src/main/java/heartbeat/service/report/ReportService.java index 3f355ede28..6e88e4cea3 100644 --- a/backend/src/main/java/heartbeat/service/report/ReportService.java +++ b/backend/src/main/java/heartbeat/service/report/ReportService.java @@ -10,6 +10,7 @@ import heartbeat.handler.AsyncMetricsDataHandler; import heartbeat.service.report.calculator.ReportGenerator; import heartbeat.util.IdUtil; +import heartbeat.util.TimeUtil; import lombok.RequiredArgsConstructor; import org.springframework.core.io.InputStreamResource; import org.springframework.stereotype.Service; @@ -37,11 +38,16 @@ public class ReportService { private final ReportGenerator reportGenerator; - public InputStreamResource exportCsv(ReportType reportDataType, long csvTimestamp) { - if (isExpiredTimeStamp(csvTimestamp)) { + private static final char FILENAME_SEPARATOR = '-'; + + public InputStreamResource exportCsv(ReportType reportDataType, String csvTimestamp, String startTime, + String endTime) { + + String timeRangeAndTimeStamp = startTime + FILENAME_SEPARATOR + endTime + FILENAME_SEPARATOR + csvTimestamp; + if (isExpiredTimeStamp(Long.parseLong(csvTimestamp))) { throw new NotFoundException("Failed to fetch CSV data due to CSV not found"); } - return csvFileGenerator.getDataFromCSV(reportDataType, csvTimestamp); + return csvFileGenerator.getDataFromCSV(reportDataType, timeRangeAndTimeStamp); } private boolean isExpiredTimeStamp(long timeStamp) { @@ -50,8 +56,8 @@ private boolean isExpiredTimeStamp(long timeStamp) { public void generateReport(GenerateReportRequest request) { List metricTypes = request.getMetricTypes(); - String timeStamp = request.getCsvTimeStamp(); - initializeMetricsDataCompletedInHandler(metricTypes, timeStamp); + String timeRangeAndTimeStamp = request.getTimeRangeAndTimeStamp(); + initializeMetricsDataCompletedInHandler(metricTypes, timeRangeAndTimeStamp); Map> reportGeneratorMap = reportGenerator .getReportGenerator(generateReporterService); List> threadList = new ArrayList<>(); @@ -61,28 +67,32 @@ public void generateReport(GenerateReportRequest request) { threadList.add(metricTypeThread); } - CompletableFuture.runAsync(() -> { - for (CompletableFuture thread : threadList) { - thread.join(); - } - - ReportResponse reportResponse = generateReporterService.getComposedReportResponse(timeStamp); + CompletableFuture allFutures = CompletableFuture.allOf(threadList.toArray(new CompletableFuture[0])); + allFutures.thenRun(() -> { + ReportResponse reportResponse = generateReporterService.getComposedReportResponse(request.getCsvTimeStamp(), + convertTimeStampToYYYYMMDD(request.getStartTime()), + convertTimeStampToYYYYMMDD(request.getEndTime())); if (isNotGenerateMetricError(reportResponse.getReportMetricsError())) { - generateReporterService.generateCSVForMetric(reportResponse, timeStamp); + generateReporterService.generateCSVForMetric(reportResponse, request.getTimeRangeAndTimeStamp()); } - asyncMetricsDataHandler.updateOverallMetricsCompletedInHandler(IdUtil.getDataCompletedPrefix(timeStamp)); + asyncMetricsDataHandler.updateOverallMetricsCompletedInHandler( + IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp())); }); } + private String convertTimeStampToYYYYMMDD(String timeStamp) { + return TimeUtil.convertToChinaSimpleISOFormat(Long.parseLong(timeStamp)); + } + private boolean isNotGenerateMetricError(ReportMetricsError reportMetricsError) { return Objects.isNull(reportMetricsError.getBoardMetricsError()) && Objects.isNull(reportMetricsError.getSourceControlMetricsError()) && Objects.isNull(reportMetricsError.getPipelineMetricsError()); } - private void initializeMetricsDataCompletedInHandler(List metricTypes, String timeStamp) { + private void initializeMetricsDataCompletedInHandler(List metricTypes, String timeRangeAndTimeStamp) { MetricsDataCompleted previousMetricsDataCompleted = asyncMetricsDataHandler - .getMetricsDataCompleted(IdUtil.getDataCompletedPrefix(timeStamp)); + .getMetricsDataCompleted(IdUtil.getDataCompletedPrefix(timeRangeAndTimeStamp)); Boolean initializeBoardMetricsCompleted = null; Boolean initializeDoraMetricsCompleted = null; if (!Objects.isNull(previousMetricsDataCompleted)) { @@ -90,7 +100,8 @@ private void initializeMetricsDataCompletedInHandler(List metricType initializeDoraMetricsCompleted = previousMetricsDataCompleted.doraMetricsCompleted(); } asyncMetricsDataHandler - .putMetricsDataCompleted(IdUtil.getDataCompletedPrefix(timeStamp), MetricsDataCompleted.builder() + .putMetricsDataCompleted(IdUtil.getDataCompletedPrefix(timeRangeAndTimeStamp), MetricsDataCompleted + .builder() .boardMetricsCompleted(metricTypes.contains(BOARD) ? Boolean.FALSE : initializeBoardMetricsCompleted) .doraMetricsCompleted(metricTypes.contains(DORA) ? Boolean.FALSE : initializeDoraMetricsCompleted) .overallMetricCompleted(Boolean.FALSE) diff --git a/backend/src/main/java/heartbeat/service/source/github/GitHubService.java b/backend/src/main/java/heartbeat/service/source/github/GitHubService.java index 526483e948..27a51d55a8 100644 --- a/backend/src/main/java/heartbeat/service/source/github/GitHubService.java +++ b/backend/src/main/java/heartbeat/service/source/github/GitHubService.java @@ -205,9 +205,11 @@ private LeadTime parseNoMergeLeadTime(DeployInfo deployInfo, PipelineInfoOfRepos deployInfo.getCommitId(), e.getMessage()); } + Long noPRCommitTime = null; if (commitInfo.getCommit() != null && commitInfo.getCommit().getCommitter() != null && commitInfo.getCommit().getCommitter().getDate() != null) { - firstCommitTime = Instant.parse(commitInfo.getCommit().getCommitter().getDate()).toEpochMilli(); + noPRCommitTime = Instant.parse(commitInfo.getCommit().getCommitter().getDate()).toEpochMilli(); + firstCommitTime = noPRCommitTime; } else { firstCommitTime = jobStartTime; @@ -217,12 +219,24 @@ private LeadTime parseNoMergeLeadTime(DeployInfo deployInfo, PipelineInfoOfRepos .commitId(deployInfo.getCommitId()) .pipelineCreateTime(pipelineCreateTime) .jobFinishTime(jobFinishTime) + .jobStartTime(jobStartTime) + .noPRCommitTime(noPRCommitTime) + .firstCommitTime(firstCommitTime) .pipelineLeadTime(jobFinishTime - firstCommitTime) .totalTime(jobFinishTime - firstCommitTime) .prLeadTime(prLeadTime) + .isRevert(isRevert(commitInfo)) .build(); } + private Boolean isRevert(CommitInfo commitInfo) { + Boolean isRevert = null; + if (commitInfo.getCommit() != null && commitInfo.getCommit().getMessage() != null) { + isRevert = commitInfo.getCommit().getMessage().toLowerCase().startsWith("revert"); + } + return isRevert; + } + public LeadTime mapLeadTimeWithInfo(PullRequestInfo pullRequestInfo, DeployInfo deployInfo, CommitInfo commitInfo) { if (pullRequestInfo.getMergedAt() == null) { return null; @@ -230,6 +244,7 @@ public LeadTime mapLeadTimeWithInfo(PullRequestInfo pullRequestInfo, DeployInfo long prCreatedTime = Instant.parse(pullRequestInfo.getCreatedAt()).toEpochMilli(); long prMergedTime = Instant.parse(pullRequestInfo.getMergedAt()).toEpochMilli(); long jobFinishTime = Instant.parse(deployInfo.getJobFinishTime()).toEpochMilli(); + long jobStartTime = Instant.parse(deployInfo.getJobStartTime()).toEpochMilli(); long pipelineCreateTime = Instant.parse(deployInfo.getPipelineCreateTime()).toEpochMilli(); long firstCommitTimeInPr; if (commitInfo.getCommit() != null && commitInfo.getCommit().getCommitter() != null @@ -243,16 +258,12 @@ public LeadTime mapLeadTimeWithInfo(PullRequestInfo pullRequestInfo, DeployInfo long pipelineLeadTime = jobFinishTime - prMergedTime; long prLeadTime; long totalTime; - if (commitInfo.getCommit().getMessage().toLowerCase().startsWith("revert")) { + Boolean isRevert = isRevert(commitInfo); + if (Boolean.TRUE.equals(isRevert) || isNoFirstCommitTimeInPr(firstCommitTimeInPr)) { prLeadTime = 0; } else { - if (firstCommitTimeInPr > 0) { - prLeadTime = prMergedTime - firstCommitTimeInPr; - } - else { - prLeadTime = prMergedTime - prCreatedTime; - } + prLeadTime = prMergedTime - firstCommitTimeInPr; } totalTime = prLeadTime + pipelineLeadTime; @@ -265,10 +276,17 @@ public LeadTime mapLeadTimeWithInfo(PullRequestInfo pullRequestInfo, DeployInfo .prCreatedTime(prCreatedTime) .commitId(deployInfo.getCommitId()) .jobFinishTime(jobFinishTime) + .jobStartTime(jobStartTime) + .firstCommitTime(prMergedTime) .pipelineCreateTime(pipelineCreateTime) + .isRevert(isRevert) .build(); } + private static boolean isNoFirstCommitTimeInPr(long firstCommitTimeInPr) { + return firstCommitTimeInPr == 0; + } + public CommitInfo fetchCommitInfo(String commitId, String repositoryId, String token) { try { String realToken = BEARER_TITLE + token; diff --git a/backend/src/main/java/heartbeat/util/BoardUtil.java b/backend/src/main/java/heartbeat/util/BoardUtil.java index c974755051..edbfd545ee 100644 --- a/backend/src/main/java/heartbeat/util/BoardUtil.java +++ b/backend/src/main/java/heartbeat/util/BoardUtil.java @@ -20,10 +20,12 @@ public class BoardUtil { private final WorkDay workDay; - public List getOriginCycleTimeInfos(List statusChangedArray) { + public List getOriginCycleTimeInfos(List statusChangedArray, + Boolean treatFlagCardAsBlock) { List flagTimeStamp = getFlagTimeStamps(statusChangedArray); List columnTimeStamp = getColumnTimeStamps(statusChangedArray); - List originCycleTimeInfos = calculateOriginCycleTime(flagTimeStamp, columnTimeStamp); + List originCycleTimeInfos = calculateOriginCycleTime(flagTimeStamp, columnTimeStamp, + treatFlagCardAsBlock); return getCollectRemovedDuplicates(originCycleTimeInfos); } @@ -50,7 +52,7 @@ public List getCycleTimeInfos(List statusChang } private List calculateOriginCycleTime(List flagTimeStamp, - List columnTimeStamp) { + List columnTimeStamp, Boolean treatFlagCardAsBlock) { List originCycleTimeInfos = new ArrayList<>(); for (StatusTimeStamp columnTimeStampItem : columnTimeStamp) { @@ -62,9 +64,14 @@ private List calculateOriginCycleTime(List flagT .build()); } - double totalFlagTimeInDays = calculateTotalFlagCycleTime(flagTimeStamp); - originCycleTimeInfos - .add(CycleTimeInfo.builder().day(totalFlagTimeInDays).column(CardStepsEnum.FLAG.getValue()).build()); + if (Boolean.TRUE.equals(treatFlagCardAsBlock)) { + double totalFlagTimeInDays = calculateTotalFlagCycleTime(flagTimeStamp); + originCycleTimeInfos + .add(CycleTimeInfo.builder().day(totalFlagTimeInDays).column(CardStepsEnum.FLAG.getValue()).build()); + originCycleTimeInfos = originCycleTimeInfos.stream() + .filter(it -> !Objects.equals(it.getColumn(), CardStepsEnum.BLOCK.getValue().toUpperCase())) + .toList(); + } return originCycleTimeInfos; } @@ -101,10 +108,8 @@ private List calculateCycleTime(List realDoneStatus, List } if (!isBlockColumnExisted(columnTimeStamp) && totalFlagTimeInDays > 0) { double blockDays = totalFlagTimeInDays - totalFlagAndRealDoneOverlapTime; - cycleTimeInfos.add(CycleTimeInfo.builder() - .day(blockDays) - .column(CardStepsEnum.BLOCK.getValue().toUpperCase()) - .build()); + cycleTimeInfos.add( + CycleTimeInfo.builder().day(blockDays).column(CardStepsEnum.FLAG.getValue().toUpperCase()).build()); } return cycleTimeInfos; diff --git a/backend/src/main/java/heartbeat/util/IdUtil.java b/backend/src/main/java/heartbeat/util/IdUtil.java index d9181768af..de23eb0496 100644 --- a/backend/src/main/java/heartbeat/util/IdUtil.java +++ b/backend/src/main/java/heartbeat/util/IdUtil.java @@ -10,20 +10,20 @@ public interface IdUtil { String DATA_COMPLETED_PREFIX = "dataCompleted-"; - static String getBoardReportId(String timeStamp) { - return BOARD_REPORT_PREFIX + timeStamp; + static String getBoardReportFileId(String timeRangeAndTimeStamp) { + return BOARD_REPORT_PREFIX + timeRangeAndTimeStamp; } - static String getPipelineReportId(String timeStamp) { - return PIPELINE_REPORT_PREFIX + timeStamp; + static String getPipelineReportFileId(String timeRangeAndTimeStamp) { + return PIPELINE_REPORT_PREFIX + timeRangeAndTimeStamp; } - static String getSourceControlReportId(String timeStamp) { - return SOURCE_CONTROL_PREFIX + timeStamp; + static String getSourceControlReportFileId(String timeRangeAndTimeStamp) { + return SOURCE_CONTROL_PREFIX + timeRangeAndTimeStamp; } - static String getDataCompletedPrefix(String timeStamp) { - return DATA_COMPLETED_PREFIX + timeStamp; + static String getDataCompletedPrefix(String timeRangeAndTimeStamp) { + return DATA_COMPLETED_PREFIX + timeRangeAndTimeStamp; } } diff --git a/backend/src/main/java/heartbeat/util/TimeUtil.java b/backend/src/main/java/heartbeat/util/TimeUtil.java index 7e56782ba3..25cf49acfe 100644 --- a/backend/src/main/java/heartbeat/util/TimeUtil.java +++ b/backend/src/main/java/heartbeat/util/TimeUtil.java @@ -32,6 +32,11 @@ static String convertToSimpleISOFormat(Long timestamp) { return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); } + static String convertToChinaSimpleISOFormat(Long timestamp) { + LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.of("Asia/Shanghai")); + return dateTime.format(DateTimeFormatter.ofPattern("yyyyMMdd")); + } + static String msToHMS(long timeStamp) { long tempTimeStamp = timeStamp; long milliseconds = tempTimeStamp % 1000; diff --git a/backend/src/test/java/heartbeat/controller/report/ReporterControllerTest.java b/backend/src/test/java/heartbeat/controller/report/ReporterControllerTest.java index 9b29650093..b49dc00daf 100644 --- a/backend/src/test/java/heartbeat/controller/report/ReporterControllerTest.java +++ b/backend/src/test/java/heartbeat/controller/report/ReporterControllerTest.java @@ -31,9 +31,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.mockito.Mockito.times; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -58,18 +58,25 @@ class ReporterControllerTest { private final ObjectMapper mapper = new ObjectMapper(); + public static final String START_TIME = "20240310"; + + public static final String END_TIME = "20240409"; + @Test void shouldGetSuccessDataGivenReportId() throws Exception { - String reportId = Long.toString(System.currentTimeMillis()); + String timeStamp = Long.toString(System.currentTimeMillis()); ReportResponse MockReportResponse = ReportResponse.builder() .boardMetricsCompleted(true) .allMetricsCompleted(true) .build(); - when(generateReporterService.getComposedReportResponse(reportId)).thenReturn(MockReportResponse); + when(generateReporterService.getComposedReportResponse(timeStamp, START_TIME, END_TIME)) + .thenReturn(MockReportResponse); String reportResponseString = mockMvc - .perform(get("/reports/{reportId}", reportId).contentType(MediaType.APPLICATION_JSON)) + .perform(get("/reports/{reportId}", timeStamp).param("startTime", START_TIME) + .param("endTime", END_TIME) + .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.allMetricsCompleted").value(true)) .andReturn() @@ -77,7 +84,7 @@ void shouldGetSuccessDataGivenReportId() throws Exception { .getContentAsString(); ReportResponse response = mapper.readValue(reportResponseString, new TypeReference<>() { }); - verify(generateReporterService).getComposedReportResponse(any()); + verify(generateReporterService).getComposedReportResponse(any(), any(), any()); assertEquals(true, response.getBoardMetricsCompleted()); assertEquals(true, response.getAllMetricsCompleted()); } @@ -87,26 +94,28 @@ void shouldReturn500StatusWhenRequestGenerateReportGivenReportTimeIsExpired() th String reportId = Long.toString(System.currentTimeMillis() - EXPORT_CSV_VALIDITY_TIME - 200L); doThrow(new GenerateReportException("Failed to get report due to report time expires")) .when(generateReporterService) - .getComposedReportResponse(any()); + .getComposedReportResponse(any(), any(), any()); mockMvc.perform(get("/reports/{reportId}", reportId).contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isInternalServerError()) .andExpect(jsonPath("$.message").value("Failed to get report due to report time expires")) .andReturn() .getResponse(); - verify(generateReporterService).getComposedReportResponse(any()); + verify(generateReporterService).getComposedReportResponse(any(), any(), any()); } @Test void shouldReturnWhenExportCsv() throws Exception { - long csvTimeStamp = TimeUtils.mockTimeStamp(2023, 5, 25, 18, 21, 20); + long timeStamp = TimeUtils.mockTimeStamp(2023, 5, 25, 18, 21, 20); String expectedResponse = "csv data"; - when(reporterService.exportCsv(ReportType.PIPELINE, csvTimeStamp)) + when(reporterService.exportCsv(ReportType.PIPELINE, String.valueOf(timeStamp), START_TIME, END_TIME)) .thenReturn(new InputStreamResource(new ByteArrayInputStream(expectedResponse.getBytes()))); MockHttpServletResponse response = mockMvc - .perform(get("/reports/{reportType}/{csvTimeStamp}", ReportType.PIPELINE.getValue(), csvTimeStamp)) + .perform(get("/reports/{reportType}/{timeStamp}", ReportType.PIPELINE.getValue(), timeStamp) + .param("startTime", START_TIME) + .param("endTime", END_TIME)) .andExpect(status().isOk()) .andReturn() .getResponse(); @@ -119,7 +128,9 @@ void shouldReturnCallBackUrlWithAcceptedStatusAndInvokeWhenGenerateReportByType( ObjectMapper mapper = new ObjectMapper(); GenerateReportRequest request = mapper.readValue(new File(REQUEST_FILE_PATH), GenerateReportRequest.class); - String currentTimeStamp = "1685010080107"; + String currentTimeStamp = String.valueOf(TimeUtils.mockTimeStamp(2023, 5, 25, 18, 21, 20)); + String startTime = "20220829"; + String endTme = "20220909"; request.setCsvTimeStamp(currentTimeStamp); doAnswer(invocation -> null).when(reporterService).generateReport(request); @@ -128,7 +139,8 @@ void shouldReturnCallBackUrlWithAcceptedStatusAndInvokeWhenGenerateReportByType( .perform(post("/reports").contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsString(request))) .andExpect(status().isAccepted()) - .andExpect(jsonPath("$.callbackUrl").value("/reports/" + currentTimeStamp)) + .andExpect(jsonPath("$.callbackUrl") + .value("/reports/" + currentTimeStamp + "?startTime=" + startTime + "&endTime=" + endTme)) .andExpect(jsonPath("$.interval").value("10")) .andReturn() .getResponse(); diff --git a/backend/src/test/java/heartbeat/controller/report/dto/request/GenerateReportRequestTest.java b/backend/src/test/java/heartbeat/controller/report/dto/request/GenerateReportRequestTest.java index 12324fde17..e3f9c675c7 100644 --- a/backend/src/test/java/heartbeat/controller/report/dto/request/GenerateReportRequestTest.java +++ b/backend/src/test/java/heartbeat/controller/report/dto/request/GenerateReportRequestTest.java @@ -16,6 +16,8 @@ public class GenerateReportRequestTest { private final GenerateReportRequest request = GenerateReportRequest.builder() .metrics(UPPER_METRICS) .csvTimeStamp("123456789") + .startTime("1710000000000") + .endTime("1712678399999") .build(); @Test @@ -38,13 +40,13 @@ void shouldReturnRelatedMetrics() { @Test void shouldReturnRelatedReportId() { - String boardReportId = request.getBoardReportId(); - String pipelineReportId = request.getPipelineReportId(); - String sourceControlReportId = request.getSourceControlReportId(); + String boardReportId = request.getBoardReportFileId(); + String pipelineReportId = request.getPipelineReportFileId(); + String sourceControlReportId = request.getSourceControlReportFileId(); - Assertions.assertEquals("board-123456789", boardReportId); - Assertions.assertEquals("pipeline-123456789", pipelineReportId); - Assertions.assertEquals("sourceControl-123456789", sourceControlReportId); + Assertions.assertEquals("board-20240310-20240409-123456789", boardReportId); + Assertions.assertEquals("pipeline-20240310-20240409-123456789", pipelineReportId); + Assertions.assertEquals("sourceControl-20240310-20240409-123456789", sourceControlReportId); } } diff --git a/backend/src/test/java/heartbeat/controller/report/dto/response/LeadTimeInfoTest.java b/backend/src/test/java/heartbeat/controller/report/dto/response/LeadTimeInfoTest.java index 690d16df6a..25a045ba49 100644 --- a/backend/src/test/java/heartbeat/controller/report/dto/response/LeadTimeInfoTest.java +++ b/backend/src/test/java/heartbeat/controller/report/dto/response/LeadTimeInfoTest.java @@ -10,18 +10,24 @@ public class LeadTimeInfoTest { @Test void shouldCreateLeadTimeInfoWithLeadTime() { LeadTimeInfo info = new LeadTimeInfo(LeadTime.builder() - .prLeadTime(47255635l) - .firstCommitTimeInPr(1672556350000l) - .prCreatedTime(1706067997l) - .prMergedTime(1706067997l) - .totalTime(57255635l) + .prLeadTime(47255635L) + .firstCommitTimeInPr(1672556350000L) + .prCreatedTime(1706067997L) + .prMergedTime(1706067997L) + .firstCommitTime(1706067997L) + .noPRCommitTime(1706067997L) + .totalTime(57255635L) + .isRevert(Boolean.FALSE) .build()); assertEquals("13:7:35", info.getPrLeadTime()); assertEquals("2023-01-01T06:59:10Z", info.getFirstCommitTimeInPr()); assertEquals("1970-01-20T17:54:27Z", info.getPrMergedTime()); assertEquals("1970-01-20T17:54:27Z", info.getPrCreatedTime()); + assertEquals("1970-01-20T17:54:27Z", info.getFirstCommitTime()); + assertEquals("1970-01-20T17:54:27Z", info.getNoPRCommitTime()); assertEquals("15:54:15", info.getTotalTime()); + assertEquals(Boolean.FALSE, info.getIsRevert()); } } diff --git a/backend/src/test/java/heartbeat/handler/AsyncExceptionHandlerTest.java b/backend/src/test/java/heartbeat/handler/AsyncExceptionHandlerTest.java index 0070b27e6f..19aa8e5f71 100644 --- a/backend/src/test/java/heartbeat/handler/AsyncExceptionHandlerTest.java +++ b/backend/src/test/java/heartbeat/handler/AsyncExceptionHandlerTest.java @@ -34,6 +34,10 @@ class AsyncExceptionHandlerTest { public static final String APP_OUTPUT_ERROR = "./app/output/error"; + public static final String START_TIME = "20240417"; + + public static final String END_TIME = "20240418"; + @InjectMocks AsyncExceptionHandler asyncExceptionHandler; @@ -56,8 +60,8 @@ void shouldDeleteAsyncException() { long fileId = System.currentTimeMillis(); String currentTime = Long.toString(fileId); String expireTime = Long.toString(fileId - 1900000L); - String unExpireFile = IdUtil.getBoardReportId(currentTime); - String expireFile = IdUtil.getBoardReportId(expireTime); + String unExpireFile = getBoardReportFileId(currentTime); + String expireFile = getBoardReportFileId(expireTime); asyncExceptionHandler.put(unExpireFile, new UnauthorizedException("")); asyncExceptionHandler.put(expireFile, new UnauthorizedException("")); @@ -74,8 +78,8 @@ void shouldDeleteAsyncExceptionTmpFile() { long fileId = System.currentTimeMillis(); String currentTime = Long.toString(fileId); String expireTime = Long.toString(fileId - 1900000L); - String unExpireFile = IdUtil.getBoardReportId(currentTime) + ".tmp"; - String expireFile = IdUtil.getBoardReportId(expireTime) + ".tmp"; + String unExpireFile = getBoardReportFileId(currentTime) + ".tmp"; + String expireFile = getBoardReportFileId(expireTime) + ".tmp"; asyncExceptionHandler.put(unExpireFile, new UnauthorizedException("")); asyncExceptionHandler.put(expireFile, new UnauthorizedException("")); @@ -92,8 +96,8 @@ void shouldSafeDeleteAsyncExceptionWhenHaveManyThordToDeleteFile() throws Interr long fileId = System.currentTimeMillis(); String currentTime = Long.toString(fileId); String expireTime = Long.toString(fileId - 1900000L); - String unExpireFile = IdUtil.getBoardReportId(currentTime); - String expireFile = IdUtil.getBoardReportId(expireTime); + String unExpireFile = getBoardReportFileId(currentTime); + String expireFile = getBoardReportFileId(expireTime); asyncExceptionHandler.put(unExpireFile, new UnauthorizedException("")); asyncExceptionHandler.put(expireFile, new UnauthorizedException("")); CyclicBarrier barrier = new CyclicBarrier(3); @@ -125,7 +129,7 @@ void shouldSafeDeleteAsyncExceptionWhenHaveManyThordToDeleteFile() throws Interr void shouldPutAndGetAsyncException() { long currentTimeMillis = System.currentTimeMillis(); String currentTime = Long.toString(currentTimeMillis); - String boardReportId = IdUtil.getBoardReportId(currentTime); + String boardReportId = getBoardReportFileId(currentTime); asyncExceptionHandler.put(boardReportId, new UnauthorizedException("test")); var baseException = asyncExceptionHandler.get(boardReportId); @@ -147,7 +151,7 @@ void shouldThrowExceptionGivenCantWriteFileWhenPutFile() { @Test void shouldThrowExceptionGivenCannotReadFileWhenGetFile() throws IOException { new File("./app/output/error/").mkdirs(); - String boardReportId = IdUtil.getBoardReportId(Long.toString(System.currentTimeMillis())); + String boardReportId = getBoardReportFileId(Long.toString(System.currentTimeMillis())); Path filePath = Paths.get("./app/output/error/" + boardReportId); Files.createFile(filePath); Files.write(filePath, "test".getBytes()); @@ -163,7 +167,7 @@ void shouldCreateTargetDirWhenPutAsyncException() { boolean mkdirs = new File(APP_OUTPUT_ERROR).mkdirs(); long currentTimeMillis = System.currentTimeMillis(); String currentTime = Long.toString(currentTimeMillis); - String boardReportId = IdUtil.getBoardReportId(currentTime); + String boardReportId = getBoardReportFileId(currentTime); asyncExceptionHandler.put(boardReportId, new UnauthorizedException("test")); @@ -177,7 +181,7 @@ void shouldCreateTargetDirWhenPutAsyncException() { void shouldPutAndRemoveAsyncException() { long currentTimeMillis = System.currentTimeMillis(); String currentTime = Long.toString(currentTimeMillis); - String boardReportId = IdUtil.getBoardReportId(currentTime); + String boardReportId = getBoardReportFileId(currentTime); asyncExceptionHandler.put(boardReportId, new UnauthorizedException("test")); AsyncExceptionDTO baseException = asyncExceptionHandler.remove(boardReportId); @@ -190,7 +194,7 @@ void shouldPutAndRemoveAsyncException() { @Test void shouldReturnExceptionGivenWrongFileWhenReadAndRemoveAsyncException() throws IOException { new File("./app/output/error/").mkdirs(); - String boardReportId = IdUtil.getBoardReportId(Long.toString(System.currentTimeMillis())); + String boardReportId = getBoardReportFileId(Long.toString(System.currentTimeMillis())); Path filePath = Paths.get("./app/output/error/" + boardReportId); Files.createFile(filePath); Files.write(filePath, "test".getBytes()); @@ -204,7 +208,7 @@ void shouldReturnExceptionGivenWrongFileWhenReadAndRemoveAsyncException() throws @Test void shouldThrowExceptionWhenDeleteFile() { File mockFile = mock(File.class); - when(mockFile.getName()).thenReturn("board-1683734399999"); + when(mockFile.getName()).thenReturn("board-20240417-20240418-1683734399999"); when(mockFile.delete()).thenThrow(new RuntimeException("test")); File[] mockFiles = new File[] { mockFile }; File directory = mock(File.class); @@ -217,7 +221,7 @@ void shouldThrowExceptionWhenDeleteFile() { @Test void shouldDeleteFailWhenDeleteFile() { File mockFile = mock(File.class); - when(mockFile.getName()).thenReturn("board-1683734399999"); + when(mockFile.getName()).thenReturn("board-20240417-20240418-1683734399999"); when(mockFile.delete()).thenReturn(false); when(mockFile.exists()).thenReturn(true); File[] mockFiles = new File[] { mockFile }; @@ -237,4 +241,8 @@ private void deleteTestFile(String reportId) { asyncExceptionHandler.remove(reportId); } + private String getBoardReportFileId(String timestamp) { + return "board-" + START_TIME + "-" + END_TIME + "-" + timestamp; + } + } diff --git a/backend/src/test/java/heartbeat/handler/AsyncMetricsDataHandlerTest.java b/backend/src/test/java/heartbeat/handler/AsyncMetricsDataHandlerTest.java index 612465955d..143902446a 100644 --- a/backend/src/test/java/heartbeat/handler/AsyncMetricsDataHandlerTest.java +++ b/backend/src/test/java/heartbeat/handler/AsyncMetricsDataHandlerTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.junit.jupiter.MockitoExtension; @@ -17,9 +18,15 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import static heartbeat.controller.report.dto.request.MetricType.BOARD; +import static heartbeat.controller.report.dto.request.MetricType.DORA; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -90,7 +97,7 @@ class DeleteExpireMetricsDataCompletedFile { @Test void shouldDeleteMetricsDataReadyWhenMetricsFileIsExpire() throws IOException { long currentTimeMillis = System.currentTimeMillis(); - String prefix = "prefix-"; + String prefix = "prefix-20240417-20240418-"; String currentTimeFileId = prefix + currentTimeMillis; String expireTimeFileId = prefix + (currentTimeMillis - 1900000L); String expireTimeLockFileId = prefix + (currentTimeMillis - 1900000L) + ".lock"; @@ -142,8 +149,7 @@ void shouldThrowGenerateReportExceptionWhenPreviousMetricsStatusIsNull() { String currentTime = Long.toString(currentTimeMillis); GenerateReportException exception = assertThrows(GenerateReportException.class, - () -> asyncMetricsDataHandler.updateMetricsDataCompletedInHandler(currentTime, MetricType.BOARD, - false)); + () -> asyncMetricsDataHandler.updateMetricsDataCompletedInHandler(currentTime, BOARD, false)); assertEquals("Failed to update metrics data completed through this timestamp.", exception.getMessage()); } @@ -157,7 +163,7 @@ void shouldUpdateBoardMetricDataWhenPreviousMetricsStatusIsNotNullAndMetricTypeI .build(); asyncMetricsDataHandler.putMetricsDataCompleted(currentTime, metricsDataCompleted); - asyncMetricsDataHandler.updateMetricsDataCompletedInHandler(currentTime, MetricType.BOARD, true); + asyncMetricsDataHandler.updateMetricsDataCompletedInHandler(currentTime, BOARD, true); MetricsDataCompleted completed = asyncMetricsDataHandler.getMetricsDataCompleted(currentTime); assertTrue(completed.boardMetricsCompleted()); @@ -239,6 +245,67 @@ void shouldUpdateAllMetricDataWhenPreviousMetricsStatusIsNotNull() throws IOExce } + @Nested + class UpdateAllMetricsCompletedInHandlerAtTheSameTime { + + // The test should be moved to integration test next. + @RepeatedTest(100) + @SuppressWarnings("unchecked") + void shouldUpdateAllMetricDataAtTheSameTimeWhenPreviousMetricsStatusIsNotNull() throws IOException { + long currentTimeMillis = System.currentTimeMillis(); + String currentTime = Long.toString(currentTimeMillis); + List sleepTime = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + sleepTime.add(new Random().nextInt(100)); + } + MetricsDataCompleted metricsDataCompleted = MetricsDataCompleted.builder() + .boardMetricsCompleted(false) + .doraMetricsCompleted(false) + .overallMetricCompleted(false) + .build(); + asyncMetricsDataHandler.putMetricsDataCompleted(currentTime, metricsDataCompleted); + + List> threadList = new ArrayList<>(); + + threadList.add(CompletableFuture.runAsync(() -> { + try { + TimeUnit.MILLISECONDS.sleep(sleepTime.get(0)); // NOSONAR + } + catch (InterruptedException ignored) { + } + asyncMetricsDataHandler.updateMetricsDataCompletedInHandler(currentTime, BOARD, true); + })); + threadList.add(CompletableFuture.runAsync(() -> { + try { + TimeUnit.MILLISECONDS.sleep(sleepTime.get(1)); // NOSONAR + } + catch (InterruptedException ignored) { + } + asyncMetricsDataHandler.updateMetricsDataCompletedInHandler(currentTime, DORA, true); + })); + threadList.add(CompletableFuture.runAsync(() -> { + try { + TimeUnit.MILLISECONDS.sleep(sleepTime.get(2)); // NOSONAR + } + catch (InterruptedException ignored) { + } + asyncMetricsDataHandler.updateOverallMetricsCompletedInHandler(currentTime); + })); + + for (CompletableFuture thread : threadList) { + thread.join(); + } + + MetricsDataCompleted completed = asyncMetricsDataHandler.getMetricsDataCompleted(currentTime); + assertTrue(completed.boardMetricsCompleted()); + assertTrue(completed.doraMetricsCompleted()); + assertTrue(completed.allMetricsCompleted()); + Files.deleteIfExists(Path.of(APP_OUTPUT_METRICS + "/" + currentTime)); + assertNull(asyncMetricsDataHandler.getMetricsDataCompleted(currentTime)); + } + + } + private void createLockFile(String currentTime) throws IOException { String fileName = APP_OUTPUT_METRICS + "/" + currentTime + ".lock"; File file = new File(fileName); diff --git a/backend/src/test/java/heartbeat/handler/AsyncReportRequestHandlerTest.java b/backend/src/test/java/heartbeat/handler/AsyncReportRequestHandlerTest.java index 5a86093a9e..a09cab1bac 100644 --- a/backend/src/test/java/heartbeat/handler/AsyncReportRequestHandlerTest.java +++ b/backend/src/test/java/heartbeat/handler/AsyncReportRequestHandlerTest.java @@ -25,6 +25,10 @@ class AsyncReportRequestHandlerTest { public static final String APP_OUTPUT_REPORT = "./app/output/report"; + public static final String START_TIME = "20240417"; + + public static final String END_TIME = "20240418"; + @InjectMocks AsyncReportRequestHandler asyncReportRequestHandler; @@ -47,8 +51,8 @@ void shouldDeleteReportWhenReportIsExpire() throws IOException { long currentTimeMillis = System.currentTimeMillis(); String currentTime = Long.toString(currentTimeMillis); String expireTime = Long.toString(currentTimeMillis - 1900000L); - String unExpireFile = IdUtil.getBoardReportId(currentTime); - String expireFile = IdUtil.getBoardReportId(expireTime); + String unExpireFile = getBoardReportFileId(currentTime); + String expireFile = getBoardReportFileId(expireTime); asyncReportRequestHandler.putReport(unExpireFile, ReportResponse.builder().build()); asyncReportRequestHandler.putReport(expireFile, ReportResponse.builder().build()); @@ -64,7 +68,7 @@ void shouldDeleteReportWhenReportIsExpire() throws IOException { void shouldGetAsyncReportWhenPuttingReportIntoAsyncReportRequestHandler() throws IOException { long currentTimeMillis = System.currentTimeMillis(); String currentTime = Long.toString(currentTimeMillis); - String boardReportId = IdUtil.getBoardReportId(currentTime); + String boardReportId = IdUtil.getBoardReportFileId(currentTime); asyncReportRequestHandler.putReport(boardReportId, ReportResponse.builder().build()); @@ -79,9 +83,8 @@ void shouldThrowGenerateReportExceptionGivenFileNameInvalidWhenHandlerPutData() () -> asyncReportRequestHandler.putReport("../", ReportResponse.builder().build())); } - @Test - void shouldThrowGenerateReportExceptionGivenFileNameInvalidWhenHandlerGetData() { - assertThrows(GenerateReportException.class, () -> asyncReportRequestHandler.getReport("../")); + private String getBoardReportFileId(String timestamp) { + return "board-" + START_TIME + "-" + END_TIME + "-" + timestamp; } } diff --git a/backend/src/test/java/heartbeat/handler/base/AsyncDataBaseHandlerTest.java b/backend/src/test/java/heartbeat/handler/base/AsyncDataBaseHandlerTest.java index 7055f5953d..ababd2b214 100644 --- a/backend/src/test/java/heartbeat/handler/base/AsyncDataBaseHandlerTest.java +++ b/backend/src/test/java/heartbeat/handler/base/AsyncDataBaseHandlerTest.java @@ -1,9 +1,12 @@ package heartbeat.handler.base; +import heartbeat.controller.report.dto.response.ReportResponse; +import heartbeat.exception.GenerateReportException; import org.junit.jupiter.api.Test; import java.io.File; +import static org.junit.Assert.assertThrows; import static org.junit.jupiter.api.Assertions.assertFalse; class AsyncDataBaseHandlerTest { @@ -19,4 +22,12 @@ void shouldReturnFalseGivenCreateFileThrowExceptionWhenTryLock() { assertFalse(result); } + @Test + void shouldReturnGenerateReportExceptionGivenFileNotStartWithRightFilePath() { + File file = new File("./app/input/lock"); + + assertThrows(GenerateReportException.class, + () -> asyncDataBaseHandler.readFileByType(file, FIleType.ERROR, "111", ReportResponse.class)); + } + } diff --git a/backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java b/backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java index f404d6525e..b08f514373 100644 --- a/backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java +++ b/backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java @@ -1166,7 +1166,7 @@ public void shouldProcessCustomFieldsForCardsWhenCallGetStoryPointsAndCycleTime( when(jiraFeignClient.getJiraCardHistoryByCount(any(), any(), anyInt(), anyInt(), any())) .thenReturn(CARD_HISTORY_MULTI_RESPONSE_BUILDER().build()); when(boardUtil.getCycleTimeInfos(any(), any(), any())).thenReturn(CYCLE_TIME_INFO_LIST()); - when(boardUtil.getOriginCycleTimeInfos(any())).thenReturn(CYCLE_TIME_INFO_LIST()); + when(boardUtil.getOriginCycleTimeInfos(any(), any())).thenReturn(CYCLE_TIME_INFO_LIST()); CardCollection doneCards = jiraService.getStoryPointsAndCycleTimeAndReworkInfoForDoneCards( storyPointsAndCycleTimeRequest, jiraBoardSetting.getBoardColumns(), List.of("Zhang San"), ""); diff --git a/backend/src/test/java/heartbeat/service/pipeline/buildkite/BuildKiteServiceTest.java b/backend/src/test/java/heartbeat/service/pipeline/buildkite/BuildKiteServiceTest.java index a686fd522f..87d72a8040 100644 --- a/backend/src/test/java/heartbeat/service/pipeline/buildkite/BuildKiteServiceTest.java +++ b/backend/src/test/java/heartbeat/service/pipeline/buildkite/BuildKiteServiceTest.java @@ -132,7 +132,7 @@ public void shouldReturnResponseWhenFetchPipelineStepsSuccess() { List buildKiteBuildInfoList = new ArrayList<>(); buildKiteBuildInfoList.add(BuildKiteBuildInfo.builder() .jobs(List.of(testJob)) - .author(BuildKiteBuildInfo.Author.builder().name("xx").build()) + .author(BuildKiteBuildInfo.Author.builder().name("author").build()) .creator(BuildKiteBuildInfo.Creator.builder().name("xx").build()) .build()); ResponseEntity> responseEntity = new ResponseEntity<>(buildKiteBuildInfoList, @@ -146,6 +146,9 @@ public void shouldReturnResponseWhenFetchPipelineStepsSuccess() { assertNotNull(pipelineStepsDTO); assertThat(pipelineStepsDTO.getSteps().get(0)).isEqualTo(TEST_JOB_NAME); + assertEquals(2, pipelineStepsDTO.getPipelineCrews().size()); + assertEquals("author", pipelineStepsDTO.getPipelineCrews().get(0)); + assertEquals("Unknown", pipelineStepsDTO.getPipelineCrews().get(1)); } @Test diff --git a/backend/src/test/java/heartbeat/service/report/CSVFileGeneratorTest.java b/backend/src/test/java/heartbeat/service/report/CSVFileGeneratorTest.java index 839316fc7b..12f523280b 100644 --- a/backend/src/test/java/heartbeat/service/report/CSVFileGeneratorTest.java +++ b/backend/src/test/java/heartbeat/service/report/CSVFileGeneratorTest.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.stream.Collectors; +import static heartbeat.tools.TimeUtils.mockTimeStamp; import static org.junit.Assert.assertThrows; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -72,11 +73,11 @@ void shouldConvertPipelineDataToCsvGivenCommitInfoNotNull() throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream)); String headers = reader.readLine(); assertEquals( - "\"Organization\",\"Pipeline Name\",\"Pipeline Step\",\"Valid\",\"Build Number\",\"Code Committer\",\"Pipeline Creator\",\"First Code Committed Time In PR\",\"Code Committed Time\",\"PR Created Time\",\"PR Merged Time\",\"Deployment Completed Time\",\"Total Lead Time (HH:mm:ss)\",\"PR Lead Time (HH:mm:ss)\",\"Pipeline Lead Time (HH:mm:ss)\",\"Status\",\"Branch\"", + "\"Organization\",\"Pipeline Name\",\"Pipeline Step\",\"Valid\",\"Build Number\",\"Code Committer\",\"Build Creator\",\"First Code Committed Time In PR\",\"PR Created Time\",\"PR Merged Time\",\"No PR Committed Time\",\"Job Start Time\",\"Pipeline Start Time\",\"Pipeline Finish Time\",\"Total Lead Time (HH:mm:ss)\",\"PR Lead Time (HH:mm:ss)\",\"Pipeline Lead Time (HH:mm:ss)\",\"Status\",\"Branch\",\"Revert\"", headers); String firstLine = reader.readLine(); assertEquals( - "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"true\",\"880\",\"XXXX\",\"XXXX\",\"2023-05-08T07:18:18Z\",\"2023-05-10T06:43:02.653Z\",\"168369327000\",\"1683793037000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\"", + "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"true\",\"880\",\"XXXX\",,\"2023-05-08T07:18:18Z\",\"168369327000\",\"1683793037000\",,\"168369327000\",\"168369327000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\",\"\"", firstLine); reader.close(); fileInputStream.close(); @@ -98,11 +99,11 @@ void shouldConvertPipelineDataToCsvGivenCommitInfoNotNullAndPipelineStateIsCance BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream)); String headers = reader.readLine(); assertEquals( - "\"Organization\",\"Pipeline Name\",\"Pipeline Step\",\"Valid\",\"Build Number\",\"Code Committer\",\"Pipeline Creator\",\"First Code Committed Time In PR\",\"Code Committed Time\",\"PR Created Time\",\"PR Merged Time\",\"Deployment Completed Time\",\"Total Lead Time (HH:mm:ss)\",\"PR Lead Time (HH:mm:ss)\",\"Pipeline Lead Time (HH:mm:ss)\",\"Status\",\"Branch\"", + "\"Organization\",\"Pipeline Name\",\"Pipeline Step\",\"Valid\",\"Build Number\",\"Code Committer\",\"Build Creator\",\"First Code Committed Time In PR\",\"PR Created Time\",\"PR Merged Time\",\"No PR Committed Time\",\"Job Start Time\",\"Pipeline Start Time\",\"Pipeline Finish Time\",\"Total Lead Time (HH:mm:ss)\",\"PR Lead Time (HH:mm:ss)\",\"Pipeline Lead Time (HH:mm:ss)\",\"Status\",\"Branch\",\"Revert\"", headers); String firstLine = reader.readLine(); assertEquals( - "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"true\",\"880\",\"XXXX\",\"XXXX\",\"2023-05-08T07:18:18Z\",\"2023-05-10T06:43:02.653Z\",\"168369327000\",\"1683793037000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"canceled\",\"branch\"", + "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"true\",\"880\",\"XXXX\",,\"2023-05-08T07:18:18Z\",\"168369327000\",\"1683793037000\",,\"168369327000\",\"168369327000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"canceled\",\"branch\",\"\"", firstLine); reader.close(); fileInputStream.close(); @@ -123,10 +124,10 @@ void shouldConvertPipelineDataToCsvWithoutCreator() throws IOException { assertTrue(file.exists()); assertEquals( - "\"Organization\",\"Pipeline Name\",\"Pipeline Step\",\"Valid\",\"Build Number\",\"Code Committer\",\"Pipeline Creator\",\"First Code Committed Time In PR\",\"Code Committed Time\",\"PR Created Time\",\"PR Merged Time\",\"Deployment Completed Time\",\"Total Lead Time (HH:mm:ss)\",\"PR Lead Time (HH:mm:ss)\",\"Pipeline Lead Time (HH:mm:ss)\",\"Status\",\"Branch\"", + "\"Organization\",\"Pipeline Name\",\"Pipeline Step\",\"Valid\",\"Build Number\",\"Code Committer\",\"Build Creator\",\"First Code Committed Time In PR\",\"PR Created Time\",\"PR Merged Time\",\"No PR Committed Time\",\"Job Start Time\",\"Pipeline Start Time\",\"Pipeline Finish Time\",\"Total Lead Time (HH:mm:ss)\",\"PR Lead Time (HH:mm:ss)\",\"Pipeline Lead Time (HH:mm:ss)\",\"Status\",\"Branch\",\"Revert\"", headers); assertEquals( - "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"null\",\"880\",\"XXXX\",,\"2023-05-08T07:18:18Z\",\"2023-05-10T06:43:02.653Z\",\"168369327000\",\"1683793037000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\"", + "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"null\",\"880\",\"XXXX\",,\"2023-05-08T07:18:18Z\",\"168369327000\",\"1683793037000\",,\"1683793037000\",\"168369327000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\",\"\"", firstLine); reader.close(); fileInputStream.close(); @@ -147,10 +148,10 @@ void shouldConvertPipelineDataToCsvWithoutCreatorName() throws IOException { assertTrue(file.exists()); assertEquals( - "\"Organization\",\"Pipeline Name\",\"Pipeline Step\",\"Valid\",\"Build Number\",\"Code Committer\",\"Pipeline Creator\",\"First Code Committed Time In PR\",\"Code Committed Time\",\"PR Created Time\",\"PR Merged Time\",\"Deployment Completed Time\",\"Total Lead Time (HH:mm:ss)\",\"PR Lead Time (HH:mm:ss)\",\"Pipeline Lead Time (HH:mm:ss)\",\"Status\",\"Branch\"", + "\"Organization\",\"Pipeline Name\",\"Pipeline Step\",\"Valid\",\"Build Number\",\"Code Committer\",\"Build Creator\",\"First Code Committed Time In PR\",\"PR Created Time\",\"PR Merged Time\",\"No PR Committed Time\",\"Job Start Time\",\"Pipeline Start Time\",\"Pipeline Finish Time\",\"Total Lead Time (HH:mm:ss)\",\"PR Lead Time (HH:mm:ss)\",\"Pipeline Lead Time (HH:mm:ss)\",\"Status\",\"Branch\",\"Revert\"", headers); assertEquals( - "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"null\",\"880\",\"XXXX\",,\"2023-05-08T07:18:18Z\",\"2023-05-10T06:43:02.653Z\",\"168369327000\",\"1683793037000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\"", + "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"null\",\"880\",,,\"2023-05-08T07:18:18Z\",\"168369327000\",\"1683793037000\",,\"168369327000\",\"168369327000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\",\"\"", firstLine); reader.close(); fileInputStream.close(); @@ -171,11 +172,59 @@ void shouldConvertPipelineDataToCsvGivenNullCommitInfo() throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream)); String headers = reader.readLine(); assertEquals( - "\"Organization\",\"Pipeline Name\",\"Pipeline Step\",\"Valid\",\"Build Number\",\"Code Committer\",\"Pipeline Creator\",\"First Code Committed Time In PR\",\"Code Committed Time\",\"PR Created Time\",\"PR Merged Time\",\"Deployment Completed Time\",\"Total Lead Time (HH:mm:ss)\",\"PR Lead Time (HH:mm:ss)\",\"Pipeline Lead Time (HH:mm:ss)\",\"Status\",\"Branch\"", + "\"Organization\",\"Pipeline Name\",\"Pipeline Step\",\"Valid\",\"Build Number\",\"Code Committer\",\"Build Creator\",\"First Code Committed Time In PR\",\"PR Created Time\",\"PR Merged Time\",\"No PR Committed Time\",\"Job Start Time\",\"Pipeline Start Time\",\"Pipeline Finish Time\",\"Total Lead Time (HH:mm:ss)\",\"PR Lead Time (HH:mm:ss)\",\"Pipeline Lead Time (HH:mm:ss)\",\"Status\",\"Branch\",\"Revert\"", headers); String firstLine = reader.readLine(); assertEquals( - "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"true\",\"880\",,,\"2023-05-08T07:18:18Z\",,\"168369327000\",\"1683793037000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\"", + "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"true\",\"880\",\"XXXX\",,\"2023-05-08T07:18:18Z\",\"168369327000\",\"1683793037000\",,\"168369327000\",\"168369327000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\",\"\"", + firstLine); + reader.close(); + fileInputStream.close(); + file.delete(); + } + + @Test + void shouldConvertPipelineDataToCsvGivenCommitMessageIsRevert() throws IOException { + List pipelineCSVInfos = PipelineCsvFixture.MOCK_PIPELINE_CSV_DATA_WITH_MESSAGE_IS_REVERT(); + String fileName = CSVFileNameEnum.PIPELINE.getValue() + "-" + mockTimeStamp + ".csv"; + File file = new File(fileName); + + csvFileGenerator.convertPipelineDataToCSV(pipelineCSVInfos, mockTimeStamp); + FileInputStream fileInputStream = new FileInputStream(file); + BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream)); + String headers = reader.readLine(); + String firstLine = reader.readLine(); + + assertTrue(file.exists()); + assertEquals( + "\"Organization\",\"Pipeline Name\",\"Pipeline Step\",\"Valid\",\"Build Number\",\"Code Committer\",\"Build Creator\",\"First Code Committed Time In PR\",\"PR Created Time\",\"PR Merged Time\",\"No PR Committed Time\",\"Job Start Time\",\"Pipeline Start Time\",\"Pipeline Finish Time\",\"Total Lead Time (HH:mm:ss)\",\"PR Lead Time (HH:mm:ss)\",\"Pipeline Lead Time (HH:mm:ss)\",\"Status\",\"Branch\",\"Revert\"", + headers); + assertEquals( + "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"null\",\"880\",\"XXXX\",,\"2023-05-08T07:18:18Z\",\"168369327000\",\"1683793037000\",,\"168369327000\",\"168369327000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\",\"true\"", + firstLine); + reader.close(); + fileInputStream.close(); + file.delete(); + } + + @Test + void shouldConvertPipelineDataToCsvGivenAuthorIsNull() throws IOException { + List pipelineCSVInfos = PipelineCsvFixture.MOCK_PIPELINE_CSV_DATA_WITHOUT_Author_NAME(); + String fileName = CSVFileNameEnum.PIPELINE.getValue() + "-" + mockTimeStamp + ".csv"; + File file = new File(fileName); + + csvFileGenerator.convertPipelineDataToCSV(pipelineCSVInfos, mockTimeStamp); + FileInputStream fileInputStream = new FileInputStream(file); + BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream)); + String headers = reader.readLine(); + String firstLine = reader.readLine(); + + assertTrue(file.exists()); + assertEquals( + "\"Organization\",\"Pipeline Name\",\"Pipeline Step\",\"Valid\",\"Build Number\",\"Code Committer\",\"Build Creator\",\"First Code Committed Time In PR\",\"PR Created Time\",\"PR Merged Time\",\"No PR Committed Time\",\"Job Start Time\",\"Pipeline Start Time\",\"Pipeline Finish Time\",\"Total Lead Time (HH:mm:ss)\",\"PR Lead Time (HH:mm:ss)\",\"Pipeline Lead Time (HH:mm:ss)\",\"Status\",\"Branch\",\"Revert\"", + headers); + assertEquals( + "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"null\",\"880\",,\"XXX\",\"2023-05-08T07:18:18Z\",\"168369327000\",\"1683793037000\",,\"168369327000\",\"168369327000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\",\"\"", firstLine); reader.close(); fileInputStream.close(); @@ -202,15 +251,14 @@ void shouldHasContentWhenGetDataFromCsvGivenDataTypeIsPipeline() throws IOExcept List pipelineCSVInfos = PipelineCsvFixture.MOCK_PIPELINE_CSV_DATA(); csvFileGenerator.convertPipelineDataToCSV(pipelineCSVInfos, mockTimeStamp); - InputStreamResource inputStreamResource = csvFileGenerator.getDataFromCSV(ReportType.PIPELINE, - Long.parseLong(mockTimeStamp)); + InputStreamResource inputStreamResource = csvFileGenerator.getDataFromCSV(ReportType.PIPELINE, mockTimeStamp); InputStream csvDataInputStream = inputStreamResource.getInputStream(); String csvPipelineData = new BufferedReader(new InputStreamReader(csvDataInputStream)).lines() .collect(Collectors.joining("\n")); Assertions.assertEquals( - "\"Organization\",\"Pipeline Name\",\"Pipeline Step\",\"Valid\",\"Build Number\",\"Code Committer\",\"Pipeline Creator\",\"First Code Committed Time In PR\",\"Code Committed Time\",\"PR Created Time\",\"PR Merged Time\",\"Deployment Completed Time\",\"Total Lead Time (HH:mm:ss)\",\"PR Lead Time (HH:mm:ss)\",\"Pipeline Lead Time (HH:mm:ss)\",\"Status\",\"Branch\"\n" - + "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"true\",\"880\",\"XXXX\",\"XXXX\",\"2023-05-08T07:18:18Z\",\"2023-05-10T06:43:02.653Z\",\"168369327000\",\"1683793037000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\"", + "\"Organization\",\"Pipeline Name\",\"Pipeline Step\",\"Valid\",\"Build Number\",\"Code Committer\",\"Build Creator\",\"First Code Committed Time In PR\",\"PR Created Time\",\"PR Merged Time\",\"No PR Committed Time\",\"Job Start Time\",\"Pipeline Start Time\",\"Pipeline Finish Time\",\"Total Lead Time (HH:mm:ss)\",\"PR Lead Time (HH:mm:ss)\",\"Pipeline Lead Time (HH:mm:ss)\",\"Status\",\"Branch\",\"Revert\"\n" + + "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"true\",\"880\",\"XXXX\",,\"2023-05-08T07:18:18Z\",\"168369327000\",\"1683793037000\",,\"168369327000\",\"168369327000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\",\"\"", csvPipelineData); String fileName = CSVFileNameEnum.PIPELINE.getValue() + "-" + mockTimeStamp + ".csv"; @@ -233,22 +281,22 @@ void shouldConvertPipelineDataToCsvGivenTwoOrganizationsPipeline() throws IOExce BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream)); String headers = reader.readLine(); assertEquals( - "\"Organization\",\"Pipeline Name\",\"Pipeline Step\",\"Valid\",\"Build Number\",\"Code Committer\",\"Pipeline Creator\",\"First Code Committed Time In PR\",\"Code Committed Time\",\"PR Created Time\",\"PR Merged Time\",\"Deployment Completed Time\",\"Total Lead Time (HH:mm:ss)\",\"PR Lead Time (HH:mm:ss)\",\"Pipeline Lead Time (HH:mm:ss)\",\"Status\",\"Branch\"", + "\"Organization\",\"Pipeline Name\",\"Pipeline Step\",\"Valid\",\"Build Number\",\"Code Committer\",\"Build Creator\",\"First Code Committed Time In PR\",\"PR Created Time\",\"PR Merged Time\",\"No PR Committed Time\",\"Job Start Time\",\"Pipeline Start Time\",\"Pipeline Finish Time\",\"Total Lead Time (HH:mm:ss)\",\"PR Lead Time (HH:mm:ss)\",\"Pipeline Lead Time (HH:mm:ss)\",\"Status\",\"Branch\",\"Revert\"", headers); String firstLine = reader.readLine(); assertEquals( - "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"true\",\"880\",\"XXXX\",\"XXXX\",\"2023-05-08T07:18:18Z\",\"2023-05-10T06:43:02.653Z\",\"168369327000\",\"1683793037000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\"", + "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"true\",\"880\",,\"XXXX\",\"2023-05-08T07:18:18Z\",\"168369327000\",\"1683793037000\",,\"168369327000\",\"168369327000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\",\"\"", firstLine); String secondLine = reader.readLine(); assertEquals( - "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"true\",\"880\",\"XXXX\",\"XXXX\",\"2023-05-08T07:18:18Z\",\"2023-05-10T06:43:02.653Z\",\"168369327000\",\"1683793037000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\"", + "\"Thoughtworks-Heartbeat\",\"Heartbeat\",\":rocket: Deploy prod\",\"true\",\"880\",\"XXXX\",,\"2023-05-08T07:18:18Z\",\"168369327000\",\"1683793037000\",,\"168369327000\",\"168369327000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\",\"\"", secondLine); String thirdLine = reader.readLine(); assertEquals( - "\"Thoughtworks-Foxtel\",\"Heartbeat1\",\":rocket: Deploy prod\",\"true\",\"880\",\"XXXX\",\"XXXX\",\"2023-05-08T07:18:18Z\",\"2023-05-10T06:43:02.653Z\",\"168369327000\",\"1683793037000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\"", + "\"Thoughtworks-Foxtel\",\"Heartbeat1\",\":rocket: Deploy prod\",\"true\",\"880\",,\"XXXX\",\"2023-05-08T07:18:18Z\",\"168369327000\",\"1683793037000\",,\"168369327000\",\"168369327000\",\"1684793037000\",\"8379303\",\"16837\",\"653037000\",\"passed\",\"branch\",\"\"", thirdLine); reader.close(); @@ -259,7 +307,7 @@ void shouldConvertPipelineDataToCsvGivenTwoOrganizationsPipeline() throws IOExce @Test void shouldThrowExceptionWhenFileNotExist() { List pipelineCSVInfos = PipelineCsvFixture.MOCK_PIPELINE_CSV_DATA(); - assertThrows(FileIOException.class, () -> csvFileGenerator.getDataFromCSV(ReportType.PIPELINE, 123456L)); + assertThrows(FileIOException.class, () -> csvFileGenerator.getDataFromCSV(ReportType.PIPELINE, "123456")); assertThrows(FileIOException.class, () -> csvFileGenerator.convertPipelineDataToCSV(pipelineCSVInfos, "15469:89/033")); } @@ -302,8 +350,7 @@ void shouldGetValueWhenConvertBoardDataToCsvGivenExtraFields() throws IOExceptio List extraFields = BoardCsvFixture.MOCK_EXTRA_FIELDS_WITH_CUSTOM(); csvFileGenerator.convertBoardDataToCSV(cardDTOList, fields, extraFields, mockTimeStamp); - InputStreamResource inputStreamResource = csvFileGenerator.getDataFromCSV(ReportType.BOARD, - Long.parseLong(mockTimeStamp)); + InputStreamResource inputStreamResource = csvFileGenerator.getDataFromCSV(ReportType.BOARD, mockTimeStamp); InputStream csvDataInputStream = inputStreamResource.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(csvDataInputStream)); String header = reader.readLine(); @@ -366,7 +413,7 @@ void shouldThrowExceptionWhenBoardCsvNotExist() { List fields = BoardCsvFixture.MOCK_ALL_FIELDS(); List extraFields = BoardCsvFixture.MOCK_EXTRA_FIELDS(); - assertThrows(FileIOException.class, () -> csvFileGenerator.getDataFromCSV(ReportType.BOARD, 1686710104536L)); + assertThrows(FileIOException.class, () -> csvFileGenerator.getDataFromCSV(ReportType.BOARD, "1686710104536")); assertThrows(FileIOException.class, () -> csvFileGenerator.convertBoardDataToCSV(cardDTOList, fields, extraFields, "15469:89/033")); } @@ -378,8 +425,7 @@ void shouldHasContentWhenGetDataFromCsvGivenDataTypeIsBoard() throws IOException List extraFields = BoardCsvFixture.MOCK_EXTRA_FIELDS(); csvFileGenerator.convertBoardDataToCSV(cardDTOList, fields, extraFields, mockTimeStamp); - InputStreamResource inputStreamResource = csvFileGenerator.getDataFromCSV(ReportType.BOARD, - Long.parseLong(mockTimeStamp)); + InputStreamResource inputStreamResource = csvFileGenerator.getDataFromCSV(ReportType.BOARD, mockTimeStamp); InputStream csvDataInputStream = inputStreamResource.getInputStream(); String boardCsvData = new BufferedReader(new InputStreamReader(csvDataInputStream)).lines() .collect(Collectors.joining("\n")); @@ -432,19 +478,32 @@ void shouldMakeCsvDirWhenNotExistGivenDataTypeIsMetric() throws IOException { void shouldThrowExceptionWhenMetricCsvNotExist() { ReportResponse reportResponse = MetricCsvFixture.MOCK_METRIC_CSV_DATA(); - assertThrows(FileIOException.class, () -> csvFileGenerator.getDataFromCSV(ReportType.METRIC, 1686710104536L)); + assertThrows(FileIOException.class, () -> csvFileGenerator.getDataFromCSV(ReportType.METRIC, "1686710104536")); assertThrows(FileIOException.class, () -> csvFileGenerator.convertMetricDataToCSV(reportResponse, "15469:89/033")); } + @Test + void shouldThrowExceptionWhenTimeStampIsIllegal() { + String mockTimeRangeTimeStampWithBackSlash = mockTimeStamp(2021, 4, 9, 0, 0, 0) + "\\"; + String mockTimeRangeTimeStampWithSlash = mockTimeStamp(2021, 4, 9, 0, 0, 0) + "/"; + String mockTimeRangeTimeStampWithPoint = mockTimeStamp(2021, 4, 9, 0, 0, 0) + ".."; + + assertThrows(IllegalArgumentException.class, + () -> csvFileGenerator.getDataFromCSV(ReportType.METRIC, mockTimeRangeTimeStampWithBackSlash)); + assertThrows(IllegalArgumentException.class, + () -> csvFileGenerator.getDataFromCSV(ReportType.METRIC, mockTimeRangeTimeStampWithSlash)); + assertThrows(IllegalArgumentException.class, + () -> csvFileGenerator.getDataFromCSV(ReportType.METRIC, mockTimeRangeTimeStampWithPoint)); + } + @Test void shouldHasContentWhenGetDataFromCsvGivenDataTypeIsMetric() throws IOException { ReportResponse reportResponse = MetricCsvFixture.MOCK_METRIC_CSV_DATA(); csvFileGenerator.convertMetricDataToCSV(reportResponse, mockTimeStamp); - InputStreamResource inputStreamResource = csvFileGenerator.getDataFromCSV(ReportType.METRIC, - Long.parseLong(mockTimeStamp)); + InputStreamResource inputStreamResource = csvFileGenerator.getDataFromCSV(ReportType.METRIC, mockTimeStamp); InputStream csvDataInputStream = inputStreamResource.getInputStream(); String metricCsvData = new BufferedReader(new InputStreamReader(csvDataInputStream)).lines() .collect(Collectors.joining("\n")); @@ -501,8 +560,7 @@ void shouldHasNoContentWhenGetDataFromCsvGivenDataTypeIsMetricAndResponseIsEmpty ReportResponse reportResponse = MetricCsvFixture.MOCK_EMPTY_METRIC_CSV_DATA(); csvFileGenerator.convertMetricDataToCSV(reportResponse, mockTimeStamp); - InputStreamResource inputStreamResource = csvFileGenerator.getDataFromCSV(ReportType.METRIC, - Long.parseLong(mockTimeStamp)); + InputStreamResource inputStreamResource = csvFileGenerator.getDataFromCSV(ReportType.METRIC, mockTimeStamp); InputStream csvDataInputStream = inputStreamResource.getInputStream(); String metricCsvData = new BufferedReader(new InputStreamReader(csvDataInputStream)).lines() .collect(Collectors.joining("\n")); @@ -519,8 +577,7 @@ void shouldHasNoContentForAveragesWhenGetDataFromCsvGivenDataTypeIsMetricAndTheQ ReportResponse reportResponse = MetricCsvFixture.MOCK_METRIC_CSV_DATA_WITH_ONE_PIPELINE(); csvFileGenerator.convertMetricDataToCSV(reportResponse, mockTimeStamp); - InputStreamResource inputStreamResource = csvFileGenerator.getDataFromCSV(ReportType.METRIC, - Long.parseLong(mockTimeStamp)); + InputStreamResource inputStreamResource = csvFileGenerator.getDataFromCSV(ReportType.METRIC, mockTimeStamp); InputStream csvDataInputStream = inputStreamResource.getInputStream(); String metricCsvData = new BufferedReader(new InputStreamReader(csvDataInputStream)).lines() .collect(Collectors.joining("\n")); @@ -566,4 +623,12 @@ void shouldThrowGenerateReportExceptionWhenGenerateMetricsCsvAndCsvTimeStampInva () -> csvFileGenerator.convertMetricDataToCSV(reportResponse, "../")); } + @Test + void shouldThrowIllegalArgumentExceptionWhenFilePathIsError() { + String fileName = "./app/output/docs/metric-20240310-20240409-127272861371"; + File file = new File(fileName); + + assertThrows(IllegalArgumentException.class, () -> CSVFileGenerator.readStringFromCsvFile(file)); + } + } diff --git a/backend/src/test/java/heartbeat/service/report/GenerateReporterServiceTest.java b/backend/src/test/java/heartbeat/service/report/GenerateReporterServiceTest.java index 07fec71249..d3f3083cf6 100644 --- a/backend/src/test/java/heartbeat/service/report/GenerateReporterServiceTest.java +++ b/backend/src/test/java/heartbeat/service/report/GenerateReporterServiceTest.java @@ -144,6 +144,10 @@ class GenerateReporterServiceTest { @Captor ArgumentCaptor exceptionCaptor; + public static final String START_TIME = "20240310"; + + public static final String END_TIME = "20240409"; + @AfterEach void afterEach() { new File(APP_OUTPUT_CSV_PATH).delete(); @@ -169,11 +173,13 @@ void shouldSaveReportResponseWithReworkInfoWhenReworkInfoTimesIsNotEmpty() { ReworkTimesSetting.builder().reworkState("In Dev").excludedStates(List.of()).build()) .build()) .csvTimeStamp(TIMESTAMP) + .startTime("1710000000000") + .endTime("1712678399999") .build(); when(asyncMetricsDataHandler.getMetricsDataCompleted(any())) .thenReturn(MetricsDataCompleted.builder().build()); doAnswer(invocation -> null).when(asyncMetricsDataHandler) - .updateMetricsDataCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp()), + .updateMetricsDataCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp()), MetricType.BOARD, true); when(kanbanService.fetchDataFromKanban(request)).thenReturn(FetchedData.CardCollectionInfo.builder() .realDoneCardCollection(CardCollection.builder().build()) @@ -189,10 +195,10 @@ void shouldSaveReportResponseWithReworkInfoWhenReworkInfoTimesIsNotEmpty() { generateReporterService.generateBoardReport(request); - verify(asyncExceptionHandler).remove(request.getBoardReportId()); + verify(asyncExceptionHandler).remove(request.getBoardReportFileId()); verify(kanbanService).fetchDataFromKanban(request); verify(workDay).changeConsiderHolidayMode(false); - verify(asyncReportRequestHandler).putReport(eq(request.getBoardReportId()), + verify(asyncReportRequestHandler).putReport(eq(request.getBoardReportFileId()), responseArgumentCaptor.capture()); ReportResponse response = responseArgumentCaptor.getValue(); assertEquals(2, response.getRework().getFromTesting()); @@ -212,6 +218,8 @@ void shouldSaveReportResponseWithReworkInfoWhenReworkSettingIsNullAndMetricsHasR .buildKiteSetting(BuildKiteSetting.builder().build()) .jiraBoardSetting(JiraBoardSetting.builder().build()) .csvTimeStamp(TIMESTAMP) + .startTime("1710000000000") + .endTime("1712678399999") .build(); when(asyncMetricsDataHandler.getMetricsDataCompleted(any())) .thenReturn(MetricsDataCompleted.builder().build()); @@ -223,10 +231,10 @@ void shouldSaveReportResponseWithReworkInfoWhenReworkSettingIsNullAndMetricsHasR generateReporterService.generateBoardReport(request); - verify(asyncExceptionHandler).remove(request.getBoardReportId()); + verify(asyncExceptionHandler).remove(request.getBoardReportFileId()); verify(kanbanService).fetchDataFromKanban(request); verify(workDay).changeConsiderHolidayMode(false); - verify(asyncReportRequestHandler).putReport(eq(request.getBoardReportId()), + verify(asyncReportRequestHandler).putReport(eq(request.getBoardReportFileId()), responseArgumentCaptor.capture()); ReportResponse response = responseArgumentCaptor.getValue(); assertNull(response.getRework()); @@ -239,18 +247,20 @@ void shouldSaveReportResponseWithoutMetricDataAndUpdateMetricCompletedWhenMetric .metrics(List.of()) .buildKiteSetting(BuildKiteSetting.builder().build()) .csvTimeStamp(TIMESTAMP) + .startTime("1710000000000") + .endTime("1712678399999") .build(); when(asyncMetricsDataHandler.getMetricsDataCompleted(any())) .thenReturn(MetricsDataCompleted.builder().build()); doAnswer(invocation -> null).when(asyncMetricsDataHandler) - .updateMetricsDataCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp()), + .updateMetricsDataCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp()), MetricType.BOARD, true); generateReporterService.generateBoardReport(request); verify(kanbanService, never()).fetchDataFromKanban(eq(request)); verify(pipelineService, never()).fetchGitHubData(any()); verify(workDay).changeConsiderHolidayMode(false); - verify(asyncReportRequestHandler).putReport(eq(request.getBoardReportId()), + verify(asyncReportRequestHandler).putReport(eq(request.getBoardReportFileId()), responseArgumentCaptor.capture()); ReportResponse response = responseArgumentCaptor.getValue(); assertEquals(1800000L, response.getExportValidityTime()); @@ -266,22 +276,24 @@ void shouldThrowErrorWhenJiraBoardSettingIsNull() { .metrics(List.of("velocity")) .buildKiteSetting(BuildKiteSetting.builder().build()) .csvTimeStamp(TIMESTAMP) + .startTime("1710000000000") + .endTime("1712678399999") .build(); when(asyncMetricsDataHandler.getMetricsDataCompleted(any())) .thenReturn(MetricsDataCompleted.builder().build()); doAnswer(invocation -> null).when(asyncMetricsDataHandler) - .updateMetricsDataCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp()), + .updateMetricsDataCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp()), MetricType.BOARD, true); generateReporterService.generateBoardReport(request); - verify(asyncExceptionHandler).put(eq(request.getBoardReportId()), exceptionCaptor.capture()); + verify(asyncExceptionHandler).put(eq(request.getBoardReportFileId()), exceptionCaptor.capture()); assertEquals("Failed to fetch Jira info due to Jira board setting is null.", exceptionCaptor.getValue().getMessage()); assertEquals(400, exceptionCaptor.getValue().getStatus()); verify(kanbanService, never()).fetchDataFromKanban(eq(request)); verify(workDay).changeConsiderHolidayMode(false); - verify(asyncReportRequestHandler, never()).putReport(eq(request.getBoardReportId()), any()); + verify(asyncReportRequestHandler, never()).putReport(eq(request.getBoardReportFileId()), any()); } @Test @@ -292,11 +304,13 @@ void shouldSaveReportResponseWithVelocityAndUpdateMetricCompletedWhenVelocityMet .buildKiteSetting(BuildKiteSetting.builder().build()) .jiraBoardSetting(JiraBoardSetting.builder().build()) .csvTimeStamp(TIMESTAMP) + .startTime("1710000000000") + .endTime("1712678399999") .build(); when(asyncMetricsDataHandler.getMetricsDataCompleted(any())) .thenReturn(MetricsDataCompleted.builder().build()); doAnswer(invocation -> null).when(asyncMetricsDataHandler) - .updateMetricsDataCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp()), + .updateMetricsDataCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp()), MetricType.BOARD, true); when(velocityCalculator.calculateVelocity(any())) .thenReturn(Velocity.builder().velocityForSP(10).velocityForCards(20).build()); @@ -306,11 +320,11 @@ void shouldSaveReportResponseWithVelocityAndUpdateMetricCompletedWhenVelocityMet generateReporterService.generateBoardReport(request); - verify(asyncExceptionHandler).remove(request.getBoardReportId()); + verify(asyncExceptionHandler).remove(request.getBoardReportFileId()); verify(pipelineService, never()).fetchGitHubData(any()); verify(kanbanService).fetchDataFromKanban(eq(request)); verify(workDay).changeConsiderHolidayMode(false); - verify(asyncReportRequestHandler).putReport(eq(request.getBoardReportId()), + verify(asyncReportRequestHandler).putReport(eq(request.getBoardReportFileId()), responseArgumentCaptor.capture()); ReportResponse response = responseArgumentCaptor.getValue(); assertEquals(1800000L, response.getExportValidityTime()); @@ -326,6 +340,8 @@ void shouldSaveReportResponseWithCycleTimeAndUpdateMetricCompletedWhenCycleTimeM .buildKiteSetting(BuildKiteSetting.builder().build()) .jiraBoardSetting(JiraBoardSetting.builder().build()) .csvTimeStamp(TIMESTAMP) + .startTime("1710000000000") + .endTime("1712678399999") .build(); when(asyncMetricsDataHandler.getMetricsDataCompleted(any())) .thenReturn(MetricsDataCompleted.builder().boardMetricsCompleted(true).build()); @@ -345,7 +361,7 @@ void shouldSaveReportResponseWithCycleTimeAndUpdateMetricCompletedWhenCycleTimeM verify(pipelineService, never()).fetchGitHubData(any()); verify(kanbanService).fetchDataFromKanban(eq(request)); verify(workDay).changeConsiderHolidayMode(false); - verify(asyncReportRequestHandler).putReport(eq(request.getBoardReportId()), + verify(asyncReportRequestHandler).putReport(eq(request.getBoardReportFileId()), responseArgumentCaptor.capture()); ReportResponse response = responseArgumentCaptor.getValue(); assertEquals(1800000L, response.getExportValidityTime()); @@ -362,11 +378,13 @@ void shouldSaveReportResponseWithClassificationAndUpdateMetricCompletedWhenClass .buildKiteSetting(BuildKiteSetting.builder().build()) .jiraBoardSetting(JiraBoardSetting.builder().build()) .csvTimeStamp(TIMESTAMP) + .startTime("1710000000000") + .endTime("1712678399999") .build(); when(asyncMetricsDataHandler.getMetricsDataCompleted(any())) .thenReturn(MetricsDataCompleted.builder().build()); doAnswer(invocation -> null).when(asyncMetricsDataHandler) - .updateMetricsDataCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp()), + .updateMetricsDataCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp()), MetricType.BOARD, true); List classifications = List.of(Classification.builder().build()); when(classificationCalculator.calculate(any(), any())).thenReturn(classifications); @@ -379,7 +397,7 @@ void shouldSaveReportResponseWithClassificationAndUpdateMetricCompletedWhenClass verify(pipelineService, never()).fetchGitHubData(any()); verify(kanbanService).fetchDataFromKanban(eq(request)); verify(workDay).changeConsiderHolidayMode(false); - verify(asyncReportRequestHandler).putReport(eq(request.getBoardReportId()), + verify(asyncReportRequestHandler).putReport(eq(request.getBoardReportFileId()), responseArgumentCaptor.capture()); ReportResponse response = responseArgumentCaptor.getValue(); assertEquals(1800000L, response.getExportValidityTime()); @@ -394,19 +412,21 @@ void shouldUpdateMetricCompletedWhenExceptionStart4() { .buildKiteSetting(BuildKiteSetting.builder().build()) .jiraBoardSetting(JiraBoardSetting.builder().build()) .csvTimeStamp(TIMESTAMP) + .startTime("1710000000000") + .endTime("1712678399999") .build(); when(kanbanService.fetchDataFromKanban(request)).thenThrow(new NotFoundException("")); when(asyncMetricsDataHandler.getMetricsDataCompleted(any())) .thenReturn(MetricsDataCompleted.builder().build()); doAnswer(invocation -> null).when(asyncMetricsDataHandler) - .updateMetricsDataCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp()), + .updateMetricsDataCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp()), MetricType.BOARD, true); generateReporterService.generateBoardReport(request); Awaitility.await() .atMost(5, TimeUnit.SECONDS) - .untilAsserted(() -> verify(asyncExceptionHandler).put(eq(request.getBoardReportId()), any())); + .untilAsserted(() -> verify(asyncExceptionHandler).put(eq(request.getBoardReportFileId()), any())); } @Test @@ -420,6 +440,8 @@ void shouldAsyncToGenerateCsvAndGenerateReportWhenFetchRight() { ReworkTimesSetting.builder().reworkState("To do").excludedStates(List.of("block")).build()) .build()) .csvTimeStamp(TIMESTAMP) + .startTime("1710000000000") + .endTime("1712678399999") .build(); when(kanbanService.fetchDataFromKanban(request)).thenReturn(FetchedData.CardCollectionInfo.builder() .realDoneCardCollection(CardCollection.builder().reworkCardNumber(2).build()) @@ -430,7 +452,7 @@ void shouldAsyncToGenerateCsvAndGenerateReportWhenFetchRight() { when(reworkCalculator.calculateRework(any(), any())) .thenReturn(Rework.builder().totalReworkCards(2).build()); doAnswer(invocation -> null).when(asyncMetricsDataHandler) - .updateMetricsDataCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp()), + .updateMetricsDataCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp()), MetricType.BOARD, true); generateReporterService.generateBoardReport(request); @@ -453,24 +475,25 @@ void shouldGenerateCsvFile() { .metrics(List.of()) .buildKiteSetting(BuildKiteSetting.builder().build()) .csvTimeStamp(TIMESTAMP) + .startTime("1710000000000") + .endTime("1712678399999") .build(); when(asyncMetricsDataHandler.getMetricsDataCompleted(any())) .thenReturn(MetricsDataCompleted.builder().doraMetricsCompleted(false).build()); doAnswer(invocation -> null).when(asyncMetricsDataHandler) .updateMetricsDataCompletedInHandler(TIMESTAMP, MetricType.DORA, true); List pipelineCSVInfos = List.of(); - when(pipelineService.generateCSVForPipelineWithCodebase(any(), any(), any(), any(), any())) - .thenReturn(pipelineCSVInfos); + when(pipelineService.generateCSVForPipeline(any(), any(), any(), any())).thenReturn(pipelineCSVInfos); generateReporterService.generateDoraReport(request); - verify(asyncExceptionHandler).remove(request.getPipelineReportId()); - verify(asyncExceptionHandler).remove(request.getSourceControlReportId()); + verify(asyncExceptionHandler).remove(request.getPipelineReportFileId()); + verify(asyncExceptionHandler).remove(request.getSourceControlReportFileId()); verify(kanbanService, never()).fetchDataFromKanban(eq(request)); Awaitility.await() .atMost(5, TimeUnit.SECONDS) .untilAsserted(() -> verify(csvFileGenerator).convertPipelineDataToCSV(pipelineCSVInfos, - request.getCsvTimeStamp())); + request.getTimeRangeAndTimeStamp())); } @Test @@ -480,14 +503,15 @@ void shouldThrowErrorWhenCodeSettingIsNullButSourceControlMetricsIsNotEmpty() { .metrics(List.of("lead time for changes")) .buildKiteSetting(BuildKiteSetting.builder().build()) .csvTimeStamp(TIMESTAMP) + .startTime("1710000000000") + .endTime("1712678399999") .build(); when(asyncMetricsDataHandler.getMetricsDataCompleted(any())) .thenReturn(MetricsDataCompleted.builder().doraMetricsCompleted(true).build()); doAnswer(invocation -> null).when(asyncMetricsDataHandler) .updateMetricsDataCompletedInHandler(TIMESTAMP, MetricType.DORA, true); List pipelineCSVInfos = List.of(); - when(pipelineService.generateCSVForPipelineWithCodebase(any(), any(), any(), any(), any())) - .thenReturn(pipelineCSVInfos); + when(pipelineService.generateCSVForPipeline(any(), any(), any(), any())).thenReturn(pipelineCSVInfos); try { generateReporterService.generateDoraReport(request); @@ -497,11 +521,11 @@ void shouldThrowErrorWhenCodeSettingIsNullButSourceControlMetricsIsNotEmpty() { assertEquals("Failed to fetch Github info due to code base setting is null.", e.getMessage()); } - verify(asyncExceptionHandler).remove(request.getPipelineReportId()); - verify(asyncExceptionHandler).remove(request.getSourceControlReportId()); + verify(asyncExceptionHandler).remove(request.getPipelineReportFileId()); + verify(asyncExceptionHandler).remove(request.getSourceControlReportFileId()); verify(kanbanService, never()).fetchDataFromKanban(eq(request)); verify(csvFileGenerator, never()).convertPipelineDataToCSV(eq(pipelineCSVInfos), - eq(request.getCsvTimeStamp())); + eq(request.getTimeRangeAndTimeStamp())); } @Test @@ -509,14 +533,15 @@ void shouldThrowErrorWhenBuildKiteSettingIsNullButPipelineMetricsIsNotEmpty() { GenerateReportRequest request = GenerateReportRequest.builder() .metrics(List.of("deployment frequency")) .csvTimeStamp(TIMESTAMP) + .startTime("1710000000000") + .endTime("1712678399999") .build(); when(asyncMetricsDataHandler.getMetricsDataCompleted(any())) .thenReturn(MetricsDataCompleted.builder().doraMetricsCompleted(true).build()); doAnswer(invocation -> null).when(asyncMetricsDataHandler) .updateMetricsDataCompletedInHandler(TIMESTAMP, MetricType.DORA, true); List pipelineCSVInfos = List.of(); - when(pipelineService.generateCSVForPipelineWithCodebase(any(), any(), any(), any(), any())) - .thenReturn(pipelineCSVInfos); + when(pipelineService.generateCSVForPipeline(any(), any(), any(), any())).thenReturn(pipelineCSVInfos); try { generateReporterService.generateDoraReport(request); @@ -526,11 +551,11 @@ void shouldThrowErrorWhenBuildKiteSettingIsNullButPipelineMetricsIsNotEmpty() { assertEquals(400, e.getStatus()); } - verify(asyncExceptionHandler).remove(request.getPipelineReportId()); - verify(asyncExceptionHandler).remove(request.getSourceControlReportId()); + verify(asyncExceptionHandler).remove(request.getPipelineReportFileId()); + verify(asyncExceptionHandler).remove(request.getSourceControlReportFileId()); verify(kanbanService, never()).fetchDataFromKanban(eq(request)); verify(csvFileGenerator, never()).convertPipelineDataToCSV(eq(pipelineCSVInfos), - eq(request.getCsvTimeStamp())); + eq(request.getTimeRangeAndTimeStamp())); } @Test @@ -548,8 +573,7 @@ void shouldGenerateCsvWithPipelineReportWhenPipeLineMetricIsNotEmpty() { doAnswer(invocation -> null).when(asyncMetricsDataHandler) .updateMetricsDataCompletedInHandler(TIMESTAMP, MetricType.DORA, true); List pipelineCSVInfos = List.of(); - when(pipelineService.generateCSVForPipelineWithCodebase(any(), any(), any(), any(), any())) - .thenReturn(pipelineCSVInfos); + when(pipelineService.generateCSVForPipeline(any(), any(), any(), any())).thenReturn(pipelineCSVInfos); when(pipelineService.fetchBuildKiteInfo(request)) .thenReturn(FetchedData.BuildKiteData.builder().buildInfosList(List.of()).build()); DeploymentFrequency fakeDeploymentFrequency = DeploymentFrequency.builder().build(); @@ -562,7 +586,7 @@ void shouldGenerateCsvWithPipelineReportWhenPipeLineMetricIsNotEmpty() { generateReporterService.generateDoraReport(request); verify(workDay).changeConsiderHolidayMode(false); - verify(asyncReportRequestHandler).putReport(eq(request.getPipelineReportId()), + verify(asyncReportRequestHandler).putReport(eq(request.getPipelineReportFileId()), responseArgumentCaptor.capture()); ReportResponse response = responseArgumentCaptor.getValue(); @@ -576,7 +600,7 @@ void shouldGenerateCsvWithPipelineReportWhenPipeLineMetricIsNotEmpty() { Awaitility.await() .atMost(5, TimeUnit.SECONDS) .untilAsserted(() -> verify(csvFileGenerator).convertPipelineDataToCSV(pipelineCSVInfos, - request.getCsvTimeStamp())); + request.getTimeRangeAndTimeStamp())); } @Test @@ -594,15 +618,14 @@ void shouldUpdateMetricCompletedWhenGenerateCsvWithPipelineReportFailed() { doAnswer(invocation -> null).when(asyncMetricsDataHandler) .updateMetricsDataCompletedInHandler(TIMESTAMP, MetricType.DORA, false); List pipelineCSVInfos = List.of(); - when(pipelineService.generateCSVForPipelineWithCodebase(any(), any(), any(), any(), any())) - .thenReturn(pipelineCSVInfos); + when(pipelineService.generateCSVForPipeline(any(), any(), any(), any())).thenReturn(pipelineCSVInfos); when(pipelineService.fetchBuildKiteInfo(request)) .thenReturn(FetchedData.BuildKiteData.builder().buildInfosList(List.of()).build()); when(devChangeFailureRate.calculate(any())).thenThrow(new NotFoundException("")); generateReporterService.generateDoraReport(request); - verify(asyncExceptionHandler).put(eq(request.getPipelineReportId()), any()); + verify(asyncExceptionHandler).put(eq(request.getPipelineReportFileId()), any()); verify(asyncMetricsDataHandler, times(1)).updateMetricsDataCompletedInHandler(any(), any(), anyBoolean()); } @@ -623,8 +646,7 @@ void shouldGenerateCsvWithSourceControlReportWhenSourceControlMetricIsNotEmpty() doAnswer(invocation -> null).when(asyncMetricsDataHandler) .updateMetricsDataCompletedInHandler(TIMESTAMP, MetricType.DORA, true); List pipelineCSVInfos = List.of(); - when(pipelineService.generateCSVForPipelineWithCodebase(any(), any(), any(), any(), any())) - .thenReturn(pipelineCSVInfos); + when(pipelineService.generateCSVForPipeline(any(), any(), any(), any())).thenReturn(pipelineCSVInfos); when(pipelineService.fetchGitHubData(request)) .thenReturn(FetchedData.BuildKiteData.builder().buildInfosList(List.of()).build()); LeadTimeForChanges fakeLeadTimeForChange = LeadTimeForChanges.builder().build(); @@ -633,7 +655,7 @@ void shouldGenerateCsvWithSourceControlReportWhenSourceControlMetricIsNotEmpty() generateReporterService.generateDoraReport(request); verify(workDay).changeConsiderHolidayMode(false); - verify(asyncReportRequestHandler).putReport(eq(request.getSourceControlReportId()), + verify(asyncReportRequestHandler).putReport(eq(request.getSourceControlReportFileId()), responseArgumentCaptor.capture()); ReportResponse response = responseArgumentCaptor.getValue(); assertEquals(1800000L, response.getExportValidityTime()); @@ -641,7 +663,7 @@ void shouldGenerateCsvWithSourceControlReportWhenSourceControlMetricIsNotEmpty() Awaitility.await() .atMost(5, TimeUnit.SECONDS) .untilAsserted(() -> verify(csvFileGenerator).convertPipelineDataToCSV(pipelineCSVInfos, - request.getCsvTimeStamp())); + request.getTimeRangeAndTimeStamp())); } @@ -662,8 +684,7 @@ void shouldGenerateCsvWithCachedDataWhenBuildKiteDataAlreadyExisted() { doAnswer(invocation -> null).when(asyncMetricsDataHandler) .updateMetricsDataCompletedInHandler(TIMESTAMP, MetricType.DORA, true); List pipelineCSVInfos = List.of(); - when(pipelineService.generateCSVForPipelineWithCodebase(any(), any(), any(), any(), any())) - .thenReturn(pipelineCSVInfos); + when(pipelineService.generateCSVForPipeline(any(), any(), any(), any())).thenReturn(pipelineCSVInfos); when(pipelineService.fetchGitHubData(any())) .thenReturn(FetchedData.BuildKiteData.builder().buildInfosList(List.of()).build()); when(pipelineService.fetchBuildKiteInfo(any())) @@ -674,7 +695,7 @@ void shouldGenerateCsvWithCachedDataWhenBuildKiteDataAlreadyExisted() { generateReporterService.generateDoraReport(request); verify(workDay, times(2)).changeConsiderHolidayMode(false); - verify(asyncReportRequestHandler).putReport(eq(request.getSourceControlReportId()), + verify(asyncReportRequestHandler).putReport(eq(request.getSourceControlReportFileId()), responseArgumentCaptor.capture()); ReportResponse response = responseArgumentCaptor.getValue(); assertEquals(1800000L, response.getExportValidityTime()); @@ -682,7 +703,7 @@ void shouldGenerateCsvWithCachedDataWhenBuildKiteDataAlreadyExisted() { Awaitility.await() .atMost(5, TimeUnit.SECONDS) .untilAsserted(() -> verify(csvFileGenerator).convertPipelineDataToCSV(pipelineCSVInfos, - request.getCsvTimeStamp())); + request.getTimeRangeAndTimeStamp())); } @Test @@ -701,15 +722,14 @@ void shouldUpdateMetricCompletedWhenGenerateCsvWithSourceControlReportFailed() { doAnswer(invocation -> null).when(asyncMetricsDataHandler) .updateMetricsDataCompletedInHandler(TIMESTAMP, MetricType.DORA, true); List pipelineCSVInfos = List.of(); - when(pipelineService.generateCSVForPipelineWithCodebase(any(), any(), any(), any(), any())) - .thenReturn(pipelineCSVInfos); + when(pipelineService.generateCSVForPipeline(any(), any(), any(), any())).thenReturn(pipelineCSVInfos); when(pipelineService.fetchGitHubData(request)).thenReturn( FetchedData.BuildKiteData.builder().pipelineLeadTimes(List.of()).buildInfosList(List.of()).build()); doThrow(new NotFoundException("")).when(leadTimeForChangesCalculator).calculate(any()); generateReporterService.generateDoraReport(request); - verify(asyncExceptionHandler).put(eq(request.getSourceControlReportId()), any()); + verify(asyncExceptionHandler).put(eq(request.getSourceControlReportFileId()), any()); verify(asyncMetricsDataHandler, times(1)).updateMetricsDataCompletedInHandler(any(), any(), anyBoolean()); } @@ -756,7 +776,7 @@ class GetComposedReportResponse { @BeforeEach void setUp() { reportId = String.valueOf(System.currentTimeMillis() - EXPORT_CSV_VALIDITY_TIME + 200); - dataCompletedId = IdUtil.getDataCompletedPrefix(reportId); + dataCompletedId = IdUtil.getDataCompletedPrefix(START_TIME + "-" + END_TIME + "-" + reportId); } @Test @@ -766,7 +786,7 @@ void shouldGetDataFromCache() { .thenReturn(MetricsDataCompleted.builder().boardMetricsCompleted(false).doraMetricsCompleted(true).overallMetricCompleted(false).build()); when(asyncExceptionHandler.get(any())).thenReturn(null); - ReportResponse res = generateReporterService.getComposedReportResponse(reportId); + ReportResponse res = generateReporterService.getComposedReportResponse(reportId, START_TIME, END_TIME); assertEquals(EXPORT_CSV_VALIDITY_TIME, res.getExportValidityTime()); assertFalse(res.getBoardMetricsCompleted()); @@ -782,7 +802,7 @@ void shouldReturnErrorDataWhenExceptionIs404Or403Or401() { .thenReturn(MetricsDataCompleted.builder().boardMetricsCompleted(false).doraMetricsCompleted(true).overallMetricCompleted(false).build()); when(asyncExceptionHandler.get(any())).thenReturn(new AsyncExceptionDTO(new NotFoundException("error"))); - ReportResponse res = generateReporterService.getComposedReportResponse(reportId); + ReportResponse res = generateReporterService.getComposedReportResponse(reportId, START_TIME, END_TIME); assertEquals(EXPORT_CSV_VALIDITY_TIME, res.getExportValidityTime()); assertFalse(res.getAllMetricsCompleted()); @@ -797,7 +817,7 @@ void shouldThrowGenerateReportExceptionWhenErrorIs500() { when(asyncExceptionHandler.get(any())).thenReturn(new AsyncExceptionDTO(new GenerateReportException("errorMessage"))); try { - generateReporterService.getComposedReportResponse(reportId); + generateReporterService.getComposedReportResponse(reportId, START_TIME, END_TIME); fail(); } catch (BaseException e) { @@ -814,7 +834,7 @@ void shouldThrowServiceUnavailableExceptionWhenErrorIs503() { when(asyncExceptionHandler.get(any())).thenReturn(new AsyncExceptionDTO(new ServiceUnavailableException("errorMessage"))); try { - generateReporterService.getComposedReportResponse(reportId); + generateReporterService.getComposedReportResponse(reportId, START_TIME, END_TIME); fail(); } catch (BaseException e) { @@ -830,7 +850,7 @@ void shouldThrowRequestFailedExceptionWhenErrorIsDefault() { .thenReturn(MetricsDataCompleted.builder().boardMetricsCompleted(false).doraMetricsCompleted(true).overallMetricCompleted(false).build()); when(asyncExceptionHandler.get(any())).thenReturn(new AsyncExceptionDTO(new BadRequestException("error"))); try { - generateReporterService.getComposedReportResponse(reportId); + generateReporterService.getComposedReportResponse(reportId, START_TIME, END_TIME); fail(); } catch (BaseException e) { @@ -846,7 +866,7 @@ void shouldGetDataWhenBoardMetricsCompletedIsFalseDoraMetricsCompletedIsNull() { .thenReturn(MetricsDataCompleted.builder().boardMetricsCompleted(false).overallMetricCompleted(false).build()); when(asyncExceptionHandler.get(any())).thenReturn(null); - ReportResponse res = generateReporterService.getComposedReportResponse(reportId); + ReportResponse res = generateReporterService.getComposedReportResponse(reportId, START_TIME, END_TIME); assertEquals(EXPORT_CSV_VALIDITY_TIME, res.getExportValidityTime()); assertFalse(res.getBoardMetricsCompleted()); @@ -861,7 +881,7 @@ void shouldGetDataWhenBoardMetricsCompletedIsNullDoraMetricsCompletedIsFalse() { .thenReturn(MetricsDataCompleted.builder().doraMetricsCompleted(false).overallMetricCompleted(false).build()); when(asyncExceptionHandler.get(any())).thenReturn(null); - ReportResponse res = generateReporterService.getComposedReportResponse(reportId); + ReportResponse res = generateReporterService.getComposedReportResponse(reportId, START_TIME, END_TIME); assertEquals(EXPORT_CSV_VALIDITY_TIME, res.getExportValidityTime()); assertNull(res.getBoardMetricsCompleted()); @@ -876,7 +896,7 @@ void shouldGetDataWhenBoardMetricsCompletedIsTrueDoraMetricsCompletedIsTrueOvera .thenReturn(MetricsDataCompleted.builder().boardMetricsCompleted(true).doraMetricsCompleted(true).overallMetricCompleted(true).build()); when(asyncExceptionHandler.get(any())).thenReturn(null); - ReportResponse res = generateReporterService.getComposedReportResponse(reportId); + ReportResponse res = generateReporterService.getComposedReportResponse(reportId, START_TIME, END_TIME); assertEquals(EXPORT_CSV_VALIDITY_TIME, res.getExportValidityTime()); assertTrue(res.getBoardMetricsCompleted()); @@ -891,7 +911,7 @@ void shouldGetDataWhenBoardMetricsCompletedIsNullDoraMetricsCompletedIsNullOvera .thenReturn(MetricsDataCompleted.builder().overallMetricCompleted(true).build()); when(asyncExceptionHandler.get(any())).thenReturn(null); - ReportResponse res = generateReporterService.getComposedReportResponse(reportId); + ReportResponse res = generateReporterService.getComposedReportResponse(reportId, START_TIME, END_TIME); assertEquals(EXPORT_CSV_VALIDITY_TIME, res.getExportValidityTime()); assertNull(res.getBoardMetricsCompleted()); @@ -906,7 +926,7 @@ void shouldGetDataWhenBoardMetricsCompletedIsNullDoraMetricsCompletedIsNullOvera .thenReturn(MetricsDataCompleted.builder().overallMetricCompleted(false).build()); when(asyncExceptionHandler.get(any())).thenReturn(null); - ReportResponse res = generateReporterService.getComposedReportResponse(reportId); + ReportResponse res = generateReporterService.getComposedReportResponse(reportId, START_TIME,END_TIME); assertEquals(EXPORT_CSV_VALIDITY_TIME, res.getExportValidityTime()); assertNull(res.getBoardMetricsCompleted()); @@ -937,7 +957,7 @@ void shouldNotDeleteOldCsvWhenExportCsvWithoutOldCsvInsideThirtyMinutes() throws @Test void shouldDeleteFailWhenDeleteCSV() { File mockFile = mock(File.class); - when(mockFile.getName()).thenReturn("file1-1683734399999.CSV"); + when(mockFile.getName()).thenReturn("file1-20240417-20240418-1683734399999.CSV"); when(mockFile.delete()).thenReturn(false); File[] mockFiles = new File[] { mockFile }; File directory = mock(File.class); @@ -965,7 +985,7 @@ void shouldThrowExceptionWhenDeleteCSV() { @Test void shouldDeleteFailWhenDeleteFile() { File mockFile = mock(File.class); - when(mockFile.getName()).thenReturn("board-1683734399999"); + when(mockFile.getName()).thenReturn("board-20240417-20240418-1683734399999"); when(mockFile.delete()).thenReturn(false); when(mockFile.exists()).thenReturn(true); File[] mockFiles = new File[] { mockFile }; @@ -980,7 +1000,7 @@ void shouldDeleteFailWhenDeleteFile() { @Test void shouldDeleteTempFailWhenDeleteFile() { File mockFile = mock(File.class); - when(mockFile.getName()).thenReturn("board-1683734399999.tmp"); + when(mockFile.getName()).thenReturn("board-20240417-20240418-1683734399999.tmp"); when(mockFile.delete()).thenReturn(true); when(mockFile.exists()).thenReturn(false); File[] mockFiles = new File[] { mockFile }; diff --git a/backend/src/test/java/heartbeat/service/report/KanbanCsvServiceTest.java b/backend/src/test/java/heartbeat/service/report/KanbanCsvServiceTest.java index a2bd26c7db..3f7449efba 100644 --- a/backend/src/test/java/heartbeat/service/report/KanbanCsvServiceTest.java +++ b/backend/src/test/java/heartbeat/service/report/KanbanCsvServiceTest.java @@ -38,6 +38,7 @@ import static heartbeat.service.jira.JiraBoardConfigDTOFixture.MOCK_JIRA_BOARD_COLUMN_SETTING_LIST; import static heartbeat.service.report.BoardCsvFixture.MOCK_JIRA_CARD; import static heartbeat.service.report.BoardCsvFixture.MOCK_REWORK_TIMES_INFO_LIST; +import static heartbeat.tools.TimeUtils.mockTimeStamp; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -75,6 +76,12 @@ class KanbanCsvServiceTest { @Captor private ArgumentCaptor> csvNewFieldsCaptor; + public static final String CSV_TIME_STAMP = String.valueOf(mockTimeStamp(2024, 7, 3, 17, 45, 50)); + + public static final String START_TIME = String.valueOf(mockTimeStamp(2024, 3, 10, 0, 0, 0)); + + public static final String END_TIME = String.valueOf(mockTimeStamp(2024, 4, 9, 0, 0, 0)); + @Test void shouldSaveCsvWithoutNonDoneCardsWhenNonDoneCardIsNull() throws URISyntaxException { @@ -89,6 +96,9 @@ void shouldSaveCsvWithoutNonDoneCardsWhenNonDoneCardIsNull() throws URISyntaxExc .targetFields(List.of(TargetField.builder().name("Doing").build(), TargetField.builder().name("Done").build())) .build()) + .csvTimeStamp(CSV_TIME_STAMP) + .startTime(START_TIME) + .endTime(END_TIME) .build(), CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().build()); @@ -112,6 +122,9 @@ void shouldSaveCsvWithoutNonDoneCardsWhenNonDoneCardIsEmpty() throws URISyntaxEx .targetFields(List.of(TargetField.builder().name("Doing").build(), TargetField.builder().name("Done").build())) .build()) + .csvTimeStamp(CSV_TIME_STAMP) + .startTime(START_TIME) + .endTime(END_TIME) .build(), CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(Lists.list()).build()); @@ -172,6 +185,9 @@ void shouldSaveCsvWithOrderedNonDoneCardsByJiraColumnDescendingWhenNonDoneCardIs .targetFields(List.of(TargetField.builder().name("Doing").build(), TargetField.builder().name("Done").build())) .build()) + .csvTimeStamp(CSV_TIME_STAMP) + .startTime(START_TIME) + .endTime(END_TIME) .build(), CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); @@ -234,6 +250,9 @@ void shouldSaveCsvWithOrderedNonDoneCardsByJiraColumnDescendingWhenNonDoneCardIs .targetFields(List.of(TargetField.builder().name("Doing").build(), TargetField.builder().name("Done").build())) .build()) + .csvTimeStamp(CSV_TIME_STAMP) + .startTime(START_TIME) + .endTime(END_TIME) .build(), CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); @@ -278,6 +297,9 @@ void shouldSaveCsvWithOrderedDoneCardsByJiraColumnDescendingWhenDoneCardIsNotEmp .targetFields(List.of(TargetField.builder().name("Doing").build(), TargetField.builder().name("Done").build())) .build()) + .csvTimeStamp(CSV_TIME_STAMP) + .startTime(START_TIME) + .endTime(END_TIME) .build(), CardCollection.builder().jiraCardDTOList(doneJiraCardDTOList).build(), CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build()); @@ -326,6 +348,9 @@ void shouldSaveCsvWithOrderedDoneCardsByJiraColumnDescendingWhenNonDoneCardIsNot .jiraBoardSetting(JiraBoardSetting.builder() .targetFields(List.of(TargetField.builder().name("Done").build())) .build()) + .csvTimeStamp(CSV_TIME_STAMP) + .startTime(START_TIME) + .endTime(END_TIME) .build(), CardCollection.builder().jiraCardDTOList(doneJiraCardDTOList).build(), CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build()); @@ -377,6 +402,9 @@ void shouldSaveCsvWithoutOrderedNonDoneCardsByJiraColumnWhenNonDoneCardIsNotEmpt .targetFields(List.of(TargetField.builder().name("Doing").build(), TargetField.builder().name("Done").build())) .build()) + .csvTimeStamp(CSV_TIME_STAMP) + .startTime(START_TIME) + .endTime(END_TIME) .build(), CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); @@ -426,6 +454,9 @@ void shouldSaveCsvWithOrderedNonDoneCardsByJiraColumnDescendingWhenNonDoneCardIs .targetFields(List.of(TargetField.builder().name("Doing").build(), TargetField.builder().name("Done").build())) .build()) + .csvTimeStamp(CSV_TIME_STAMP) + .startTime(START_TIME) + .endTime(END_TIME) .build(), CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); @@ -465,6 +496,9 @@ void shouldAddFixedFieldsWhenItIsNotInSettingsFields() throws URISyntaxException TargetField.builder().name("fake-target1").flag(true).key("key-target1").build(), TargetField.builder().name("fake-target2").flag(false).key("key-target2").build())) .build()) + .csvTimeStamp(CSV_TIME_STAMP) + .startTime(START_TIME) + .endTime(END_TIME) .build(), CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); @@ -506,6 +540,9 @@ void shouldAddFixedFieldsWhenItIsNotInSettingsFieldsAndCardHasOriginCycleTime() TargetField.builder().name("fake-target1").flag(true).key("key-target1").build(), TargetField.builder().name("fake-target2").flag(false).key("key-target2").build())) .build()) + .csvTimeStamp(CSV_TIME_STAMP) + .startTime(START_TIME) + .endTime(END_TIME) .build(), CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); @@ -548,6 +585,9 @@ void shouldAddFixedFieldsWhenItIsNotInSettingsFieldsAndCardHasOriginCycleTimeAnd TargetField.builder().name("fake-target1").flag(true).key("key-target1").build(), TargetField.builder().name("fake-target2").flag(false).key("key-target2").build())) .build()) + .csvTimeStamp(CSV_TIME_STAMP) + .startTime(START_TIME) + .endTime(END_TIME) .build(), CardCollection.builder().jiraCardDTOList(List.of(jiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); @@ -597,6 +637,9 @@ void shouldAddFixedFieldsWithCorrectValueFormatWhenCustomFieldValueInstanceOfLis TargetField.builder().name("fake-target6").flag(true).key("json-array").build(), TargetField.builder().name("fake-target7").flag(true).key("json-obj").build())) .build()) + .csvTimeStamp(CSV_TIME_STAMP) + .startTime(START_TIME) + .endTime(END_TIME) .build(), CardCollection.builder().jiraCardDTOList(List.of(doneJiraCardDTO)).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); @@ -666,7 +709,9 @@ void shouldAddReworkFieldsWhenGenerateSheetGivenReworkStateAndExcludedStatesAndC .boardColumns(MOCK_JIRA_BOARD_COLUMN_SETTING_LIST()) .treatFlagCardAsBlock(Boolean.TRUE) .build()) - .csvTimeStamp("2022-01-01 00:00:00") + .csvTimeStamp(CSV_TIME_STAMP) + .startTime(START_TIME) + .endTime(END_TIME) .build(), CardCollection.builder().jiraCardDTOList(jiraCardDTOS).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); @@ -737,7 +782,9 @@ void shouldAddReworkFieldsWhenGenerateSheetGivenReworkStateAndExcludedStatesAndN .boardColumns(MOCK_JIRA_BOARD_COLUMN_SETTING_LIST()) .treatFlagCardAsBlock(Boolean.FALSE) .build()) - .csvTimeStamp("2022-01-01 00:00:00") + .csvTimeStamp(CSV_TIME_STAMP) + .startTime(START_TIME) + .endTime(END_TIME) .build(), CardCollection.builder().jiraCardDTOList(jiraCardDTOS).build(), CardCollection.builder().jiraCardDTOList(NonDoneJiraCardDTOList).build()); diff --git a/backend/src/test/java/heartbeat/service/report/PipelineCsvFixture.java b/backend/src/test/java/heartbeat/service/report/PipelineCsvFixture.java index 32fbda18ac..35ed249efc 100644 --- a/backend/src/test/java/heartbeat/service/report/PipelineCsvFixture.java +++ b/backend/src/test/java/heartbeat/service/report/PipelineCsvFixture.java @@ -4,8 +4,6 @@ import heartbeat.client.dto.codebase.github.Commit; import heartbeat.client.dto.codebase.github.CommitInfo; import heartbeat.client.dto.codebase.github.Committer; -import heartbeat.client.dto.codebase.github.LeadTime; -import heartbeat.client.dto.codebase.github.PipelineLeadTime; import heartbeat.client.dto.pipeline.buildkite.BuildKiteBuildInfo; import heartbeat.client.dto.pipeline.buildkite.BuildKiteJob; import heartbeat.client.dto.pipeline.buildkite.DeployInfo; @@ -28,7 +26,6 @@ public static List MOCK_PIPELINE_CSV_DATA() { .pipelineCreateTime("2023-05-10T06:17:21.844Z") .state("passed") .number(880) - .creator(BuildKiteBuildInfo.Creator.builder().name("XXXX").email("XXX@test.com").build()) .jobs(List.of(BuildKiteJob.builder() .name(":rocket: Deploy prod") .state("passed") @@ -36,6 +33,7 @@ public static List MOCK_PIPELINE_CSV_DATA() { .finishedAt("2023-05-10T06:43:02.653Z") .build())) .branch("branch") + .author(BuildKiteBuildInfo.Author.builder().name("XXXX").build()) .build()) .commitInfo(CommitInfo.builder() .commit(Commit.builder() @@ -57,7 +55,9 @@ public static List MOCK_PIPELINE_CSV_DATA() { .prMergedTime("1683793037000") .prLeadTime("16837") .prCreatedTime("168369327000") + .jobStartTime("168369327000") .jobFinishTime("1684793037000") + .firstCommitTime("168369327000") .pipelineLeadTime("653037000") .build()) .deployInfo(DeployInfo.builder() @@ -88,6 +88,7 @@ public static List MOCK_PIPELINE_CSV_DATA_WITHOUT_CREATOR() { .finishedAt("2023-05-10T06:43:02.653Z") .build())) .branch("branch") + .author(BuildKiteBuildInfo.Author.builder().name("XXXX").build()) .build()) .commitInfo(CommitInfo.builder() .commit(Commit.builder() @@ -109,8 +110,10 @@ public static List MOCK_PIPELINE_CSV_DATA_WITHOUT_CREATOR() { .prMergedTime("1683793037000") .prLeadTime("16837") .prCreatedTime("168369327000") + .jobStartTime("1683793037000") .jobFinishTime("1684793037000") .pipelineLeadTime("653037000") + .firstCommitTime("168369327000") .build()) .deployInfo(DeployInfo.builder() .state("passed") @@ -161,7 +164,121 @@ public static List MOCK_PIPELINE_CSV_DATA_WITHOUT_CREATOR_NAME( .prMergedTime("1683793037000") .prLeadTime("16837") .prCreatedTime("168369327000") + .jobStartTime("168369327000") .jobFinishTime("1684793037000") + .firstCommitTime("168369327000") + .pipelineLeadTime("653037000") + .build()) + .deployInfo(DeployInfo.builder() + .state("passed") + .jobFinishTime("1684793037000") + .jobStartTime("168369327000") + .build()) + .build(); + return List.of(pipelineCsvInfo); + } + + public static List MOCK_PIPELINE_CSV_DATA_WITH_MESSAGE_IS_REVERT() { + PipelineCSVInfo pipelineCsvInfo = PipelineCSVInfo.builder() + .organizationName("Thoughtworks-Heartbeat") + .pipeLineName("Heartbeat") + .piplineStatus("passed") + .stepName(":rocket: Deploy prod") + .buildInfo(BuildKiteBuildInfo.builder() + .commit("713b31878c756c205a6c03eac5be3ac7c7e6a227") + .creator(BuildKiteBuildInfo.Creator.builder().name(null).email("XXX@test.com").build()) + .pipelineCreateTime("2023-05-10T06:17:21.844Z") + .state("passed") + .number(880) + .jobs(List.of(BuildKiteJob.builder() + .name(":rocket: Deploy prod") + .state("passed") + .startedAt("2023-05-10T06:42:47.498Z") + .finishedAt("2023-05-10T06:43:02.653Z") + .build())) + .branch("branch") + .author(BuildKiteBuildInfo.Author.builder().name("XXXX").build()) + .build()) + .commitInfo(CommitInfo.builder() + .commit(Commit.builder() + .author(Author.builder() + .name("XXXX") + .email("XXX@test.com") + .date("2023-05-10T06:43:02.653Z") + .build()) + .committer(Committer.builder() + .name("XXXX") + .email("XXX@test.com") + .date("2023-05-10T06:43:02.653Z") + .build()) + .message("Revert:xxxx") + .build()) + .build()) + .leadTimeInfo(LeadTimeInfo.builder() + .firstCommitTimeInPr("2023-05-08T07:18:18Z") + .totalTime("8379303") + .prMergedTime("1683793037000") + .prLeadTime("16837") + .prCreatedTime("168369327000") + .jobStartTime("168369327000") + .jobFinishTime("1684793037000") + .firstCommitTime("168369327000") + .pipelineLeadTime("653037000") + .isRevert(Boolean.TRUE) + .build()) + .deployInfo(DeployInfo.builder() + .state("passed") + .jobFinishTime("1684793037000") + .jobStartTime("168369327000") + .build()) + .build(); + return List.of(pipelineCsvInfo); + } + + public static List MOCK_PIPELINE_CSV_DATA_WITHOUT_Author_NAME() { + PipelineCSVInfo pipelineCsvInfo = PipelineCSVInfo.builder() + .organizationName("Thoughtworks-Heartbeat") + .pipeLineName("Heartbeat") + .piplineStatus("passed") + .stepName(":rocket: Deploy prod") + .buildInfo(BuildKiteBuildInfo.builder() + .commit("713b31878c756c205a6c03eac5be3ac7c7e6a227") + .creator(BuildKiteBuildInfo.Creator.builder().name("XXX").email("XXX@test.com").build()) + .pipelineCreateTime("2023-05-10T06:17:21.844Z") + .state("passed") + .number(880) + .jobs(List.of(BuildKiteJob.builder() + .name(":rocket: Deploy prod") + .state("passed") + .startedAt("2023-05-10T06:42:47.498Z") + .finishedAt("2023-05-10T06:43:02.653Z") + .build())) + .branch("branch") + .author(BuildKiteBuildInfo.Author.builder().build()) + .build()) + .commitInfo(CommitInfo.builder() + .commit(Commit.builder() + .author(Author.builder() + .name("XXXX") + .email("XXX@test.com") + .date("2023-05-10T06:43:02.653Z") + .build()) + .committer(Committer.builder() + .name("XXXX") + .email("XXX@test.com") + .date("2023-05-10T06:43:02.653Z") + .build()) + .build()) + .build()) + .leadTimeInfo(LeadTimeInfo.builder() + .firstCommitTimeInPr("2023-05-08T07:18:18Z") + .totalTime("8379303") + .prMergedTime("1683793037000") + .prLeadTime("16837") + .prCreatedTime("168369327000") + .jobStartTime("168369327000") + .jobFinishTime("1684793037000") + .firstCommitTime("168369327000") .pipelineLeadTime("653037000") .build()) .deployInfo(DeployInfo.builder() @@ -185,7 +302,6 @@ public static List MOCK_PIPELINE_CSV_DATA_WITH_PIPELINE_STATUS_ .pipelineCreateTime("2023-05-10T06:17:21.844Z") .state("canceled") .number(880) - .creator(BuildKiteBuildInfo.Creator.builder().name("XXXX").email("XXX@test.com").build()) .jobs(List.of(BuildKiteJob.builder() .name(":rocket: Deploy prod") .state("passed") @@ -193,6 +309,7 @@ public static List MOCK_PIPELINE_CSV_DATA_WITH_PIPELINE_STATUS_ .finishedAt("2023-05-10T06:43:02.653Z") .build())) .branch("branch") + .author(BuildKiteBuildInfo.Author.builder().name("XXXX").build()) .build()) .commitInfo(CommitInfo.builder() .commit(Commit.builder() @@ -214,7 +331,9 @@ public static List MOCK_PIPELINE_CSV_DATA_WITH_PIPELINE_STATUS_ .prMergedTime("1683793037000") .prLeadTime("16837") .prCreatedTime("168369327000") + .jobStartTime("168369327000") .jobFinishTime("1684793037000") + .firstCommitTime("168369327000") .pipelineLeadTime("653037000") .build()) .deployInfo(DeployInfo.builder() @@ -245,6 +364,7 @@ public static List MOCK_PIPELINE_CSV_DATA_WITH_NULL_COMMIT_INFO .finishedAt("2023-05-10T06:43:02.653Z") .build())) .branch("branch") + .author(BuildKiteBuildInfo.Author.builder().name("XXXX").build()) .build()) .leadTimeInfo(LeadTimeInfo.builder() .firstCommitTimeInPr("2023-05-08T07:18:18Z") @@ -252,7 +372,9 @@ public static List MOCK_PIPELINE_CSV_DATA_WITH_NULL_COMMIT_INFO .prMergedTime("1683793037000") .prLeadTime("16837") .prCreatedTime("168369327000") + .jobStartTime("168369327000") .jobFinishTime("1684793037000") + .firstCommitTime("168369327000") .pipelineLeadTime("653037000") .build()) .deployInfo(DeployInfo.builder() @@ -264,25 +386,6 @@ public static List MOCK_PIPELINE_CSV_DATA_WITH_NULL_COMMIT_INFO return List.of(pipelineCsvInfo); } - public static PipelineLeadTime MOCK_PIPELINE_LEAD_TIME_DATA() { - return PipelineLeadTime.builder() - .pipelineStep("xx") - .pipelineName("xx") - .leadTimes(List.of(LeadTime.builder() - .commitId("xx") - .prCreatedTime(1658549100000L) - .prMergedTime(1658549160000L) - .firstCommitTimeInPr(1658549100000L) - .jobFinishTime(1658549160000L) - .pipelineCreateTime(1658549100000L) - .prLeadTime(60000L) - .pipelineLeadTime(60000) - .totalTime(120000) - .build())) - .build(); - - } - public static List MOCK_TWO_ORGANIZATIONS_PIPELINE_CSV_DATA() { PipelineCSVInfo pipelineCsvInfo1 = PipelineCSVInfo.builder() .organizationName("Thoughtworks-Heartbeat") @@ -303,6 +406,7 @@ public static List MOCK_TWO_ORGANIZATIONS_PIPELINE_CSV_DATA() { .finishedAt("2023-05-10T06:43:02.653Z") .build())) .branch("branch") + .author(BuildKiteBuildInfo.Author.builder().build()) .build()) .commitInfo(CommitInfo.builder() .commit(Commit.builder() @@ -324,7 +428,9 @@ public static List MOCK_TWO_ORGANIZATIONS_PIPELINE_CSV_DATA() { .prMergedTime("1683793037000") .prLeadTime("16837") .prCreatedTime("168369327000") + .jobStartTime("168369327000") .jobFinishTime("1684793037000") + .firstCommitTime("168369327000") .pipelineLeadTime("653037000") .build()) .deployInfo(DeployInfo.builder() @@ -344,7 +450,6 @@ public static List MOCK_TWO_ORGANIZATIONS_PIPELINE_CSV_DATA() { .pipelineCreateTime("2023-05-10T06:17:21.844Z") .state("passed") .number(880) - .creator(BuildKiteBuildInfo.Creator.builder().name("XXXX").email("XXX@test.com").build()) .jobs(List.of(BuildKiteJob.builder() .name(":rocket: Deploy prod") .state("passed") @@ -352,6 +457,7 @@ public static List MOCK_TWO_ORGANIZATIONS_PIPELINE_CSV_DATA() { .finishedAt("2023-05-10T06:43:02.653Z") .build())) .branch("branch") + .author(BuildKiteBuildInfo.Author.builder().name("XXXX").build()) .build()) .commitInfo(CommitInfo.builder() .commit(Commit.builder() @@ -373,7 +479,9 @@ public static List MOCK_TWO_ORGANIZATIONS_PIPELINE_CSV_DATA() { .prMergedTime("1683793037000") .prLeadTime("16837") .prCreatedTime("168369327000") + .jobStartTime("168369327000") .jobFinishTime("1684793037000") + .firstCommitTime("168369327000") .pipelineLeadTime("653037000") .build()) .deployInfo(DeployInfo.builder() @@ -422,7 +530,9 @@ public static List MOCK_TWO_ORGANIZATIONS_PIPELINE_CSV_DATA() { .prMergedTime("1683793037000") .prLeadTime("16837") .prCreatedTime("168369327000") + .jobStartTime("168369327000") .jobFinishTime("1684793037000") + .firstCommitTime("168369327000") .pipelineLeadTime("653037000") .build()) .deployInfo(DeployInfo.builder() diff --git a/backend/src/test/java/heartbeat/service/report/PipelineServiceTest.java b/backend/src/test/java/heartbeat/service/report/PipelineServiceTest.java index 29b6cb172b..23999b918e 100644 --- a/backend/src/test/java/heartbeat/service/report/PipelineServiceTest.java +++ b/backend/src/test/java/heartbeat/service/report/PipelineServiceTest.java @@ -1,6 +1,5 @@ package heartbeat.service.report; -import heartbeat.client.dto.codebase.github.CommitInfo; import heartbeat.client.dto.codebase.github.LeadTime; import heartbeat.client.dto.codebase.github.PipelineLeadTime; import heartbeat.client.dto.pipeline.buildkite.BuildKiteBuildInfo; @@ -33,7 +32,6 @@ import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; @@ -215,6 +213,7 @@ void shouldReturnEmptyWhenDeploymentEnvListIsEmpty() { void shouldReturnValueWhenDeploymentEnvListIsNotEmpty() { List fakeBuildKiteBuildInfos = List.of(BuildKiteBuildInfo.builder() .creator(BuildKiteBuildInfo.Creator.builder().name("someone").build()) + .author(BuildKiteBuildInfo.Author.builder().name("someone").build()) .build()); GenerateReportRequest request = GenerateReportRequest.builder() .buildKiteSetting(BuildKiteSetting.builder() @@ -237,24 +236,28 @@ void shouldReturnValueWhenDeploymentEnvListIsNotEmpty() { assertEquals(result.getDeployTimesList().size(), 1); assertEquals(result.getBuildInfosList().size(), 1); + assertEquals(1, result.getBuildInfosList().get(0).getValue().size()); + assertEquals("someone", result.getBuildInfosList().get(0).getValue().get(0).getAuthor().getName()); verify(buildKiteService, times(1)).fetchPipelineBuilds(any(), any(), any(), any()); verify(buildKiteService, times(1)).countDeployTimes(any(), any(), any(), any()); } @Test - void shouldFilterCreatorByInputCrews() { + void shouldFilterAuthorsByInputCrews() { List fakeBuildKiteBuildInfos = List.of( BuildKiteBuildInfo.builder() .creator(BuildKiteBuildInfo.Creator.builder().name("test-creator1").build()) + .author(BuildKiteBuildInfo.Author.builder().name("test-author1").build()) .build(), BuildKiteBuildInfo.builder() .creator(BuildKiteBuildInfo.Creator.builder().name("test-creator2").build()) + .author(BuildKiteBuildInfo.Author.builder().name("test-author2").build()) .build(), - BuildKiteBuildInfo.builder().creator(null).build()); + BuildKiteBuildInfo.builder().author(null).build()); GenerateReportRequest request = GenerateReportRequest.builder() .buildKiteSetting(BuildKiteSetting.builder() .deploymentEnvList(List.of(DeploymentEnvironment.builder().id("env1").repository("repo1").build())) - .pipelineCrews(List.of("test-creator2", "test-creator3", "Unknown")) + .pipelineCrews(List.of("test-author2", "test-author3", "Unknown")) .build()) .startTime(MOCK_START_TIME) .endTime(MOCK_END_TIME) @@ -283,8 +286,7 @@ class GenerateCSVForPipelineWithCodebase { @Test void shouldReturnEmptyWhenDeploymentEnvironmentsIsEmpty() { - List result = pipelineService.generateCSVForPipelineWithCodebase( - CodebaseSetting.builder().build(), MOCK_START_TIME, MOCK_END_TIME, + List result = pipelineService.generateCSVForPipeline(MOCK_START_TIME, MOCK_END_TIME, FetchedData.BuildKiteData.builder().build(), Lists.list()); assertEquals(0, result.size()); @@ -293,8 +295,7 @@ void shouldReturnEmptyWhenDeploymentEnvironmentsIsEmpty() { @Test void shouldReturnEmptyWhenNoBuildInfoFoundForDeploymentEnvironment() { - List result = pipelineService.generateCSVForPipelineWithCodebase( - CodebaseSetting.builder().build(), MOCK_START_TIME, MOCK_END_TIME, + List result = pipelineService.generateCSVForPipeline(MOCK_START_TIME, MOCK_END_TIME, FetchedData.BuildKiteData.builder().buildInfosList(List.of(Map.entry("env1", List.of()))).build(), List.of(DeploymentEnvironment.builder().id("env1").build())); @@ -307,8 +308,7 @@ void shouldReturnEmptyWhenPipelineStepsIsEmpty() { List kiteBuildInfos = List.of(BuildKiteBuildInfo.builder().build()); when(buildKiteService.getPipelineStepNames(eq(kiteBuildInfos))).thenReturn(List.of()); - List result = pipelineService.generateCSVForPipelineWithCodebase( - CodebaseSetting.builder().build(), MOCK_START_TIME, MOCK_END_TIME, + List result = pipelineService.generateCSVForPipeline(MOCK_START_TIME, MOCK_END_TIME, FetchedData.BuildKiteData.builder() .buildInfosList(List.of(Map.entry("env1", kiteBuildInfos))) .build(), @@ -325,8 +325,7 @@ void shouldReturnEmptyWhenBuildJobIsEmpty() { when(buildKiteService.getBuildKiteJob(any(), any(), any(), eq(MOCK_START_TIME), eq(MOCK_END_TIME))) .thenReturn(null); when(buildKiteService.getStepsBeforeEndStep(any(), any())).thenReturn(List.of("check")); - List result = pipelineService.generateCSVForPipelineWithCodebase( - CodebaseSetting.builder().build(), MOCK_START_TIME, MOCK_END_TIME, + List result = pipelineService.generateCSVForPipeline(MOCK_START_TIME, MOCK_END_TIME, FetchedData.BuildKiteData.builder() .buildInfosList(List.of(Map.entry("env1", kiteBuildInfos))) .build(), @@ -346,8 +345,7 @@ void shouldFilterOutInvalidBuildOfCommentIsEmtpy() { when(buildKiteService.getBuildKiteJob(any(), any(), any(), eq(MOCK_START_TIME), eq(MOCK_END_TIME))) .thenReturn(BuildKiteJob.builder().build()); - List result = pipelineService.generateCSVForPipelineWithCodebase( - CodebaseSetting.builder().build(), MOCK_START_TIME, MOCK_END_TIME, + List result = pipelineService.generateCSVForPipeline(MOCK_START_TIME, MOCK_END_TIME, FetchedData.BuildKiteData.builder() .buildInfosList(List.of(Map.entry("env1", kiteBuildInfos))) .build(), @@ -359,89 +357,52 @@ void shouldFilterOutInvalidBuildOfCommentIsEmtpy() { } @Test - void shouldGenerateValueWithoutCommitWhenCodebaseSettingIsEmpty() { - List kiteBuildInfos = List.of(BuildKiteBuildInfo.builder().commit("commit").build()); - when(buildKiteService.getPipelineStepNames(eq(kiteBuildInfos))).thenReturn(List.of("check")); - when(buildKiteService.getStepsBeforeEndStep(any(), any())).thenReturn(List.of("check")); - when(buildKiteService.getBuildKiteJob(any(), any(), any(), eq(MOCK_START_TIME), eq(MOCK_END_TIME))) - .thenReturn(BuildKiteJob.builder().build()); - when(buildKiteService.mapToDeployInfo(any(), any(), any(), eq(MOCK_START_TIME), eq(MOCK_END_TIME))) - .thenReturn(DeployInfo.builder().jobName("test").build()); - - List result = pipelineService.generateCSVForPipelineWithCodebase(null, MOCK_START_TIME, - MOCK_END_TIME, - FetchedData.BuildKiteData.builder() - .buildInfosList(List.of(Map.entry("env1", kiteBuildInfos))) - .build(), - List.of(DeploymentEnvironment.builder().id("env1").build())); - - assertEquals(1, result.size()); - assertNull(result.get(0).getCommitInfo()); - verify(buildKiteService, times(1)).getPipelineStepNames(any()); - verify(buildKiteService, times(1)).getBuildKiteJob(any(), any(), any(), any(), any()); - } - - @Test - void shouldGenerateValueWithoutCommitWhenCodebaseSettingTokenIsEmpty() { - List kiteBuildInfos = List.of(BuildKiteBuildInfo.builder().commit("commit").build()); - when(buildKiteService.getPipelineStepNames(eq(kiteBuildInfos))).thenReturn(List.of("check")); - when(buildKiteService.getStepsBeforeEndStep(any(), any())).thenReturn(List.of("check")); - when(buildKiteService.getBuildKiteJob(any(), any(), any(), eq(MOCK_START_TIME), eq(MOCK_END_TIME))) - .thenReturn(BuildKiteJob.builder().build()); - when(buildKiteService.mapToDeployInfo(any(), any(), any(), eq(MOCK_START_TIME), eq(MOCK_END_TIME))) - .thenReturn(DeployInfo.builder().jobName("test").build()); - - List result = pipelineService.generateCSVForPipelineWithCodebase( - CodebaseSetting.builder().build(), MOCK_START_TIME, MOCK_END_TIME, - FetchedData.BuildKiteData.builder() - .buildInfosList(List.of(Map.entry("env1", kiteBuildInfos))) - .build(), - List.of(DeploymentEnvironment.builder().id("env1").build())); - - assertEquals(1, result.size()); - assertNull(result.get(0).getCommitInfo()); - verify(buildKiteService, times(1)).getPipelineStepNames(any()); - verify(buildKiteService, times(1)).getBuildKiteJob(any(), any(), any(), any(), any()); - } - - @Test - void shouldGenerateValueWithoutCommitWhenCommitIdIsEmpty() { - List kiteBuildInfos = List.of(BuildKiteBuildInfo.builder().commit("commit").build()); + void shouldGenerateValueHasCommit() { + List kiteBuildInfos = List.of(BuildKiteBuildInfo.builder() + .commit("commit") + .author(BuildKiteBuildInfo.Author.builder().name("xxxx").build()) + .build()); when(buildKiteService.getPipelineStepNames(eq(kiteBuildInfos))).thenReturn(List.of("check")); when(buildKiteService.getStepsBeforeEndStep(any(), any())).thenReturn(List.of("check")); when(buildKiteService.getBuildKiteJob(any(), any(), any(), eq(MOCK_START_TIME), eq(MOCK_END_TIME))) .thenReturn(BuildKiteJob.builder().build()); - when(buildKiteService.mapToDeployInfo(any(), any(), any(), eq(MOCK_START_TIME), eq(MOCK_END_TIME))) - .thenReturn(DeployInfo.builder().jobName("test").build()); + DeployInfo fakeDeploy = DeployInfo.builder().commitId("commitId").jobName("test").build(); + when(buildKiteService.mapToDeployInfo(any(), any(), any(), any(), any())).thenReturn(fakeDeploy); - List result = pipelineService.generateCSVForPipelineWithCodebase( - CodebaseSetting.builder().token("token").build(), MOCK_START_TIME, MOCK_END_TIME, + List result = pipelineService.generateCSVForPipeline(MOCK_START_TIME, MOCK_END_TIME, FetchedData.BuildKiteData.builder() .buildInfosList(List.of(Map.entry("env1", kiteBuildInfos))) .build(), - List.of(DeploymentEnvironment.builder().id("env1").build())); + List.of(DeploymentEnvironment.builder().id("env1").name("env-name").build())); assertEquals(1, result.size()); - assertNull(result.get(0).getCommitInfo()); + PipelineCSVInfo pipelineCSVInfo = result.get(0); + assertEquals("env-name", pipelineCSVInfo.getPipeLineName()); + assertEquals("xxxx", pipelineCSVInfo.getBuildInfo().getAuthor().getName()); + assertEquals(fakeDeploy, pipelineCSVInfo.getDeployInfo()); verify(buildKiteService, times(1)).getPipelineStepNames(any()); verify(buildKiteService, times(1)).getBuildKiteJob(any(), any(), any(), any(), any()); } @Test - void shouldGenerateValueHasCommit() { - List kiteBuildInfos = List.of(BuildKiteBuildInfo.builder().commit("commit").build()); - CommitInfo fakeCommitInfo = CommitInfo.builder().build(); + void shouldGenerateValueWithLeadTimeWhenLeadTimeExisting() { + List kiteBuildInfos = List.of(BuildKiteBuildInfo.builder() + .commit("commit") + .author(BuildKiteBuildInfo.Author.builder().name("xxxx").build()) + .build()); when(buildKiteService.getPipelineStepNames(eq(kiteBuildInfos))).thenReturn(List.of("check")); when(buildKiteService.getStepsBeforeEndStep(any(), any())).thenReturn(List.of("check")); when(buildKiteService.getBuildKiteJob(any(), any(), any(), eq(MOCK_START_TIME), eq(MOCK_END_TIME))) .thenReturn(BuildKiteJob.builder().build()); DeployInfo fakeDeploy = DeployInfo.builder().commitId("commitId").jobName("test").build(); when(buildKiteService.mapToDeployInfo(any(), any(), any(), any(), any())).thenReturn(fakeDeploy); - when(gitHubService.fetchCommitInfo(any(), any(), any())).thenReturn(fakeCommitInfo); - List result = pipelineService.generateCSVForPipelineWithCodebase( - CodebaseSetting.builder().token("token").build(), MOCK_START_TIME, MOCK_END_TIME, + List result = pipelineService.generateCSVForPipeline(MOCK_START_TIME, MOCK_END_TIME, FetchedData.BuildKiteData.builder() + .pipelineLeadTimes(List.of(PipelineLeadTime.builder() + .leadTimes(List.of(LeadTime.builder().commitId("commitId").build())) + .pipelineName("env-name") + .build())) .buildInfosList(List.of(Map.entry("env1", kiteBuildInfos))) .build(), List.of(DeploymentEnvironment.builder().id("env1").name("env-name").build())); @@ -449,26 +410,26 @@ void shouldGenerateValueHasCommit() { assertEquals(1, result.size()); PipelineCSVInfo pipelineCSVInfo = result.get(0); assertEquals("env-name", pipelineCSVInfo.getPipeLineName()); - assertEquals(fakeCommitInfo, pipelineCSVInfo.getCommitInfo()); + assertEquals("xxxx", pipelineCSVInfo.getBuildInfo().getAuthor().getName()); assertEquals(fakeDeploy, pipelineCSVInfo.getDeployInfo()); verify(buildKiteService, times(1)).getPipelineStepNames(any()); verify(buildKiteService, times(1)).getBuildKiteJob(any(), any(), any(), any(), any()); } @Test - void shouldGenerateValueWithLeadTimeWhenLeadTimeExisting() { - List kiteBuildInfos = List.of(BuildKiteBuildInfo.builder().commit("commit").build()); - CommitInfo fakeCommitInfo = CommitInfo.builder().build(); + void shouldGenerateValueWithOrganizationWhenDeployHasOrganization() { + List kiteBuildInfos = List.of(BuildKiteBuildInfo.builder() + .commit("commit") + .author(BuildKiteBuildInfo.Author.builder().name("xxxx").build()) + .build()); when(buildKiteService.getPipelineStepNames(eq(kiteBuildInfos))).thenReturn(List.of("check")); when(buildKiteService.getStepsBeforeEndStep(any(), any())).thenReturn(List.of("check")); when(buildKiteService.getBuildKiteJob(any(), any(), any(), eq(MOCK_START_TIME), eq(MOCK_END_TIME))) .thenReturn(BuildKiteJob.builder().build()); DeployInfo fakeDeploy = DeployInfo.builder().commitId("commitId").jobName("test").build(); when(buildKiteService.mapToDeployInfo(any(), any(), any(), any(), any())).thenReturn(fakeDeploy); - when(gitHubService.fetchCommitInfo(any(), any(), any())).thenReturn(fakeCommitInfo); - List result = pipelineService.generateCSVForPipelineWithCodebase( - CodebaseSetting.builder().token("token").build(), MOCK_START_TIME, MOCK_END_TIME, + List result = pipelineService.generateCSVForPipeline(MOCK_START_TIME, MOCK_END_TIME, FetchedData.BuildKiteData.builder() .pipelineLeadTimes(List.of(PipelineLeadTime.builder() .leadTimes(List.of(LeadTime.builder().commitId("commitId").build())) @@ -476,31 +437,35 @@ void shouldGenerateValueWithLeadTimeWhenLeadTimeExisting() { .build())) .buildInfosList(List.of(Map.entry("env1", kiteBuildInfos))) .build(), - List.of(DeploymentEnvironment.builder().id("env1").name("env-name").build())); + List.of(DeploymentEnvironment.builder() + .id("env1") + .name("env-name") + .orgName("Thoughtworks-Heartbeat") + .build())); assertEquals(1, result.size()); PipelineCSVInfo pipelineCSVInfo = result.get(0); - assertEquals("env-name", pipelineCSVInfo.getPipeLineName()); - assertEquals(fakeCommitInfo, pipelineCSVInfo.getCommitInfo()); + assertEquals("Thoughtworks-Heartbeat", pipelineCSVInfo.getOrganizationName()); + assertEquals("xxxx", pipelineCSVInfo.getBuildInfo().getAuthor().getName()); assertEquals(fakeDeploy, pipelineCSVInfo.getDeployInfo()); verify(buildKiteService, times(1)).getPipelineStepNames(any()); verify(buildKiteService, times(1)).getBuildKiteJob(any(), any(), any(), any(), any()); } @Test - void shouldGenerateValueWithOrganizationWhenDeployHasOrganization() { - List kiteBuildInfos = List.of(BuildKiteBuildInfo.builder().commit("commit").build()); - CommitInfo fakeCommitInfo = CommitInfo.builder().build(); - when(buildKiteService.getPipelineStepNames(eq(kiteBuildInfos))).thenReturn(List.of("check")); + void shouldGenerateValueWhenBuildKiteDataAuthorIsNotNull() { + List kiteBuildInfos = List.of(BuildKiteBuildInfo.builder() + .commit("commit") + .author(BuildKiteBuildInfo.Author.builder().name("xxxx").build()) + .build()); + when(buildKiteService.getPipelineStepNames(kiteBuildInfos)).thenReturn(List.of("check")); when(buildKiteService.getStepsBeforeEndStep(any(), any())).thenReturn(List.of("check")); when(buildKiteService.getBuildKiteJob(any(), any(), any(), eq(MOCK_START_TIME), eq(MOCK_END_TIME))) .thenReturn(BuildKiteJob.builder().build()); DeployInfo fakeDeploy = DeployInfo.builder().commitId("commitId").jobName("test").build(); when(buildKiteService.mapToDeployInfo(any(), any(), any(), any(), any())).thenReturn(fakeDeploy); - when(gitHubService.fetchCommitInfo(any(), any(), any())).thenReturn(fakeCommitInfo); - List result = pipelineService.generateCSVForPipelineWithCodebase( - CodebaseSetting.builder().token("token").build(), MOCK_START_TIME, MOCK_END_TIME, + List result = pipelineService.generateCSVForPipeline(MOCK_START_TIME, MOCK_END_TIME, FetchedData.BuildKiteData.builder() .pipelineLeadTimes(List.of(PipelineLeadTime.builder() .leadTimes(List.of(LeadTime.builder().commitId("commitId").build())) @@ -517,7 +482,7 @@ void shouldGenerateValueWithOrganizationWhenDeployHasOrganization() { assertEquals(1, result.size()); PipelineCSVInfo pipelineCSVInfo = result.get(0); assertEquals("Thoughtworks-Heartbeat", pipelineCSVInfo.getOrganizationName()); - assertEquals(fakeCommitInfo, pipelineCSVInfo.getCommitInfo()); + assertEquals("xxxx", pipelineCSVInfo.getBuildInfo().getAuthor().getName()); assertEquals(fakeDeploy, pipelineCSVInfo.getDeployInfo()); verify(buildKiteService, times(1)).getPipelineStepNames(any()); verify(buildKiteService, times(1)).getBuildKiteJob(any(), any(), any(), any(), any()); diff --git a/backend/src/test/java/heartbeat/service/report/ReportServiceTest.java b/backend/src/test/java/heartbeat/service/report/ReportServiceTest.java index 4dd300290c..828046e644 100644 --- a/backend/src/test/java/heartbeat/service/report/ReportServiceTest.java +++ b/backend/src/test/java/heartbeat/service/report/ReportServiceTest.java @@ -37,6 +37,7 @@ import static heartbeat.controller.report.dto.request.MetricType.BOARD; import static heartbeat.controller.report.dto.request.MetricType.DORA; import static heartbeat.service.report.scheduler.DeleteExpireCSVScheduler.EXPORT_CSV_VALIDITY_TIME; +import static heartbeat.tools.TimeUtils.mockTimeStamp; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; @@ -68,34 +69,48 @@ public class ReportServiceTest { @Captor ArgumentCaptor metricsDataCompletedArgumentCaptor; + public static final String START_TIME = "20240310"; + + public static final String END_TIME = "20240409"; + @Test void exportCsvShouldCallCsvFileGeneratorToGotTheStreamWhenTimestampIsValid() throws IOException { long validTimestamp = System.currentTimeMillis() - EXPORT_CSV_VALIDITY_TIME + 20000L; - when(csvFileGenerator.getDataFromCSV(ReportType.METRIC, validTimestamp)) + String mockTimeRangeTimeStamp = START_TIME + "-" + END_TIME + "-" + validTimestamp; + when(csvFileGenerator.getDataFromCSV(ReportType.METRIC, mockTimeRangeTimeStamp)) .thenReturn(new InputStreamResource(new ByteArrayInputStream("csv data".getBytes()))); - InputStream result = reportService.exportCsv(ReportType.METRIC, validTimestamp).getInputStream(); + InputStream result = reportService + .exportCsv(ReportType.METRIC, String.valueOf(validTimestamp), START_TIME, END_TIME) + .getInputStream(); String returnData = new BufferedReader(new InputStreamReader(result)).lines().collect(Collectors.joining("\n")); assertEquals(returnData, "csv data"); - verify(csvFileGenerator).getDataFromCSV(ReportType.METRIC, validTimestamp); + verify(csvFileGenerator).getDataFromCSV(ReportType.METRIC, mockTimeRangeTimeStamp); } @Test void exportCsvShouldThrowNotFoundExceptionWhenTimestampIsValid() { - long invalidTimestamp = System.currentTimeMillis() - EXPORT_CSV_VALIDITY_TIME - 20000L; - - assertThrows(NotFoundException.class, () -> reportService.exportCsv(ReportType.METRIC, invalidTimestamp)); - verify(csvFileGenerator, never()).getDataFromCSV(ReportType.METRIC, invalidTimestamp); + String invalidTimestamp = String.valueOf(System.currentTimeMillis() - EXPORT_CSV_VALIDITY_TIME - 20000L); + String mockTimeRangeTimeStamp = START_TIME + "-" + END_TIME + "-" + invalidTimestamp; + assertThrows(NotFoundException.class, + () -> reportService.exportCsv(ReportType.METRIC, invalidTimestamp, START_TIME, END_TIME)); + verify(csvFileGenerator, never()).getDataFromCSV(ReportType.METRIC, mockTimeRangeTimeStamp); } @Nested class GenerateReportByType { - String timeStamp = "1683734399999"; + String timeStamp = String.valueOf(mockTimeStamp(2023, 5, 10, 0, 0, 0)); + + String startTimeStamp = String.valueOf(mockTimeStamp(2024, 3, 10, 0, 0, 0)); + + String endTimeStamp = String.valueOf(mockTimeStamp(2024, 4, 9, 0, 0, 0)); GenerateReportRequest request = GenerateReportRequest.builder() .csvTimeStamp(timeStamp) + .startTime(startTimeStamp) + .endTime(endTimeStamp) .metrics(new ArrayList<>()) .metricTypes(List.of(BOARD)) .build(); @@ -109,7 +124,7 @@ void shouldSuccessfulGenerateBoardReportAndInitializeMetricDataWhenMetricTypesLi .build(); doAnswer(invocation -> null).when(asyncMetricsDataHandler).putMetricsDataCompleted(any(), any()); doAnswer(invocation -> null).when(generateReporterService).generateBoardReport(request); - when(generateReporterService.getComposedReportResponse(any())) + when(generateReporterService.getComposedReportResponse(any(), any(), any())) .thenReturn(ReportResponse.builder().reportMetricsError(ReportMetricsError.builder().build()).build()); when(reportGenerator.getReportGenerator(generateReporterService)).thenReturn(Map.of(BOARD, generateReporterService::generateBoardReport, DORA, generateReporterService::generateDoraReport)); @@ -123,9 +138,10 @@ void shouldSuccessfulGenerateBoardReportAndInitializeMetricDataWhenMetricTypesLi Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { verify(generateReporterService).generateBoardReport(request); verify(generateReporterService, never()).generateDoraReport(request); - verify(generateReporterService).getComposedReportResponse(request.getCsvTimeStamp()); - verify(asyncMetricsDataHandler) - .updateOverallMetricsCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp())); + verify(generateReporterService).getComposedReportResponse(request.getCsvTimeStamp(), START_TIME, + END_TIME); + verify(asyncMetricsDataHandler).updateOverallMetricsCompletedInHandler( + IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp())); }); } @@ -139,7 +155,7 @@ void shouldSuccessfulGenerateDoraReportWhenMetricTypesListOnlyHasDoraMetricType( doAnswer(invocation -> null).when(asyncMetricsDataHandler).putMetricsDataCompleted(any(), any()); request.setMetricTypes(List.of(DORA)); doAnswer(invocation -> null).when(generateReporterService).generateDoraReport(request); - when(generateReporterService.getComposedReportResponse(any())) + when(generateReporterService.getComposedReportResponse(any(), any(), any())) .thenReturn(ReportResponse.builder().reportMetricsError(ReportMetricsError.builder().build()).build()); when(reportGenerator.getReportGenerator(generateReporterService)).thenReturn(Map.of(BOARD, generateReporterService::generateBoardReport, DORA, generateReporterService::generateDoraReport)); @@ -153,9 +169,10 @@ void shouldSuccessfulGenerateDoraReportWhenMetricTypesListOnlyHasDoraMetricType( Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { verify(generateReporterService).generateDoraReport(request); verify(generateReporterService, never()).generateBoardReport(request); - verify(generateReporterService).getComposedReportResponse(request.getCsvTimeStamp()); - verify(asyncMetricsDataHandler) - .updateOverallMetricsCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp())); + verify(generateReporterService).getComposedReportResponse(request.getCsvTimeStamp(), START_TIME, + END_TIME); + verify(asyncMetricsDataHandler).updateOverallMetricsCompletedInHandler( + IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp())); }); } @@ -171,7 +188,7 @@ void shouldSuccessfulGenerateDoraReportAndBoardReportGivenMetricTypesListHasDora request.setMetricTypes(List.of(BOARD, DORA)); doAnswer(invocation -> null).when(generateReporterService).generateDoraReport(request); doAnswer(invocation -> null).when(generateReporterService).generateBoardReport(request); - when(generateReporterService.getComposedReportResponse(any())) + when(generateReporterService.getComposedReportResponse(any(), any(), any())) .thenReturn(ReportResponse.builder().reportMetricsError(ReportMetricsError.builder().build()).build()); when(reportGenerator.getReportGenerator(generateReporterService)).thenReturn(Map.of(BOARD, generateReporterService::generateBoardReport, DORA, generateReporterService::generateDoraReport)); @@ -185,9 +202,10 @@ void shouldSuccessfulGenerateDoraReportAndBoardReportGivenMetricTypesListHasDora Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { verify(generateReporterService).generateDoraReport(request); verify(generateReporterService).generateBoardReport(request); - verify(generateReporterService).getComposedReportResponse(request.getCsvTimeStamp()); - verify(asyncMetricsDataHandler) - .updateOverallMetricsCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp())); + verify(generateReporterService).getComposedReportResponse(request.getCsvTimeStamp(), START_TIME, + END_TIME); + verify(asyncMetricsDataHandler).updateOverallMetricsCompletedInHandler( + IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp())); }); } @@ -203,9 +221,11 @@ void shouldNotGenerateMetricCsvWhenBoardMetricsHasError() { request.setMetricTypes(List.of(BOARD, DORA)); doAnswer(invocation -> null).when(generateReporterService).generateDoraReport(request); doAnswer(invocation -> null).when(generateReporterService).generateBoardReport(request); - when(generateReporterService.getComposedReportResponse(any())).thenReturn(ReportResponse.builder() - .reportMetricsError(ReportMetricsError.builder().boardMetricsError(ErrorInfo.builder().build()).build()) - .build()); + when(generateReporterService.getComposedReportResponse(any(), any(), any())) + .thenReturn(ReportResponse.builder() + .reportMetricsError( + ReportMetricsError.builder().boardMetricsError(ErrorInfo.builder().build()).build()) + .build()); when(reportGenerator.getReportGenerator(generateReporterService)).thenReturn(Map.of(BOARD, generateReporterService::generateBoardReport, DORA, generateReporterService::generateDoraReport)); @@ -218,10 +238,11 @@ void shouldNotGenerateMetricCsvWhenBoardMetricsHasError() { Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { verify(generateReporterService).generateDoraReport(request); verify(generateReporterService).generateBoardReport(request); - verify(generateReporterService).getComposedReportResponse(request.getCsvTimeStamp()); + verify(generateReporterService).getComposedReportResponse(request.getCsvTimeStamp(), START_TIME, + END_TIME); verify(generateReporterService, never()).generateCSVForMetric(any(), any()); - verify(asyncMetricsDataHandler, times(1)) - .updateOverallMetricsCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp())); + verify(asyncMetricsDataHandler, times(1)).updateOverallMetricsCompletedInHandler( + IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp())); }); } @@ -237,10 +258,11 @@ void shouldNotGenerateMetricCsvWhenPiplineMetricsErrorHasError() { request.setMetricTypes(List.of(BOARD, DORA)); doAnswer(invocation -> null).when(generateReporterService).generateDoraReport(request); doAnswer(invocation -> null).when(generateReporterService).generateBoardReport(request); - when(generateReporterService.getComposedReportResponse(any())).thenReturn(ReportResponse.builder() - .reportMetricsError( - ReportMetricsError.builder().pipelineMetricsError(ErrorInfo.builder().build()).build()) - .build()); + when(generateReporterService.getComposedReportResponse(any(), any(), any())) + .thenReturn(ReportResponse.builder() + .reportMetricsError( + ReportMetricsError.builder().pipelineMetricsError(ErrorInfo.builder().build()).build()) + .build()); when(reportGenerator.getReportGenerator(generateReporterService)).thenReturn(Map.of(BOARD, generateReporterService::generateBoardReport, DORA, generateReporterService::generateDoraReport)); @@ -253,10 +275,11 @@ void shouldNotGenerateMetricCsvWhenPiplineMetricsErrorHasError() { Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { verify(generateReporterService).generateDoraReport(request); verify(generateReporterService).generateBoardReport(request); - verify(generateReporterService).getComposedReportResponse(request.getCsvTimeStamp()); + verify(generateReporterService).getComposedReportResponse(request.getCsvTimeStamp(), START_TIME, + END_TIME); verify(generateReporterService, never()).generateCSVForMetric(any(), any()); - verify(asyncMetricsDataHandler, times(1)) - .updateOverallMetricsCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp())); + verify(asyncMetricsDataHandler, times(1)).updateOverallMetricsCompletedInHandler( + IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp())); }); } @@ -272,10 +295,11 @@ void shouldNotGenerateMetricCsvWhenSourceControlMetricsErrorHasError() { request.setMetricTypes(List.of(BOARD, DORA)); doAnswer(invocation -> null).when(generateReporterService).generateDoraReport(request); doAnswer(invocation -> null).when(generateReporterService).generateBoardReport(request); - when(generateReporterService.getComposedReportResponse(any())).thenReturn(ReportResponse.builder() - .reportMetricsError( - ReportMetricsError.builder().sourceControlMetricsError(ErrorInfo.builder().build()).build()) - .build()); + when(generateReporterService.getComposedReportResponse(any(), any(), any())) + .thenReturn(ReportResponse.builder() + .reportMetricsError( + ReportMetricsError.builder().sourceControlMetricsError(ErrorInfo.builder().build()).build()) + .build()); when(reportGenerator.getReportGenerator(generateReporterService)).thenReturn(Map.of(BOARD, generateReporterService::generateBoardReport, DORA, generateReporterService::generateDoraReport)); @@ -288,10 +312,11 @@ void shouldNotGenerateMetricCsvWhenSourceControlMetricsErrorHasError() { Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { verify(generateReporterService).generateDoraReport(request); verify(generateReporterService).generateBoardReport(request); - verify(generateReporterService).getComposedReportResponse(request.getCsvTimeStamp()); + verify(generateReporterService).getComposedReportResponse(request.getCsvTimeStamp(), START_TIME, + END_TIME); verify(generateReporterService, never()).generateCSVForMetric(any(), any()); - verify(asyncMetricsDataHandler, times(1)) - .updateOverallMetricsCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp())); + verify(asyncMetricsDataHandler, times(1)).updateOverallMetricsCompletedInHandler( + IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp())); }); } @@ -313,7 +338,7 @@ void shouldSuccessfulGenerateDoraReportGivenBoardReportHasBeenGeneratedWhenRetry doAnswer(invocation -> null).when(asyncMetricsDataHandler).putMetricsDataCompleted(any(), any()); request.setMetricTypes(List.of(DORA)); doAnswer(invocation -> null).when(generateReporterService).generateDoraReport(request); - when(generateReporterService.getComposedReportResponse(any())) + when(generateReporterService.getComposedReportResponse(any(), any(), any())) .thenReturn(ReportResponse.builder().reportMetricsError(ReportMetricsError.builder().build()).build()); reportService.generateReport(request); @@ -325,10 +350,11 @@ void shouldSuccessfulGenerateDoraReportGivenBoardReportHasBeenGeneratedWhenRetry Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { verify(generateReporterService).generateDoraReport(request); verify(generateReporterService, never()).generateBoardReport(request); - verify(generateReporterService).getComposedReportResponse(request.getCsvTimeStamp()); + verify(generateReporterService).getComposedReportResponse(request.getCsvTimeStamp(), START_TIME, + END_TIME); verify(generateReporterService).generateCSVForMetric(any(), any()); - verify(asyncMetricsDataHandler, times(1)) - .updateOverallMetricsCompletedInHandler(IdUtil.getDataCompletedPrefix(request.getCsvTimeStamp())); + verify(asyncMetricsDataHandler, times(1)).updateOverallMetricsCompletedInHandler( + IdUtil.getDataCompletedPrefix(request.getTimeRangeAndTimeStamp())); }); } diff --git a/backend/src/test/java/heartbeat/service/source/github/GithubServiceTest.java b/backend/src/test/java/heartbeat/service/source/github/GithubServiceTest.java index de611d50fe..aa7b23d095 100644 --- a/backend/src/test/java/heartbeat/service/source/github/GithubServiceTest.java +++ b/backend/src/test/java/heartbeat/service/source/github/GithubServiceTest.java @@ -127,12 +127,15 @@ public void setUp() { .prCreatedTime(1658548980000L) .prMergedTime(1658549040000L) .firstCommitTimeInPr(1658548980000L) + .jobStartTime(1658549040000L) .jobFinishTime(1658549160000L) .pipelineLeadTime(1658549100000L) .pipelineCreateTime(1658549100000L) .prLeadTime(60000L) .pipelineLeadTime(120000) + .firstCommitTime(1658549040000L) .totalTime(180000) + .isRevert(Boolean.FALSE) .build())) .build()); @@ -267,12 +270,15 @@ void shouldReturnLeadTimeWhenMergedTimeIsNotNull() { .prCreatedTime(1658548980000L) .prMergedTime(1658549040000L) .firstCommitTimeInPr(1658548980000L) + .jobStartTime(1658549040000L) .jobFinishTime(1658549160000L) .pipelineLeadTime(1658549100000L) .pipelineCreateTime(1658549100000L) .prLeadTime(60000L) .pipelineLeadTime(120000) + .firstCommitTime(1658549040000L) .totalTime(180000) + .isRevert(Boolean.FALSE) .build(); LeadTime result = githubService.mapLeadTimeWithInfo(pullRequestInfo, deployInfo, commitInfo); @@ -288,12 +294,15 @@ void CommitTimeInPrShouldBeZeroWhenCommitInfoIsNull() { .prCreatedTime(1658548980000L) .prMergedTime(1658549040000L) .firstCommitTimeInPr(0L) + .jobStartTime(1658549040000L) .jobFinishTime(1658549160000L) .pipelineLeadTime(1658549100000L) .pipelineCreateTime(1658549100000L) - .prLeadTime(60000L) + .prLeadTime(0L) .pipelineLeadTime(120000) - .totalTime(180000) + .firstCommitTime(1658549040000L) + .totalTime(120000) + .isRevert(Boolean.FALSE) .build(); LeadTime result = githubService.mapLeadTimeWithInfo(pullRequestInfo, deployInfo, commitInfo); @@ -309,12 +318,15 @@ void CommitTimeInPrLeadTimeShouldBeZeroWhenCommitInfoIsNotNullGivenCommitIsRever .prCreatedTime(1658548980000L) .prMergedTime(1658549040000L) .firstCommitTimeInPr(0L) + .jobStartTime(1658549040000L) .jobFinishTime(1658549160000L) .pipelineLeadTime(1658549100000L) .pipelineCreateTime(1658549100000L) .prLeadTime(0L) .pipelineLeadTime(120000) + .firstCommitTime(1658549040000L) .totalTime(120000) + .isRevert(Boolean.TRUE) .build(); LeadTime result = githubService.mapLeadTimeWithInfo(pullRequestInfo, deployInfo, commitInfo); @@ -330,11 +342,14 @@ void CommitTimeInPrLeadTimeShouldBeZeroWhenCommitInfoIsInLowerCaseGivenCommitIsR .prCreatedTime(1658548980000L) .prMergedTime(1658549040000L) .firstCommitTimeInPr(0L) + .jobStartTime(1658549040000L) .jobFinishTime(1658549160000L) .pipelineLeadTime(1658549100000L) .pipelineCreateTime(1658549100000L) .prLeadTime(0L) .pipelineLeadTime(120000) + .firstCommitTime(1658549040000L) + .isRevert(Boolean.TRUE) .totalTime(120000) .build(); @@ -343,6 +358,54 @@ void CommitTimeInPrLeadTimeShouldBeZeroWhenCommitInfoIsInLowerCaseGivenCommitIsR assertEquals(expect, result); } + @Test + void shouldReturnIsRevertIsNullWhenCommitInfoCommitIsNull() { + commitInfo = CommitInfo.builder().build(); + LeadTime expect = LeadTime.builder() + .commitId("111") + .prCreatedTime(1658548980000L) + .prMergedTime(1658549040000L) + .firstCommitTimeInPr(0L) + .jobStartTime(1658549040000L) + .jobFinishTime(1658549160000L) + .pipelineLeadTime(1658549100000L) + .pipelineCreateTime(1658549100000L) + .prLeadTime(0L) + .pipelineLeadTime(120000) + .firstCommitTime(1658549040000L) + .totalTime(120000L) + .isRevert(null) + .build(); + + LeadTime result = githubService.mapLeadTimeWithInfo(pullRequestInfo, deployInfo, commitInfo); + + assertEquals(expect, result); + } + + @Test + void shouldReturnIsRevertIsNullWhenCommitInfoCommitMessageIsNull() { + commitInfo = CommitInfo.builder().commit(Commit.builder().build()).build(); + LeadTime expect = LeadTime.builder() + .commitId("111") + .prCreatedTime(1658548980000L) + .prMergedTime(1658549040000L) + .firstCommitTimeInPr(0L) + .jobStartTime(1658549040000L) + .jobFinishTime(1658549160000L) + .pipelineLeadTime(1658549100000L) + .pipelineCreateTime(1658549100000L) + .prLeadTime(0L) + .pipelineLeadTime(120000) + .firstCommitTime(1658549040000L) + .totalTime(120000L) + .isRevert(null) + .build(); + + LeadTime result = githubService.mapLeadTimeWithInfo(pullRequestInfo, deployInfo, commitInfo); + + assertEquals(expect, result); + } + @Test void shouldReturnFirstCommitTimeInPrZeroWhenCommitInfoIsNull() { commitInfo = CommitInfo.builder().commit(Commit.builder().message("mock commit message").build()).build(); @@ -351,12 +414,15 @@ void shouldReturnFirstCommitTimeInPrZeroWhenCommitInfoIsNull() { .prCreatedTime(1658548980000L) .prMergedTime(1658549040000L) .firstCommitTimeInPr(0L) + .jobStartTime(1658549040000L) .jobFinishTime(1658549160000L) .pipelineLeadTime(1658549100000L) .pipelineCreateTime(1658549100000L) - .prLeadTime(60000L) + .prLeadTime(0L) .pipelineLeadTime(120000) - .totalTime(180000L) + .firstCommitTime(1658549040000L) + .totalTime(120000L) + .isRevert(Boolean.FALSE) .build(); LeadTime result = githubService.mapLeadTimeWithInfo(pullRequestInfo, deployInfo, commitInfo); @@ -398,11 +464,15 @@ void shouldReturnEmptyLeadTimeGithubShaIsDifferent() { .pipelineName("Name") .leadTimes(List.of(LeadTime.builder() .commitId("111") + .noPRCommitTime(1658548980000L) + .jobStartTime(1658549040000L) .jobFinishTime(1658549160000L) .pipelineCreateTime(1658549100000L) .prLeadTime(0L) .pipelineLeadTime(180000) + .firstCommitTime(1658548980000L) .totalTime(180000) + .isRevert(Boolean.FALSE) .build())) .build()); var pullRequestInfoWithDifferentSha = PullRequestInfo.builder() @@ -430,11 +500,14 @@ void shouldReturnEmptyMergeLeadTimeWhenPullRequestInfoIsEmpty() { .pipelineName("Name") .leadTimes(List.of(LeadTime.builder() .commitId("111") + .jobStartTime(1658549040000L) .jobFinishTime(1658549160000L) .pipelineCreateTime(1658549100000L) .prLeadTime(0L) .pipelineLeadTime(120000) .totalTime(120000) + .firstCommitTime(1658549040000L) + .isRevert(null) .build())) .build()); when(gitHubFeignClient.getPullRequestListInfo(any(), any(), any())).thenReturn(List.of()); @@ -454,16 +527,20 @@ void shouldReturnEmptyMergeLeadTimeWhenPullRequestInfoGot404Error() { .pipelineName("Name") .leadTimes(List.of(LeadTime.builder() .commitId("111") + .jobStartTime(1658549040000L) .jobFinishTime(1658549160000L) .pipelineCreateTime(1658549100000L) .prLeadTime(0L) .pipelineLeadTime(120000) + .firstCommitTime(1658549040000L) .totalTime(120000) + .isRevert(Boolean.FALSE) .build())) .build()); when(gitHubFeignClient.getPullRequestListInfo(any(), any(), any())).thenThrow(new NotFoundException("")); when(gitHubFeignClient.getPullRequestCommitInfo(any(), any(), any())).thenReturn(List.of()); - when(gitHubFeignClient.getCommitInfo(any(), any(), any())).thenReturn(new CommitInfo()); + when(gitHubFeignClient.getCommitInfo(any(), any(), any())) + .thenReturn(CommitInfo.builder().commit(Commit.builder().message("").build()).build()); List result = githubService.fetchPipelinesLeadTime(deployTimes, repositoryMap, mockToken); @@ -479,11 +556,14 @@ void shouldReturnEmptyMergeLeadTimeWhenMergeTimeIsEmpty() { .pipelineName("Name") .leadTimes(List.of(LeadTime.builder() .commitId("111") + .jobStartTime(1658549040000L) .jobFinishTime(1658549160000L) .pipelineCreateTime(1658549100000L) .prLeadTime(0L) .pipelineLeadTime(120000) + .firstCommitTime(1658549040000L) .totalTime(120000) + .isRevert(null) .build())) .build()); when(gitHubFeignClient.getPullRequestListInfo(any(), any(), any())).thenReturn(List.of(pullRequestInfo)); @@ -591,12 +671,15 @@ void shouldReturnPipeLineLeadTimeWhenDeployCommitShaIsDifferent() { .pipelineStep(PIPELINE_STEP) .leadTimes(List.of(LeadTime.builder() .commitId("111") + .jobStartTime(1658549040000L) .jobFinishTime(1658549160000L) .pipelineLeadTime(1658549100000L) .pipelineCreateTime(1658549100000L) .prLeadTime(0L) .pipelineLeadTime(120000) + .firstCommitTime(1658549040000L) .totalTime(120000) + .isRevert(null) .build())) .build()); when(gitHubFeignClient.getPullRequestListInfo(any(), any(), any())).thenReturn(List.of(pullRequestInfo)); diff --git a/backend/src/test/java/heartbeat/util/BoardUtilTest.java b/backend/src/test/java/heartbeat/util/BoardUtilTest.java index f676888c5b..65bfbd565f 100644 --- a/backend/src/test/java/heartbeat/util/BoardUtilTest.java +++ b/backend/src/test/java/heartbeat/util/BoardUtilTest.java @@ -81,7 +81,7 @@ void calculateCycleTimeWhenTreatFlagCardAsBlockIsFalse() { } @Test - void calculateOriginCycleTimeOfColumn() { + void shouldCalculateOriginCycleTimeGivenTreatFlagCardAsBlockIsTrue() { List statusChangedItems = StatusChangedItemsListAndCycleTimeInfosListFixture .STATUS_CHANGED_ITEMS_LIST_OF_ORIGIN(); List statusChangedItemsExpect = StatusChangedItemsListAndCycleTimeInfosListFixture @@ -89,8 +89,22 @@ void calculateOriginCycleTimeOfColumn() { when(workDay.calculateWorkDaysBy24Hours(anyLong(), anyLong())) .thenReturn(StatusChangedItemsListAndCycleTimeInfosListFixture.EXPECT_DAYS); - List result = boardUtil.getOriginCycleTimeInfos(statusChangedItems); + + List result = boardUtil.getOriginCycleTimeInfos(statusChangedItems, Boolean.TRUE); Assertions.assertEquals(statusChangedItemsExpect, result); } + @Test + void shouldCalculateOriginCycleTimeGivenTreatFlagCardAsBlockIsFalse() { + List statusChangedItems = StatusChangedItemsListAndCycleTimeInfosListFixture + .STATUS_CHANGED_ITEMS_LIST_OF_ORIGIN(); + List statusChangedItemsWithoutFlagExpect = StatusChangedItemsListAndCycleTimeInfosListFixture + .CYCLE_TIME_INFOS_LIST_OF_ORIGIN_WITHOUT_FLAG(); + + when(workDay.calculateWorkDaysBy24Hours(anyLong(), anyLong())) + .thenReturn(StatusChangedItemsListAndCycleTimeInfosListFixture.EXPECT_DAYS); + List result = boardUtil.getOriginCycleTimeInfos(statusChangedItems, Boolean.FALSE); + Assertions.assertEquals(statusChangedItemsWithoutFlagExpect, result); + } + } diff --git a/backend/src/test/java/heartbeat/util/IdUtilTest.java b/backend/src/test/java/heartbeat/util/IdUtilTest.java index af68fe91a4..112457b371 100644 --- a/backend/src/test/java/heartbeat/util/IdUtilTest.java +++ b/backend/src/test/java/heartbeat/util/IdUtilTest.java @@ -10,7 +10,7 @@ void shouldReturnBoardReportId() { String timeStamp = "121322545121"; String expected = "board-121322545121"; - String boardReportId = IdUtil.getBoardReportId(timeStamp); + String boardReportId = IdUtil.getBoardReportFileId(timeStamp); Assertions.assertEquals(expected, boardReportId); } @@ -20,7 +20,7 @@ void shouldReturnPipelineReportId() { String timeStamp = "121322545121"; String expected = "pipeline-121322545121"; - String pipelineReportId = IdUtil.getPipelineReportId(timeStamp); + String pipelineReportId = IdUtil.getPipelineReportFileId(timeStamp); Assertions.assertEquals(expected, pipelineReportId); } @@ -30,7 +30,7 @@ void shouldReturnSourceControlReportId() { String timeStamp = "121322545121"; String expected = "sourceControl-121322545121"; - String sourceControlReportId = IdUtil.getSourceControlReportId(timeStamp); + String sourceControlReportId = IdUtil.getSourceControlReportFileId(timeStamp); Assertions.assertEquals(expected, sourceControlReportId); } diff --git a/backend/src/test/java/heartbeat/util/StatusChangedItemsListAndCycleTimeInfosListFixture.java b/backend/src/test/java/heartbeat/util/StatusChangedItemsListAndCycleTimeInfosListFixture.java index 8e0c8126ed..f6c65353f3 100644 --- a/backend/src/test/java/heartbeat/util/StatusChangedItemsListAndCycleTimeInfosListFixture.java +++ b/backend/src/test/java/heartbeat/util/StatusChangedItemsListAndCycleTimeInfosListFixture.java @@ -9,6 +9,14 @@ public class StatusChangedItemsListAndCycleTimeInfosListFixture { public static double EXPECT_DAYS = 4.0; + public static final String DONE = "DONE"; + + public static final String BLOCK = "BLOCK"; + + public static final String IN_PROGRESS = "IN PROGRESS"; + + public static final String FLAG = "FLAG"; + public static List STATUS_CHANGED_ITEMS_LIST_OF_REAL_DONE_COLUMN() { return List.of(StatusChangedItem.builder().timestamp(1000000L).status("In Progress").build(), StatusChangedItem.builder().timestamp(2000000L).status("DONE").build(), @@ -17,9 +25,9 @@ public static List STATUS_CHANGED_ITEMS_LIST_OF_REAL_DONE_COL } public static List CYCLE_TIME_INFOS_LIST_OF_REAL_DONE_COLUMN() { - return List.of(CycleTimeInfo.builder().column("DONE").day(EXPECT_DAYS).build(), - CycleTimeInfo.builder().column("BLOCK").day(0.0).build(), - CycleTimeInfo.builder().column("IN PROGRESS").day(EXPECT_DAYS).build()); + return List.of(CycleTimeInfo.builder().column(DONE).day(EXPECT_DAYS).build(), + CycleTimeInfo.builder().column(IN_PROGRESS).day(EXPECT_DAYS).build(), + CycleTimeInfo.builder().column(FLAG).day(0.0).build()); } public static List STATUS_CHANGED_ITEMS_LIST_OF_BLOCK_COLUMN() { @@ -31,9 +39,9 @@ public static List STATUS_CHANGED_ITEMS_LIST_OF_BLOCK_COLUMN( } public static List CYCLE_TIME_INFOS_LIST_OF_BLOCK_COLUMN() { - return List.of(CycleTimeInfo.builder().column("DONE").day(EXPECT_DAYS).build(), - CycleTimeInfo.builder().column("BLOCK").day(EXPECT_DAYS).build(), - CycleTimeInfo.builder().column("IN PROGRESS").day(EXPECT_DAYS).build()); + return List.of(CycleTimeInfo.builder().column(DONE).day(EXPECT_DAYS).build(), + CycleTimeInfo.builder().column(BLOCK).day(EXPECT_DAYS).build(), + CycleTimeInfo.builder().column(IN_PROGRESS).day(EXPECT_DAYS).build()); } public static List STATUS_CHANGED_ITEMS_LIST_OF_OTHER_COLUMN() { @@ -45,9 +53,9 @@ public static List STATUS_CHANGED_ITEMS_LIST_OF_OTHER_COLUMN( } public static List CYCLE_TIME_INFOS_LIST_OF_OTHER_COLUMN() { - return List.of(CycleTimeInfo.builder().column("DONE").day(EXPECT_DAYS).build(), - CycleTimeInfo.builder().column("BLOCK").day(8.0).build(), - CycleTimeInfo.builder().column("IN PROGRESS").day(0.0).build()); + return List.of(CycleTimeInfo.builder().column(DONE).day(EXPECT_DAYS).build(), + CycleTimeInfo.builder().column(BLOCK).day(8.0).build(), + CycleTimeInfo.builder().column(IN_PROGRESS).day(0.0).build()); } public static List STATUS_CHANGED_ITEMS_LIST_WHEN_NOT_TREAT_FLAG_AS_BLOCK() { @@ -59,9 +67,9 @@ public static List STATUS_CHANGED_ITEMS_LIST_WHEN_NOT_TREAT_F } public static List CYCLE_TIME_INFOS_LIST_WHEN_NOT_TREAT_FLAG_AS_BLOCK() { - return List.of(CycleTimeInfo.builder().column("DONE").day(EXPECT_DAYS).build(), - CycleTimeInfo.builder().column("BLOCK").day(EXPECT_DAYS).build(), - CycleTimeInfo.builder().column("IN PROGRESS").day(EXPECT_DAYS).build()); + return List.of(CycleTimeInfo.builder().column(DONE).day(EXPECT_DAYS).build(), + CycleTimeInfo.builder().column(BLOCK).day(EXPECT_DAYS).build(), + CycleTimeInfo.builder().column(IN_PROGRESS).day(EXPECT_DAYS).build()); } public static List STATUS_CHANGED_ITEMS_LIST_OF_ORIGIN() { @@ -73,10 +81,15 @@ public static List STATUS_CHANGED_ITEMS_LIST_OF_ORIGIN() { } public static List CYCLE_TIME_INFOS_LIST_OF_ORIGIN() { - return List.of(CycleTimeInfo.builder().column("DONE").day(EXPECT_DAYS).build(), - CycleTimeInfo.builder().column("BLOCK").day(EXPECT_DAYS).build(), - CycleTimeInfo.builder().column("IN PROGRESS").day(EXPECT_DAYS).build(), - CycleTimeInfo.builder().column("FLAG").day(EXPECT_DAYS).build()); + return List.of(CycleTimeInfo.builder().column(DONE).day(EXPECT_DAYS).build(), + CycleTimeInfo.builder().column(IN_PROGRESS).day(EXPECT_DAYS).build(), + CycleTimeInfo.builder().column(FLAG).day(EXPECT_DAYS).build()); + } + + public static List CYCLE_TIME_INFOS_LIST_OF_ORIGIN_WITHOUT_FLAG() { + return List.of(CycleTimeInfo.builder().column(DONE).day(EXPECT_DAYS).build(), + CycleTimeInfo.builder().column(BLOCK).day(EXPECT_DAYS).build(), + CycleTimeInfo.builder().column(IN_PROGRESS).day(EXPECT_DAYS).build()); } } diff --git a/docs/.nvmrc b/docs/.nvmrc index 617bcf916b..87834047a6 100644 --- a/docs/.nvmrc +++ b/docs/.nvmrc @@ -1 +1 @@ -18.14.1 +20.12.2 diff --git a/docs/package.json b/docs/package.json index 58df912566..b0cafe0075 100644 --- a/docs/package.json +++ b/docs/package.json @@ -37,7 +37,7 @@ "@actions/core": "^1.10.1", "@astrojs/mdx": "^2.2.0", "@astrojs/preact": "^3.1.1", - "@astrojs/sitemap": "3.1.2", + "@astrojs/sitemap": "3.1.4", "@babel/core": "^7.24.3", "@docsearch/css": "^3.6.0", "@docsearch/react": "^3.6.0", @@ -45,7 +45,7 @@ "@types/hast": "^3.0.4", "@types/html-escaper": "^3.0.2", "@types/mdast": "^4.0.3", - "@types/node": "20.11.30", + "@types/node": "20.12.7", "@typescript-eslint/eslint-plugin": "^7.3.1", "@typescript-eslint/parser": "^7.3.1", "algoliasearch": "^4.22.1", @@ -112,7 +112,7 @@ "rehype-autolink-headings": "^7.1.0", "rehype-slug": "^6.0.0", "remark-gfm": "^4.0.0", - "remark-smartypants": "^2.1.0", + "remark-smartypants": "^3.0.0", "sass": "^1.72.0" }, "engines": { @@ -121,7 +121,7 @@ "lint-staged": { "**/*": "npm run fix" }, - "packageManager": "pnpm@8.10.0", + "packageManager": "pnpm@9.0.6", "pnpm": { "peerDependencyRules": { "ignoreMissing": [ diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index 9441904971..b3c449c45c 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -1,255 +1,4381 @@ -lockfileVersion: '6.0' +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -dependencies: - '@akebifiky/remark-simple-plantuml': - specifier: ^1.0.2 - version: 1.0.2 - '@astrojs/check': - specifier: ^0.5.9 - version: 0.5.9(prettier-plugin-astro@0.13.0)(prettier@3.2.5)(typescript@5.4.2) - '@fontsource/ibm-plex-mono': - specifier: 5.0.12 - version: 5.0.12 - '@nanostores/preact': - specifier: ^0.5.1 - version: 0.5.1(nanostores@0.10.0)(preact@10.19.7) - canvas-confetti: - specifier: ^1.9.2 - version: 1.9.2 - jsdoc-api: - specifier: ^8.0.0 - version: 8.0.0 - minimatch: - specifier: ^9.0.3 - version: 9.0.3 - nanostores: - specifier: ^0.10.0 - version: 0.10.0 - rehype-autolink-headings: - specifier: ^7.1.0 - version: 7.1.0 - rehype-slug: - specifier: ^6.0.0 - version: 6.0.0 - remark-gfm: - specifier: ^4.0.0 - version: 4.0.0 - remark-smartypants: - specifier: ^2.1.0 - version: 2.1.0 - sass: - specifier: ^1.72.0 - version: 1.72.0 - -devDependencies: - '@11ty/eleventy-fetch': - specifier: ^4.0.1 - version: 4.0.1 - '@actions/core': - specifier: ^1.10.1 - version: 1.10.1 - '@astrojs/mdx': - specifier: ^2.2.0 - version: 2.2.2(astro@4.5.7) - '@astrojs/preact': - specifier: ^3.1.1 - version: 3.1.1(@babel/core@7.24.3)(preact@10.19.7) - '@astrojs/sitemap': - specifier: 3.1.2 - version: 3.1.2 - '@babel/core': - specifier: ^7.24.3 - version: 7.24.3 - '@docsearch/css': - specifier: ^3.6.0 - version: 3.6.0 - '@docsearch/react': - specifier: ^3.6.0 - version: 3.6.0(@algolia/client-search@4.23.2)(search-insights@2.13.0) - '@types/canvas-confetti': - specifier: ^1.6.4 - version: 1.6.4 - '@types/hast': - specifier: ^3.0.4 - version: 3.0.4 - '@types/html-escaper': - specifier: ^3.0.2 - version: 3.0.2 - '@types/mdast': - specifier: ^4.0.3 - version: 4.0.3 - '@types/node': - specifier: 20.11.30 - version: 20.11.30 - '@typescript-eslint/eslint-plugin': - specifier: ^7.3.1 - version: 7.4.0(@typescript-eslint/parser@7.4.0)(eslint@8.57.0)(typescript@5.4.2) - '@typescript-eslint/parser': - specifier: ^7.3.1 - version: 7.4.0(eslint@8.57.0)(typescript@5.4.2) - algoliasearch: - specifier: ^4.22.1 - version: 4.22.1 - astro: - specifier: ^4.5.7 - version: 4.5.7(@types/node@20.11.30)(sass@1.72.0)(typescript@5.4.2) - astro-auto-import: - specifier: ^0.4.2 - version: 0.4.2(astro@4.5.7) - astro-eslint-parser: - specifier: ^0.16.3 - version: 0.16.3 - astro-expressive-code: - specifier: ^0.33.5 - version: 0.33.5(astro@4.5.7) - astro-og-canvas: - specifier: ^0.4.2 - version: 0.4.2(astro@4.5.7) - bcp-47-normalize: - specifier: ^2.3.0 - version: 2.3.0 - canvaskit-wasm: - specifier: ^0.39.1 - version: 0.39.1 - dedent-js: - specifier: ^1.0.1 - version: 1.0.1 - domhandler: - specifier: ^5.0.3 - version: 5.0.3 - eslint: - specifier: ^8.57.0 - version: 8.57.0 - eslint-plugin-astro: - specifier: ^0.33.0 - version: 0.33.1(eslint@8.57.0) - eslint-plugin-react: - specifier: ^7.34.1 - version: 7.34.1(eslint@8.57.0) - fast-glob: - specifier: ^3.3.2 - version: 3.3.2 - gray-matter: - specifier: ^4.0.3 - version: 4.0.3 - hast-util-from-html: - specifier: ^2.0.1 - version: 2.0.1 - hast-util-to-html: - specifier: ^9.0.0 - version: 9.0.0 - hast-util-to-string: - specifier: ^3.0.0 - version: 3.0.0 - hastscript: - specifier: ^9.0.0 - version: 9.0.0 - html-escaper: - specifier: ^3.0.3 - version: 3.0.3 - htmlparser2: - specifier: ^9.1.0 - version: 9.1.0 - husky: - specifier: ^9.0.11 - version: 9.0.11 - kleur: - specifier: ^4.1.5 - version: 4.1.5 - lint-staged: - specifier: ^15.2.2 - version: 15.2.2 - mdast-util-from-markdown: - specifier: ^2.0.0 - version: 2.0.0 - mdast-util-mdx-jsx: - specifier: ^3.1.2 - version: 3.1.2 - mdast-util-to-hast: - specifier: ^13.1.0 - version: 13.1.0 - mdast-util-to-string: - specifier: ^4.0.0 - version: 4.0.0 - micromark-util-character: - specifier: ^2.1.0 - version: 2.1.0 - micromark-util-symbol: - specifier: ^2.0.0 - version: 2.0.0 - node-fetch: - specifier: ^3.3.2 - version: 3.3.2 - organize-imports-cli: - specifier: ^0.10.0 - version: 0.10.0 - p-retry: - specifier: ^6.2.0 - version: 6.2.0 - parse-numeric-range: - specifier: ^1.3.0 - version: 1.3.0 - preact: - specifier: ^10.19.7 - version: 10.19.7 - prettier: - specifier: ^3.2.5 - version: 3.2.5 - prettier-plugin-astro: - specifier: ^0.13.0 - version: 0.13.0 - prompts: - specifier: ^2.4.2 - version: 2.4.2 - rehype: - specifier: ^13.0.1 - version: 13.0.1 - remark: - specifier: ^15.0.1 - version: 15.0.1 - remark-directive: - specifier: ^3.0.0 - version: 3.0.0 - remove-markdown: - specifier: ^0.5.0 - version: 0.5.0 - simple-git: - specifier: ^3.23.0 - version: 3.23.0 - tsm: - specifier: ^2.3.0 - version: 2.3.0 - typescript: - specifier: ^5.4.2 - version: 5.4.2 - unified: - specifier: ^11.0.4 - version: 11.0.4 - unist-util-remove: - specifier: ^4.0.0 - version: 4.0.0 - unist-util-visit: - specifier: ^5.0.0 - version: 5.0.0 - unist-util-walker: - specifier: ^1.0.0 - version: 1.0.0 - vfile: - specifier: ^6.0.1 - version: 6.0.1 - vitest: - specifier: ^1.4.0 - version: 1.4.0(@types/node@20.11.30)(sass@1.72.0) +importers: + + .: + dependencies: + '@akebifiky/remark-simple-plantuml': + specifier: ^1.0.2 + version: 1.0.2 + '@astrojs/check': + specifier: ^0.5.9 + version: 0.5.10(prettier-plugin-astro@0.13.0)(prettier@3.2.5)(typescript@5.4.5) + '@fontsource/ibm-plex-mono': + specifier: 5.0.12 + version: 5.0.12 + '@nanostores/preact': + specifier: ^0.5.1 + version: 0.5.1(nanostores@0.10.0)(preact@10.19.7) + canvas-confetti: + specifier: ^1.9.2 + version: 1.9.2 + jsdoc-api: + specifier: ^8.0.0 + version: 8.0.0 + minimatch: + specifier: ^9.0.3 + version: 9.0.3 + nanostores: + specifier: ^0.10.0 + version: 0.10.0 + rehype-autolink-headings: + specifier: ^7.1.0 + version: 7.1.0 + rehype-slug: + specifier: ^6.0.0 + version: 6.0.0 + remark-gfm: + specifier: ^4.0.0 + version: 4.0.0 + remark-smartypants: + specifier: ^3.0.0 + version: 3.0.1 + sass: + specifier: ^1.72.0 + version: 1.72.0 + devDependencies: + '@11ty/eleventy-fetch': + specifier: ^4.0.1 + version: 4.0.1 + '@actions/core': + specifier: ^1.10.1 + version: 1.10.1 + '@astrojs/mdx': + specifier: ^2.2.0 + version: 2.2.2(astro@4.5.7(@types/node@20.12.7)(sass@1.72.0)(typescript@5.4.5)) + '@astrojs/preact': + specifier: ^3.1.1 + version: 3.1.1(@babel/core@7.24.3)(preact@10.19.7)(vite@5.2.8(@types/node@20.12.7)(sass@1.72.0)) + '@astrojs/sitemap': + specifier: 3.1.4 + version: 3.1.4 + '@babel/core': + specifier: ^7.24.3 + version: 7.24.3 + '@docsearch/css': + specifier: ^3.6.0 + version: 3.6.0 + '@docsearch/react': + specifier: ^3.6.0 + version: 3.6.0(@algolia/client-search@4.23.3)(search-insights@2.13.0) + '@types/canvas-confetti': + specifier: ^1.6.4 + version: 1.6.4 + '@types/hast': + specifier: ^3.0.4 + version: 3.0.4 + '@types/html-escaper': + specifier: ^3.0.2 + version: 3.0.2 + '@types/mdast': + specifier: ^4.0.3 + version: 4.0.3 + '@types/node': + specifier: 20.12.7 + version: 20.12.7 + '@typescript-eslint/eslint-plugin': + specifier: ^7.3.1 + version: 7.4.0(@typescript-eslint/parser@7.4.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': + specifier: ^7.3.1 + version: 7.4.0(eslint@8.57.0)(typescript@5.4.5) + algoliasearch: + specifier: ^4.22.1 + version: 4.22.1 + astro: + specifier: ^4.5.7 + version: 4.5.7(@types/node@20.12.7)(sass@1.72.0)(typescript@5.4.5) + astro-auto-import: + specifier: ^0.4.2 + version: 0.4.2(astro@4.5.7(@types/node@20.12.7)(sass@1.72.0)(typescript@5.4.5)) + astro-eslint-parser: + specifier: ^0.16.3 + version: 0.16.3 + astro-expressive-code: + specifier: ^0.33.5 + version: 0.33.5(astro@4.5.7(@types/node@20.12.7)(sass@1.72.0)(typescript@5.4.5)) + astro-og-canvas: + specifier: ^0.4.2 + version: 0.4.2(astro@4.5.7(@types/node@20.12.7)(sass@1.72.0)(typescript@5.4.5)) + bcp-47-normalize: + specifier: ^2.3.0 + version: 2.3.0 + canvaskit-wasm: + specifier: ^0.39.1 + version: 0.39.1 + dedent-js: + specifier: ^1.0.1 + version: 1.0.1 + domhandler: + specifier: ^5.0.3 + version: 5.0.3 + eslint: + specifier: ^8.57.0 + version: 8.57.0 + eslint-plugin-astro: + specifier: ^0.33.0 + version: 0.33.1(eslint@8.57.0) + eslint-plugin-react: + specifier: ^7.34.1 + version: 7.34.1(eslint@8.57.0) + fast-glob: + specifier: ^3.3.2 + version: 3.3.2 + gray-matter: + specifier: ^4.0.3 + version: 4.0.3 + hast-util-from-html: + specifier: ^2.0.1 + version: 2.0.1 + hast-util-to-html: + specifier: ^9.0.0 + version: 9.0.0 + hast-util-to-string: + specifier: ^3.0.0 + version: 3.0.0 + hastscript: + specifier: ^9.0.0 + version: 9.0.0 + html-escaper: + specifier: ^3.0.3 + version: 3.0.3 + htmlparser2: + specifier: ^9.1.0 + version: 9.1.0 + husky: + specifier: ^9.0.11 + version: 9.0.11 + kleur: + specifier: ^4.1.5 + version: 4.1.5 + lint-staged: + specifier: ^15.2.2 + version: 15.2.2 + mdast-util-from-markdown: + specifier: ^2.0.0 + version: 2.0.0 + mdast-util-mdx-jsx: + specifier: ^3.1.2 + version: 3.1.2 + mdast-util-to-hast: + specifier: ^13.1.0 + version: 13.1.0 + mdast-util-to-string: + specifier: ^4.0.0 + version: 4.0.0 + micromark-util-character: + specifier: ^2.1.0 + version: 2.1.0 + micromark-util-symbol: + specifier: ^2.0.0 + version: 2.0.0 + node-fetch: + specifier: ^3.3.2 + version: 3.3.2 + organize-imports-cli: + specifier: ^0.10.0 + version: 0.10.0 + p-retry: + specifier: ^6.2.0 + version: 6.2.0 + parse-numeric-range: + specifier: ^1.3.0 + version: 1.3.0 + preact: + specifier: ^10.19.7 + version: 10.19.7 + prettier: + specifier: ^3.2.5 + version: 3.2.5 + prettier-plugin-astro: + specifier: ^0.13.0 + version: 0.13.0 + prompts: + specifier: ^2.4.2 + version: 2.4.2 + rehype: + specifier: ^13.0.1 + version: 13.0.1 + remark: + specifier: ^15.0.1 + version: 15.0.1 + remark-directive: + specifier: ^3.0.0 + version: 3.0.0 + remove-markdown: + specifier: ^0.5.0 + version: 0.5.0 + simple-git: + specifier: ^3.23.0 + version: 3.23.0 + tsm: + specifier: ^2.3.0 + version: 2.3.0 + typescript: + specifier: ^5.4.2 + version: 5.4.5 + unified: + specifier: ^11.0.4 + version: 11.0.4 + unist-util-remove: + specifier: ^4.0.0 + version: 4.0.0 + unist-util-visit: + specifier: ^5.0.0 + version: 5.0.0 + unist-util-walker: + specifier: ^1.0.0 + version: 1.0.0 + vfile: + specifier: ^6.0.1 + version: 6.0.1 + vitest: + specifier: ^1.4.0 + version: 1.4.0(@types/node@20.12.7)(sass@1.72.0) + +packages: + + '@11ty/eleventy-fetch@4.0.1': + resolution: {integrity: sha512-yIiLM5ziBmg86i4TlXpBdcIygJHvh/GgPJyAiFOckO9H4y9cQDM8eIcJCUQ4Mum0NEVui/OjhEut2R08xw0vlQ==} + engines: {node: '>=14'} + + '@aashutoshrathi/word-wrap@1.2.6': + resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} + engines: {node: '>=0.10.0'} + + '@actions/core@1.10.1': + resolution: {integrity: sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==} + + '@actions/http-client@2.2.1': + resolution: {integrity: sha512-KhC/cZsq7f8I4LfZSJKgCvEwfkE8o1538VoBeoGzokVLLnbFDEAdFD3UhoMklxo2un9NJVBdANOresx7vTHlHw==} + + '@akebifiky/remark-simple-plantuml@1.0.2': + resolution: {integrity: sha512-y5rWgQvU+DMpLKx1KlXCsgUeqVooqQm1S3hePLF9iecZy6YhKRybznFdvAvoAoiV2GoGhObQDHnneAl93llIcg==} + + '@algolia/autocomplete-core@1.9.3': + resolution: {integrity: sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==} + + '@algolia/autocomplete-plugin-algolia-insights@1.9.3': + resolution: {integrity: sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==} + peerDependencies: + search-insights: '>= 1 < 3' + + '@algolia/autocomplete-preset-algolia@1.9.3': + resolution: {integrity: sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/autocomplete-shared@1.9.3': + resolution: {integrity: sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/cache-browser-local-storage@4.22.1': + resolution: {integrity: sha512-Sw6IAmOCvvP6QNgY9j+Hv09mvkvEIDKjYW8ow0UDDAxSXy664RBNQk3i/0nt7gvceOJ6jGmOTimaZoY1THmU7g==} + + '@algolia/cache-common@4.22.1': + resolution: {integrity: sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA==} + + '@algolia/cache-common@4.23.3': + resolution: {integrity: sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A==} + + '@algolia/cache-in-memory@4.22.1': + resolution: {integrity: sha512-ve+6Ac2LhwpufuWavM/aHjLoNz/Z/sYSgNIXsinGofWOysPilQZPUetqLj8vbvi+DHZZaYSEP9H5SRVXnpsNNw==} + + '@algolia/client-account@4.22.1': + resolution: {integrity: sha512-k8m+oegM2zlns/TwZyi4YgCtyToackkOpE+xCaKCYfBfDtdGOaVZCM5YvGPtK+HGaJMIN/DoTL8asbM3NzHonw==} + + '@algolia/client-analytics@4.22.1': + resolution: {integrity: sha512-1ssi9pyxyQNN4a7Ji9R50nSdISIumMFDwKNuwZipB6TkauJ8J7ha/uO60sPJFqQyqvvI+px7RSNRQT3Zrvzieg==} + + '@algolia/client-common@4.22.1': + resolution: {integrity: sha512-IvaL5v9mZtm4k4QHbBGDmU3wa/mKokmqNBqPj0K7lcR8ZDKzUorhcGp/u8PkPC/e0zoHSTvRh7TRkGX3Lm7iOQ==} + + '@algolia/client-common@4.23.3': + resolution: {integrity: sha512-l6EiPxdAlg8CYhroqS5ybfIczsGUIAC47slLPOMDeKSVXYG1n0qGiz4RjAHLw2aD0xzh2EXZ7aRguPfz7UKDKw==} + + '@algolia/client-personalization@4.22.1': + resolution: {integrity: sha512-sl+/klQJ93+4yaqZ7ezOttMQ/nczly/3GmgZXJ1xmoewP5jmdP/X/nV5U7EHHH3hCUEHeN7X1nsIhGPVt9E1cQ==} + + '@algolia/client-search@4.22.1': + resolution: {integrity: sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA==} + + '@algolia/client-search@4.23.3': + resolution: {integrity: sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw==} + + '@algolia/logger-common@4.22.1': + resolution: {integrity: sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg==} + + '@algolia/logger-common@4.23.3': + resolution: {integrity: sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g==} + + '@algolia/logger-console@4.22.1': + resolution: {integrity: sha512-O99rcqpVPKN1RlpgD6H3khUWylU24OXlzkavUAMy6QZd1776QAcauE3oP8CmD43nbaTjBexZj2nGsBH9Tc0FVA==} + + '@algolia/requester-browser-xhr@4.22.1': + resolution: {integrity: sha512-dtQGYIg6MteqT1Uay3J/0NDqD+UciHy3QgRbk7bNddOJu+p3hzjTRYESqEnoX/DpEkaNYdRHUKNylsqMpgwaEw==} + + '@algolia/requester-common@4.22.1': + resolution: {integrity: sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg==} + + '@algolia/requester-common@4.23.3': + resolution: {integrity: sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw==} + + '@algolia/requester-node-http@4.22.1': + resolution: {integrity: sha512-JfmZ3MVFQkAU+zug8H3s8rZ6h0ahHZL/SpMaSasTCGYR5EEJsCc8SI5UZ6raPN2tjxa5bxS13BRpGSBUens7EA==} + + '@algolia/transporter@4.22.1': + resolution: {integrity: sha512-kzWgc2c9IdxMa3YqA6TN0NW5VrKYYW/BELIn7vnLyn+U/RFdZ4lxxt9/8yq3DKV5snvoDzzO4ClyejZRdV3lMQ==} + + '@algolia/transporter@4.23.3': + resolution: {integrity: sha512-Wjl5gttqnf/gQKJA+dafnD0Y6Yw97yvfY8R9h0dQltX1GXTgNs1zWgvtWW0tHl1EgMdhAyw189uWiZMnL3QebQ==} + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@astrojs/check@0.5.10': + resolution: {integrity: sha512-vliHXM9cu/viGeKiksUM4mXfO816ohWtawTl2ADPgTsd4nUMjFiyAl7xFZhF34yy4hq4qf7jvK1F2PlR3b5I5w==} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + + '@astrojs/compiler@1.8.2': + resolution: {integrity: sha512-o/ObKgtMzl8SlpIdzaxFnt7SATKPxu4oIP/1NL+HDJRzxfJcAkOTAb/ZKMRyULbz4q+1t2/DAebs2Z1QairkZw==} + + '@astrojs/compiler@2.7.0': + resolution: {integrity: sha512-XpC8MAaWjD1ff6/IfkRq/5k1EFj6zhCNqXRd5J43SVJEBj/Bsmizkm8N0xOYscGcDFQkRgEw6/eKnI5x/1l6aA==} + + '@astrojs/compiler@2.7.1': + resolution: {integrity: sha512-/POejAYuj8WEw7ZI0J8JBvevjfp9jQ9Wmu/Bg52RiNwGXkMV7JnYpsenVfHvvf1G7R5sXHGKlTcxlQWhoUTiGQ==} + + '@astrojs/internal-helpers@0.3.0': + resolution: {integrity: sha512-tGmHvrhpzuz0JBHaJX8GywN9g4rldVNHtkoVDC3m/DdzBO70jGoVuc0uuNVglRYnsdwkbG0K02Iw3nOOR3/Y4g==} + + '@astrojs/language-server@2.8.4': + resolution: {integrity: sha512-sJH5vGTBkhgA8+hdhzX78UUp4cFz4Mt7xkEkevD188OS5bDMkaue6hK+dtXWM47mnrXFveXA2u38K7S+5+IRjA==} + hasBin: true + peerDependencies: + prettier: ^3.0.0 + prettier-plugin-astro: '>=0.11.0' + peerDependenciesMeta: + prettier: + optional: true + prettier-plugin-astro: + optional: true + + '@astrojs/markdown-remark@4.3.0': + resolution: {integrity: sha512-iZOgYj/yNDvBRfKqkGuAvjeONhjQPq8Uk3HjyIgcTK5valq03NiUgSc5Ovq00yUVBeYJ/5EDx23c8xqtkkBlPw==} + + '@astrojs/markdown-remark@4.3.2': + resolution: {integrity: sha512-4Oa4VaYiBd0MatB+rWIU/0A8pZH/sK3c2QkRYb+OO2lPl+qzevJtWaZY8hAQc4qurIOlRdn6B6ofDAGhWw+DSg==} + + '@astrojs/mdx@2.2.2': + resolution: {integrity: sha512-5SIFtOctC813HFyqJwBf5TBvlT9sbiOOv+bdvzAoiBSab95dC7PZhss22EvUEx+897c81YoIZ4F9fg4ZkxBFIw==} + engines: {node: '>=18.14.1'} + peerDependencies: + astro: ^4.0.0 + + '@astrojs/preact@3.1.1': + resolution: {integrity: sha512-ASgmVzh4wLyIyynp5CIfDwE45Vg/tIP+Y+5SnQtURmCP1qZpjdUbsw+bGQ0wCSXtjIbzCBa7Kw7Qn0g6WE2W2w==} + engines: {node: '>=18.14.1'} + peerDependencies: + preact: ^10.6.5 + + '@astrojs/prism@3.0.0': + resolution: {integrity: sha512-g61lZupWq1bYbcBnYZqdjndShr/J3l/oFobBKPA3+qMat146zce3nz2kdO4giGbhYDt4gYdhmoBz0vZJ4sIurQ==} + engines: {node: '>=18.14.1'} + + '@astrojs/sitemap@3.1.4': + resolution: {integrity: sha512-po8CqDCK14O6phU1mB5C8SyVLyQEa+7pJM8oXxs1mVh8DgvxxaA5E7lak1vzOmBcyyyHBW32jakGqNYc66sBRw==} + + '@astrojs/telemetry@3.0.4': + resolution: {integrity: sha512-A+0c7k/Xy293xx6odsYZuXiaHO0PL+bnDoXOc47sGDF5ffIKdKQGRPFl2NMlCF4L0NqN4Ynbgnaip+pPF0s7pQ==} + engines: {node: '>=18.14.1'} + + '@babel/code-frame@7.24.1': + resolution: {integrity: sha512-bC49z4spJQR3j8vFtJBLqzyzFV0ciuL5HYX7qfSl3KEqeMVV+eTquRvmXxpvB0AMubRrvv7y5DILiLLPi57Ewg==} + engines: {node: '>=6.9.0'} + + '@babel/code-frame@7.24.2': + resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.24.1': + resolution: {integrity: sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.24.3': + resolution: {integrity: sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.24.1': + resolution: {integrity: sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.22.5': + resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.23.6': + resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-environment-visitor@7.22.20': + resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-function-name@7.23.0': + resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-hoist-variables@7.22.5': + resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.24.1': + resolution: {integrity: sha512-HfEWzysMyOa7xI5uQHc/OcZf67/jc+xe/RZlznWQHhbb8Pg1SkRdbK4yEi61aY8wxQA7PkSfoojtLQP/Kpe3og==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.24.3': + resolution: {integrity: sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.23.3': + resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.24.0': + resolution: {integrity: sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-simple-access@7.22.5': + resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-split-export-declaration@7.22.6': + resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.24.1': + resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.22.20': + resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.23.5': + resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.24.1': + resolution: {integrity: sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.24.1': + resolution: {integrity: sha512-EPmDPxidWe/Ex+HTFINpvXdPHRmgSF3T8hGvzondYjmgzTQ/0EbLpSxyt+w3zzlYSk9cNBQNF9k0dT5Z2NiBjw==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.24.2': + resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.24.1': + resolution: {integrity: sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-jsx@7.24.1': + resolution: {integrity: sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-development@7.22.5': + resolution: {integrity: sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx@7.23.4': + resolution: {integrity: sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.24.0': + resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.24.1': + resolution: {integrity: sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.24.0': + resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} + engines: {node: '>=6.9.0'} + + '@ctrl/tinycolor@3.6.1': + resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} + engines: {node: '>=10'} + + '@docsearch/css@3.6.0': + resolution: {integrity: sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==} + + '@docsearch/react@3.6.0': + resolution: {integrity: sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==} + peerDependencies: + '@types/react': '>= 16.8.0 < 19.0.0' + react: '>= 16.8.0 < 19.0.0' + react-dom: '>= 16.8.0 < 19.0.0' + search-insights: '>= 1 < 3' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + react-dom: + optional: true + search-insights: + optional: true + + '@emmetio/abbreviation@2.3.3': + resolution: {integrity: sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==} + + '@emmetio/css-abbreviation@2.1.8': + resolution: {integrity: sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==} + + '@emmetio/scanner@1.0.4': + resolution: {integrity: sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==} + + '@esbuild/aix-ppc64@0.19.12': + resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.20.2': + resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.19.12': + resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.20.2': + resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.15.18': + resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.19.12': + resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.20.2': + resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.19.12': + resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.20.2': + resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.19.12': + resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.20.2': + resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.19.12': + resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.20.2': + resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.19.12': + resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.20.2': + resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.19.12': + resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.20.2': + resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.19.12': + resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.20.2': + resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.19.12': + resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.20.2': + resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.19.12': + resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.20.2': + resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.15.18': + resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.19.12': + resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.20.2': + resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.19.12': + resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.20.2': + resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.19.12': + resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.20.2': + resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.19.12': + resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.20.2': + resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.19.12': + resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.20.2': + resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.19.12': + resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.20.2': + resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.19.12': + resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.20.2': + resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.19.12': + resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.20.2': + resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.19.12': + resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.20.2': + resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.19.12': + resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.20.2': + resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.19.12': + resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.20.2': + resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.19.12': + resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.20.2': + resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.0': + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.10.0': + resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.0': + resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@expressive-code/core@0.33.5': + resolution: {integrity: sha512-KL0EkKAvd7SSIQL3ZIP19xqe4xNjBaQYNvcJC6RmoBUnQpvxaJNFwRxCBEF/X0ftJEMaSG7WTrabZ9c/zFeqmA==} + + '@expressive-code/plugin-frames@0.33.5': + resolution: {integrity: sha512-lFt/gbnZscBSxHovg4XiWohp5nrxk4McS6RGABdj6+0gJcX8/YrFTM23GKBIkaDePxdDidVY0jQYGYDL/RrQHw==} + + '@expressive-code/plugin-shiki@0.33.5': + resolution: {integrity: sha512-LWgttQTUrIPE1X+Lya1qFWiX47tH2AS2hkbj/cZoWkdiSjn6zUvtTypK/2Xn6Rgn6z6ClzpgHvkXRqFn7nAB4A==} + + '@expressive-code/plugin-text-markers@0.33.5': + resolution: {integrity: sha512-JxSHL1MGrJAPNaUMjFXex3K+9NJDbfew9H6PmX8LQ+fm9VNQdtBYTAz/x7nqOk7bkTrtAZK5RfDqUfb8U5M+2A==} + + '@fastify/busboy@2.1.1': + resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} + engines: {node: '>=14'} + + '@fontsource/ibm-plex-mono@5.0.12': + resolution: {integrity: sha512-RamYYYUQk7FX/yVbQqGxyMR+AfX5hfCZsLo5pr5BBUBNf2i3N4AjJ4AWfieqLx1Mdwt2ukzXYojlf9J0G/gaZQ==} + + '@humanwhocodes/config-array@0.11.14': + resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} + engines: {node: '>=10.10.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.2': + resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.4.15': + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@jsdoc/salty@0.2.7': + resolution: {integrity: sha512-mh8LbS9d4Jq84KLw8pzho7XC2q2/IJGiJss3xwRoLD1A+EE16SjN4PfaG4jRCzKegTFLlN0Zd8SdUPE6XdoPFg==} + engines: {node: '>=v12.0.0'} + + '@kwsites/file-exists@1.1.1': + resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} + + '@kwsites/promise-deferred@1.1.1': + resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + + '@mdx-js/mdx@3.0.1': + resolution: {integrity: sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==} + + '@nanostores/preact@0.5.1': + resolution: {integrity: sha512-kofyeDwzM3TrOd37ay+Xxgk3Cn6jih23dxELc7Mr9IJV55jmWATfNP9b7O/awwCL7CE5z5PfzFnNk/W+tMaWGw==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + nanostores: ^0.9.0 || ^0.10.0 + preact: '>=10.0.0' + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgr/core@0.1.1': + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@preact/preset-vite@2.8.2': + resolution: {integrity: sha512-m3tl+M8IO8jgiHnk+7LSTFl8axdPXloewi7iGVLdmCwf34XOzEUur0bZVewW4DUbUipFjTS2CXu27+5f/oexBA==} + peerDependencies: + '@babel/core': 7.x + vite: 2.x || 3.x || 4.x || 5.x + + '@preact/signals-core@1.5.1': + resolution: {integrity: sha512-dE6f+WCX5ZUDwXzUIWNMhhglmuLpqJhuy3X3xHrhZYI0Hm2LyQwOu0l9mdPiWrVNsE+Q7txOnJPgtIqHCYoBVA==} + + '@preact/signals@1.2.2': + resolution: {integrity: sha512-ColCqdo4cRP18bAuIR4Oik5rDpiyFtPIJIygaYPMEAwTnl4buWkBOflGBSzhYyPyJfKpkwlekrvK+1pzQ2ldWw==} + peerDependencies: + preact: 10.x + + '@prefresh/babel-plugin@0.5.1': + resolution: {integrity: sha512-uG3jGEAysxWoyG3XkYfjYHgaySFrSsaEb4GagLzYaxlydbuREtaX+FTxuIidp241RaLl85XoHg9Ej6E4+V1pcg==} + + '@prefresh/core@1.5.2': + resolution: {integrity: sha512-A/08vkaM1FogrCII5PZKCrygxSsc11obExBScm3JF1CryK2uDS3ZXeni7FeKCx1nYdUkj4UcJxzPzc1WliMzZA==} + peerDependencies: + preact: ^10.0.0 + + '@prefresh/utils@1.2.0': + resolution: {integrity: sha512-KtC/fZw+oqtwOLUFM9UtiitB0JsVX0zLKNyRTA332sqREqSALIIQQxdUCS1P3xR/jT1e2e8/5rwH6gdcMLEmsQ==} + + '@prefresh/vite@2.4.5': + resolution: {integrity: sha512-iForDVJ2M8gQYnm5pHumvTEJjGGc7YNYC0GVKnHFL+GvFfKHfH9Rpq67nUAzNbjuLEpqEOUuQVQajMazWu2ZNQ==} + peerDependencies: + preact: ^10.4.0 + vite: '>=2.0.0' + + '@rollup/pluginutils@4.2.1': + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} + + '@rollup/rollup-android-arm-eabi@4.13.0': + resolution: {integrity: sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.13.0': + resolution: {integrity: sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.13.0': + resolution: {integrity: sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.13.0': + resolution: {integrity: sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-linux-arm-gnueabihf@4.13.0': + resolution: {integrity: sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.13.0': + resolution: {integrity: sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.13.0': + resolution: {integrity: sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.13.0': + resolution: {integrity: sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.13.0': + resolution: {integrity: sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.13.0': + resolution: {integrity: sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.13.0': + resolution: {integrity: sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.13.0': + resolution: {integrity: sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.13.0': + resolution: {integrity: sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==} + cpu: [x64] + os: [win32] + + '@shikijs/core@1.2.0': + resolution: {integrity: sha512-OlFvx+nyr5C8zpcMBnSGir0YPD6K11uYhouqhNmm1qLiis4GA7SsGtu07r9gKS9omks8RtQqHrJL4S+lqWK01A==} + + '@shikijs/core@1.2.1': + resolution: {integrity: sha512-KaIS0H4EQ3KI2d++TjYqRNgwp8E3M/68e9veR4QtInzA7kKFgcjeiJqb80fuXW+blDy5fmd11PN9g9soz/3ANQ==} + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@ts-morph/common@0.16.0': + resolution: {integrity: sha512-SgJpzkTgZKLKqQniCjLaE3c2L2sdL7UShvmTmPBejAKd2OKV/yfMpQ2IWpAuA+VY5wy7PkSUaEObIqEK6afFuw==} + + '@types/acorn@4.0.6': + resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.5': + resolution: {integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==} + + '@types/canvas-confetti@1.6.4': + resolution: {integrity: sha512-fNyZ/Fdw/Y92X0vv7B+BD6ysHL4xVU5dJcgzgxLdGbn8O3PezZNIJpml44lKM0nsGur+o/6+NZbZeNTt00U1uA==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + + '@types/hast@2.3.10': + resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/html-escaper@3.0.2': + resolution: {integrity: sha512-A8vk09eyYzk8J/lFO4OUMKCmRN0rRzfZf4n3Olwapgox/PtTiU8zPYlL1UEkJ/WeHvV6v9Xnj3o/705PKz9r4Q==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/linkify-it@3.0.5': + resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} + + '@types/markdown-it@12.2.3': + resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==} + + '@types/mdast@4.0.3': + resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==} + + '@types/mdurl@1.0.5': + resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==} + + '@types/mdx@2.0.12': + resolution: {integrity: sha512-H9VZ9YqE+H28FQVchC83RCs5xQ2J7mAAv6qdDEaWmXEVl3OpdH+xfrSUzQ1lp7U7oSTRZ0RvW08ASPJsYBi7Cw==} + + '@types/ms@0.7.34': + resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + + '@types/nlcst@1.0.4': + resolution: {integrity: sha512-ABoYdNQ/kBSsLvZAekMhIPMQ3YUZvavStpKYs7BjLLuKVmIMA0LUgZ7b54zzuWJRbHF80v1cNf4r90Vd6eMQDg==} + + '@types/nlcst@2.0.3': + resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} + + '@types/node@17.0.45': + resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} + + '@types/node@18.19.26': + resolution: {integrity: sha512-+wiMJsIwLOYCvUqSdKTrfkS8mpTp+MPINe6+Np4TAGFWWRWiBQ5kSq9nZGCSPkzx9mvT+uEukzpX4MOSCydcvw==} + + '@types/node@20.12.7': + resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} + + '@types/parse5@6.0.3': + resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} + + '@types/retry@0.12.2': + resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} + + '@types/sax@1.2.7': + resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} + + '@types/semver@7.5.8': + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + + '@types/strip-bom@3.0.0': + resolution: {integrity: sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==} + + '@types/strip-json-comments@0.0.30': + resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==} + + '@types/unist@2.0.10': + resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} + + '@types/unist@3.0.2': + resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} + + '@typescript-eslint/eslint-plugin@7.4.0': + resolution: {integrity: sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@7.4.0': + resolution: {integrity: sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@5.62.0': + resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/scope-manager@7.4.0': + resolution: {integrity: sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/type-utils@7.4.0': + resolution: {integrity: sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@5.62.0': + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/types@7.4.0': + resolution: {integrity: sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/typescript-estree@7.4.0': + resolution: {integrity: sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@7.4.0': + resolution: {integrity: sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + + '@typescript-eslint/visitor-keys@5.62.0': + resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/visitor-keys@7.4.0': + resolution: {integrity: sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@ungap/structured-clone@1.2.0': + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + + '@vitest/expect@1.4.0': + resolution: {integrity: sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==} + + '@vitest/runner@1.4.0': + resolution: {integrity: sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==} + + '@vitest/snapshot@1.4.0': + resolution: {integrity: sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==} + + '@vitest/spy@1.4.0': + resolution: {integrity: sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==} + + '@vitest/utils@1.4.0': + resolution: {integrity: sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==} + + '@volar/kit@2.1.6': + resolution: {integrity: sha512-dSuXChDGM0nSG/0fxqlNfadjpAeeo1P1SJPBQ+pDf8H1XrqeJq5gIhxRTEbiS+dyNIG69ATq1CArkbCif+oxJw==} + peerDependencies: + typescript: '*' + + '@volar/language-core@2.1.6': + resolution: {integrity: sha512-pAlMCGX/HatBSiDFMdMyqUshkbwWbLxpN/RL7HCQDOo2gYBE+uS+nanosLc1qR6pTQ/U8q00xt8bdrrAFPSC0A==} + + '@volar/language-server@2.1.6': + resolution: {integrity: sha512-0w+FV8ro37hVb3qE4ONo3VbS5kEQXv4H/D2xCePyY5dRw6XnbJAPFNKvoxI9mxHTPonvIG1si5rN9MSGSKtgZQ==} + + '@volar/language-service@2.1.6': + resolution: {integrity: sha512-1OpbbPQ6wUIumwMP5r45y8utVEmvq1n6BC8JHqGKsuFr9RGFIldDBlvA/xuO3MDKhjmmPGPHKb54kg1/YN78ow==} + + '@volar/snapshot-document@2.1.6': + resolution: {integrity: sha512-YNYk1sCOrGg7VHbZM+1It97q0GWhFxdqIwnxSNFoL0X1LuSRXoCT2DRb/aa1J6aBpPMbKqSFUWHGQEAFUnc4Zw==} + + '@volar/source-map@2.1.6': + resolution: {integrity: sha512-TeyH8pHHonRCHYI91J7fWUoxi0zWV8whZTVRlsWHSYfjm58Blalkf9LrZ+pj6OiverPTmrHRkBsG17ScQyWECw==} + + '@volar/typescript@2.1.6': + resolution: {integrity: sha512-JgPGhORHqXuyC3r6skPmPHIZj4LoMmGlYErFTuPNBq9Nhc9VTv7ctHY7A3jMN3ngKEfRrfnUcwXHztvdSQqNfw==} + + '@vscode/emmet-helper@2.9.3': + resolution: {integrity: sha512-rB39LHWWPQYYlYfpv9qCoZOVioPCftKXXqrsyqN1mTWZM6dTnONT63Db+03vgrBbHzJN45IrgS/AGxw9iiqfEw==} + + '@vscode/l10n@0.0.16': + resolution: {integrity: sha512-JT5CvrIYYCrmB+dCana8sUqJEcGB1ZDXNLMQ2+42bW995WmNoenijWMUdZfwmuQUTQcEVVIa2OecZzTYWUW9Cg==} + + '@vscode/l10n@0.0.18': + resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} + + '@webgpu/types@0.1.21': + resolution: {integrity: sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.2: + resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + engines: {node: '>=0.4.0'} + + acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + algoliasearch@4.22.1: + resolution: {integrity: sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg==} + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-escapes@6.2.0: + resolution: {integrity: sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==} + engines: {node: '>=14.16'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + array-back@1.0.4: + resolution: {integrity: sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==} + engines: {node: '>=0.12.0'} + + array-back@4.0.2: + resolution: {integrity: sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==} + engines: {node: '>=8'} + + array-back@5.0.0: + resolution: {integrity: sha512-kgVWwJReZWmVuWOQKEOohXKJX+nD02JAZ54D1RRWlv8L0NebauKAaFxACKzB74RTclt1+WNz5KHaLRDAPZbDEw==} + engines: {node: '>=10'} + + array-back@6.2.2: + resolution: {integrity: sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==} + engines: {node: '>=12.17'} + + array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} + + array-includes@3.1.7: + resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} + engines: {node: '>= 0.4'} + + array-iterate@2.0.1: + resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + array.prototype.findlast@1.2.4: + resolution: {integrity: sha512-BMtLxpV+8BD+6ZPFIWmnUBpQoy+A+ujcg4rhp2iwCRJYA7PEh2MS4NL3lz8EiDlLrJPp2hg9qWihr5pd//jcGw==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.2: + resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.2: + resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} + engines: {node: '>= 0.4'} + + array.prototype.toreversed@1.1.2: + resolution: {integrity: sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==} + + array.prototype.tosorted@1.1.3: + resolution: {integrity: sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==} + + arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} + engines: {node: '>= 0.4'} + + assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + + astring@1.8.6: + resolution: {integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==} + hasBin: true + + astro-auto-import@0.4.2: + resolution: {integrity: sha512-ZgWZQ58+EhbEym1+aoUnNyECOy0wsG5uRUs+rVp/7BzHtj1V76J2qkhjaTWLplgNb+8WrzhvTQNxytmXRCW+Ow==} + engines: {node: '>=16.0.0'} + peerDependencies: + astro: ^2.0.0 || ^3.0.0-beta || ^4.0.0-beta + + astro-eslint-parser@0.16.3: + resolution: {integrity: sha512-CGaBseNtunAV2DCpwBXqTKq8+9Tw65XZetMaC0FsMoZuLj0gxNIkbCf2QyKYScVrNOU7/ayfNdVw8ZCSHBiqCg==} + engines: {node: ^14.18.0 || >=16.0.0} + + astro-expressive-code@0.33.5: + resolution: {integrity: sha512-9JAyllueMUN8JTl/h/yTdbKinNmfalEWcV11s3lSf/UJQbAZfWJuy+IlGcArZDI/CmD21GXhFHLqYthpdY33ug==} + peerDependencies: + astro: ^4.0.0-beta || ^3.3.0 + + astro-og-canvas@0.4.2: + resolution: {integrity: sha512-OQsH6Gr2HX9ZRHdVy2OcXVBIPI65WvEtLG/60krnphh8d3ldhuAFunymYaNGcrdSZcYgXkHWejbPt//3qaRidA==} + engines: {node: '>=18.14.1'} + peerDependencies: + astro: ^3.0.0 || ^4.0.0 + + astro@4.5.7: + resolution: {integrity: sha512-Ioeg3TV42dOJvf6GlmykeR3EKZ8+JcnZyJ/X9qDPzVf2OREmtvW0182YCDXQBqwXFRHndZRcHLqinAWjzZYh/A==} + engines: {node: '>=18.14.1', npm: '>=6.14.0'} + hasBin: true + + astrojs-compiler-sync@0.3.5: + resolution: {integrity: sha512-y420rhIIJ2HHDkYeqKArBHSdJNIIGMztLH90KGIX3zjcJyt/cr9Z2wYA8CP5J1w6KE7xqMh0DAkhfjhNDpQb2Q==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@astrojs/compiler': '>=0.27.0' + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axobject-query@4.0.0: + resolution: {integrity: sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==} + + b4a@1.6.6: + resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==} + + babel-plugin-transform-hook-names@1.0.2: + resolution: {integrity: sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==} + peerDependencies: + '@babel/core': ^7.12.10 + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bare-events@2.2.1: + resolution: {integrity: sha512-9GYPpsPFvrWBkelIhOhTWtkeZxVxZOdb3VnFTCzlOo3OjvmTvzLoZFUT8kNFACx0vJej6QPney1Cf9BvzCNE/A==} + + bare-events@2.2.2: + resolution: {integrity: sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==} + + bare-fs@2.3.0: + resolution: {integrity: sha512-TNFqa1B4N99pds2a5NYHR15o0ZpdNKbAeKTE/+G6ED/UeOavv8RY3dr/Fu99HW3zU3pXpo2kDNO8Sjsm2esfOw==} + + bare-os@2.2.1: + resolution: {integrity: sha512-OwPyHgBBMkhC29Hl3O4/YfxW9n7mdTr2+SsO29XBWKKJsbgj3mnorDB80r5TiCQgQstgE5ga1qNYrpes6NvX2w==} + + bare-path@2.1.2: + resolution: {integrity: sha512-o7KSt4prEphWUHa3QUwCxUI00R86VdjiuxmJK0iNVDHYPGo+HsDaVCnqCmPbf/MiW1ok8F4p3m8RTHlWk8K2ig==} + + bare-stream@1.0.0: + resolution: {integrity: sha512-KhNUoDL40iP4gFaLSsoGE479t0jHijfYdIcxRn/XtezA2BaUD0NRf/JGRpsMq6dMNM+SrCrB0YSSo/5wBY4rOQ==} + + base-64@1.0.0: + resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bcp-47-match@2.0.3: + resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==} + + bcp-47-normalize@2.3.0: + resolution: {integrity: sha512-8I/wfzqQvttUFz7HVJgIZ7+dj3vUaIyIxYXaTRP1YWoSDfzt6TUmxaKZeuXR62qBmYr+nvuWINFRl6pZ5DlN4Q==} + + bcp-47@2.1.0: + resolution: {integrity: sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + bl@5.1.0: + resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} + + bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + boxen@7.1.1: + resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==} + engines: {node: '>=14.16'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + + browserslist@4.23.0: + resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + cache-point@2.0.0: + resolution: {integrity: sha512-4gkeHlFpSKgm3vm2gJN5sPqfmijYRFYCQ6tv5cLw0xVmT6r1z1vd4FNnpuOREco3cBs1G709sZ72LdgddKvL5w==} + engines: {node: '>=8'} + + call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@7.0.1: + resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} + engines: {node: '>=14.16'} + + caniuse-lite@1.0.30001599: + resolution: {integrity: sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==} + + canvas-confetti@1.9.2: + resolution: {integrity: sha512-6Xi7aHHzKwxZsem4mCKoqP6YwUG3HamaHHAlz1hTNQPCqXhARFpSXnkC9TWlahHY5CG6hSL5XexNjxK8irVErg==} + + canvaskit-wasm@0.37.2: + resolution: {integrity: sha512-212imazRF98gLOTiU4JAXM7xDvaknI7jaPtAg4ETXGW5rLQs6pomgIvVPUSfoKnQVTdGgzj+B4e+/u0Da20aGg==} + + canvaskit-wasm@0.39.1: + resolution: {integrity: sha512-Gy3lCmhUdKq+8bvDrs9t8+qf7RvcjuQn+we7vTVVyqgOVO1UVfHpsnBxkTZw+R4ApEJ3D5fKySl9TU11hmjl/A==} + + catharsis@0.9.0: + resolution: {integrity: sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==} + engines: {node: '>= 10'} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chai@4.4.1: + resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} + engines: {node: '>=4'} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + ci-info@4.0.0: + resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==} + engines: {node: '>=8'} + + cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + + cli-cursor@4.0.0: + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clsx@2.1.0: + resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==} + engines: {node: '>=6'} + + code-block-writer@11.0.3: + resolution: {integrity: sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==} + + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + + collect-all@1.0.4: + resolution: {integrity: sha512-RKZhRwJtJEP5FWul+gkSMEnaK6H3AGPTTWOiRimCcs+rc/OmQE3Yhy1Q7A7KsdkG3ZXVdZq68Y6ONSdvkeEcKA==} + engines: {node: '>=0.10.0'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + common-ancestor-path@1.0.1: + resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + + css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + + data-view-buffer@1.0.1: + resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.1: + resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.0: + resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + engines: {node: '>= 0.4'} + + debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.0.2: + resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + dedent-js@1.0.1: + resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==} + + deep-eql@4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + engines: {node: '>=6'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + + deterministic-object-hash@2.0.2: + resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} + engines: {node: '>=18'} + + devalue@4.3.2: + resolution: {integrity: sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + + dset@3.1.3: + resolution: {integrity: sha512-20TuZZHCEZ2O71q9/+8BwKwZ0QtD9D8ObhrihJPr+vLLYlSuAU3/zL4cSlgbfeoGHTjCSJBa7NGcrF9/Bx/WJQ==} + engines: {node: '>=4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + editorconfig@0.15.3: + resolution: {integrity: sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==} + hasBin: true + + electron-to-chromium@1.4.711: + resolution: {integrity: sha512-hRg81qzvUEibX2lDxnFlVCHACa+LtrCPIsWAxo161LDYIB3jauf57RGsMZV9mvGwE98yGH06icj3zBEoOkxd/w==} + + emmet@2.4.7: + resolution: {integrity: sha512-O5O5QNqtdlnQM2bmKHtJgyChcrFMgQuulI+WdiOw2NArzprUqqxUW6bgYtKvzKgrsYpuLWalOkdhNP+1jluhCA==} + + emoji-regex@10.3.0: + resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + + entities@2.1.0: + resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + es-abstract@1.22.5: + resolution: {integrity: sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==} + engines: {node: '>= 0.4'} + + es-abstract@1.23.2: + resolution: {integrity: sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.0.18: + resolution: {integrity: sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.4.2: + resolution: {integrity: sha512-7nOqkomXZEaxUDJw21XZNtRk739QvrPSoZoRtbsEfcii00vdzZUh6zh1CQwHhrib8MdEtJfv5rJiGeb4KuV/vw==} + + es-module-lexer@1.5.0: + resolution: {integrity: sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==} + + es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.0.2: + resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + + es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + + esbuild-android-64@0.15.18: + resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + esbuild-android-arm64@0.15.18: + resolution: {integrity: sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + esbuild-darwin-64@0.15.18: + resolution: {integrity: sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + esbuild-darwin-arm64@0.15.18: + resolution: {integrity: sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + esbuild-freebsd-64@0.15.18: + resolution: {integrity: sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + esbuild-freebsd-arm64@0.15.18: + resolution: {integrity: sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + esbuild-linux-32@0.15.18: + resolution: {integrity: sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + esbuild-linux-64@0.15.18: + resolution: {integrity: sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + esbuild-linux-arm64@0.15.18: + resolution: {integrity: sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + esbuild-linux-arm@0.15.18: + resolution: {integrity: sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + esbuild-linux-mips64le@0.15.18: + resolution: {integrity: sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + esbuild-linux-ppc64le@0.15.18: + resolution: {integrity: sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + esbuild-linux-riscv64@0.15.18: + resolution: {integrity: sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + esbuild-linux-s390x@0.15.18: + resolution: {integrity: sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + esbuild-netbsd-64@0.15.18: + resolution: {integrity: sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + esbuild-openbsd-64@0.15.18: + resolution: {integrity: sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + esbuild-sunos-64@0.15.18: + resolution: {integrity: sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + esbuild-windows-32@0.15.18: + resolution: {integrity: sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + esbuild-windows-64@0.15.18: + resolution: {integrity: sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + esbuild-windows-arm64@0.15.18: + resolution: {integrity: sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + esbuild@0.15.18: + resolution: {integrity: sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.19.12: + resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.20.2: + resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + eslint-compat-utils@0.5.0: + resolution: {integrity: sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=6.0.0' + + eslint-plugin-astro@0.33.1: + resolution: {integrity: sha512-wVyxAf8Ulmljv5qJQLgspWe17LR4hLXcksIENtUlEC3W7rleBVEKXS+hIqzBfCbpkBLZpl1tsYes1AGpYHd13w==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-react@7.34.1: + resolution: {integrity: sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-util-attach-comments@3.0.0: + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + expressive-code@0.33.5: + resolution: {integrity: sha512-UPg2jSvZEfXPiCa4MKtMoMQ5Hwiv7In5/LSCa/ukhjzZqPO48iVsCcEBgXWEUmEAQ02P0z00/xFfBmVnUKH+Zw==} + + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + file-set@4.0.2: + resolution: {integrity: sha512-fuxEgzk4L8waGXaAkd8cMr73Pm0FxOVkn8hztzUW7BAHhOGH90viQNXbiOsnecCWmfInqU6YmAMwxRMdKETceQ==} + engines: {node: '>=10'} + + fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + find-yarn-workspace-root2@1.2.16: + resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + + flattie@1.1.1: + resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} + engines: {node: '>=8'} + + for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fs-then-native@2.0.0: + resolution: {integrity: sha512-X712jAOaWXkemQCAmWeg5rOT2i+KOpWz1Z/txk/cW0qlOu2oQ9H61vc5w3X/iyuUEfq/OyaFJ78/cZAQD1/bgA==} + engines: {node: '>=4.0.0'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.2.0: + resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==} + engines: {node: '>=18'} + + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + + get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} + engines: {node: '>= 0.4'} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globalthis@1.0.3: + resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + engines: {node: '>= 0.4'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + gray-matter@4.0.3: + resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} + engines: {node: '>=6.0'} + + has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} + + has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hast-util-from-html@2.0.1: + resolution: {integrity: sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==} + + hast-util-from-parse5@7.1.2: + resolution: {integrity: sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==} + + hast-util-from-parse5@8.0.1: + resolution: {integrity: sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==} + + hast-util-heading-rank@3.0.0: + resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-parse-selector@3.1.1: + resolution: {integrity: sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-raw@7.2.3: + resolution: {integrity: sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==} + + hast-util-raw@9.0.2: + resolution: {integrity: sha512-PldBy71wO9Uq1kyaMch9AHIghtQvIwxBUkv823pKmkTM3oV1JxtsTNYdevMxvUHqcnOAuO65JKU2+0NOxc2ksA==} + + hast-util-to-estree@3.1.0: + resolution: {integrity: sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==} + + hast-util-to-html@8.0.4: + resolution: {integrity: sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==} + + hast-util-to-html@9.0.0: + resolution: {integrity: sha512-IVGhNgg7vANuUA2XKrT6sOIIPgaYZnmLx3l/CCOAK0PtgfoHrZwX7jCSYyFxHTrGmC6S9q8aQQekjp4JPZF+cw==} + + hast-util-to-jsx-runtime@2.3.0: + resolution: {integrity: sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==} + + hast-util-to-parse5@7.1.0: + resolution: {integrity: sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==} + + hast-util-to-parse5@8.0.0: + resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} + + hast-util-to-string@3.0.0: + resolution: {integrity: sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==} + + hast-util-to-text@4.0.0: + resolution: {integrity: sha512-EWiE1FSArNBPUo1cKWtzqgnuRQwEeQbQtnFJRYV1hb1BWDgrAlBU0ExptvZMM/KSA82cDpm2sFGf3Dmc5Mza3w==} + + hast-util-whitespace@2.0.1: + resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@7.2.0: + resolution: {integrity: sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==} + + hastscript@8.0.0: + resolution: {integrity: sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==} + + hastscript@9.0.0: + resolution: {integrity: sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw==} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + html-escaper@3.0.3: + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} + + html-void-elements@2.0.1: + resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + htmlparser2@9.1.0: + resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==} + + http-cache-semantics@4.1.1: + resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + husky@9.0.11: + resolution: {integrity: sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==} + engines: {node: '>=18'} + hasBin: true + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} + + immutable@4.3.5: + resolution: {integrity: sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + import-meta-resolve@4.0.0: + resolution: {integrity: sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + inline-style-parser@0.1.1: + resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} + + inline-style-parser@0.2.3: + resolution: {integrity: sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==} + + internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + engines: {node: '>= 0.4'} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + is-async-function@2.0.0: + resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} + engines: {node: '>= 0.4'} + + is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + + is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.13.1: + resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + + is-data-view@1.0.1: + resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + engines: {node: '>= 0.4'} + + is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + 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 + + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.0.2: + resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + + is-fullwidth-code-point@5.0.0: + resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} + engines: {node: '>=18'} + + is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-network-error@1.1.0: + resolution: {integrity: sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==} + engines: {node: '>=16'} + + is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-reference@3.0.2: + resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + + is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + + is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + engines: {node: '>= 0.4'} + + is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + + is-weakset@2.0.3: + resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} + engines: {node: '>= 0.4'} + + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iterator.prototype@1.1.2: + resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@8.0.3: + resolution: {integrity: sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + js2xmlparser@4.0.2: + resolution: {integrity: sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==} + + jsdoc-api@8.0.0: + resolution: {integrity: sha512-Rnhor0suB1Ds1abjmFkFfKeD+kSMRN9oHMTMZoJVUrmtCGDwXty+sWMA9sa4xbe4UyxuPjhC7tavZ40mDKK6QQ==} + engines: {node: '>=12.17'} + + jsdoc@4.0.2: + resolution: {integrity: sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==} + engines: {node: '>=12.0.0'} + hasBin: true + + jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@2.3.1: + resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} + + jsonc-parser@3.2.1: + resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + klaw@3.0.0: + resolution: {integrity: sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.0.0: + resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==} + engines: {node: '>=14'} + + linkify-it@3.0.3: + resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==} + + lint-staged@15.2.2: + resolution: {integrity: sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==} + engines: {node: '>=18.12.0'} + hasBin: true + + listr2@8.0.1: + resolution: {integrity: sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==} + engines: {node: '>=18.0.0'} + + load-yaml-file@0.2.0: + resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} + engines: {node: '>=6'} + + local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + engines: {node: '>=14'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-symbols@5.1.0: + resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} + engines: {node: '>=12'} + + log-update@6.0.0: + resolution: {integrity: sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==} + engines: {node: '>=18'} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + + lru-cache@4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + magic-string@0.30.5: + resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} + engines: {node: '>=12'} + + magic-string@0.30.8: + resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} + engines: {node: '>=12'} + + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + + markdown-it-anchor@8.6.7: + resolution: {integrity: sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==} + peerDependencies: + '@types/markdown-it': '*' + markdown-it: '*' + + markdown-it@12.3.2: + resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==} + hasBin: true + + markdown-table@3.0.3: + resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} + + marked@4.3.0: + resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} + engines: {node: '>= 12'} + hasBin: true + + mdast-util-definitions@6.0.0: + resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==} + + mdast-util-directive@3.0.0: + resolution: {integrity: sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q==} + + mdast-util-find-and-replace@3.0.1: + resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} + + mdast-util-from-markdown@2.0.0: + resolution: {integrity: sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==} + + mdast-util-gfm-autolink-literal@2.0.0: + resolution: {integrity: sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==} + + mdast-util-gfm-footnote@2.0.0: + resolution: {integrity: sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.0.0: + resolution: {integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==} + + mdast-util-mdx-expression@2.0.0: + resolution: {integrity: sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==} + + mdast-util-mdx-jsx@3.1.2: + resolution: {integrity: sha512-eKMQDeywY2wlHc97k5eD8VC+9ASMjN8ItEZQNGwJ6E0XWKiW/Z0V5/H8pvoXUf+y+Mj0VIgeRRbujBmFn4FTyA==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.1.0: + resolution: {integrity: sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA==} + + mdast-util-to-markdown@2.1.0: + resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdurl@1.0.1: + resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromark-core-commonmark@2.0.0: + resolution: {integrity: sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==} + + micromark-extension-directive@3.0.0: + resolution: {integrity: sha512-61OI07qpQrERc+0wEysLHMvoiO3s2R56x5u7glHq2Yqq6EHbH4dW25G9GfDdGCDYqA21KE6DWgNSzxSwHc2hSg==} + + micromark-extension-gfm-autolink-literal@2.0.0: + resolution: {integrity: sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==} + + micromark-extension-gfm-footnote@2.0.0: + resolution: {integrity: sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==} + + micromark-extension-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==} + + micromark-extension-gfm-table@2.0.0: + resolution: {integrity: sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.0.1: + resolution: {integrity: sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-extension-mdx-expression@3.0.0: + resolution: {integrity: sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==} + + micromark-extension-mdx-jsx@3.0.0: + resolution: {integrity: sha512-uvhhss8OGuzR4/N17L1JwvmJIpPhAd8oByMawEKx6NVdBCbesjH4t+vjEp3ZXft9DwvlKSD07fCeI44/N0Vf2w==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + micromark-factory-destination@2.0.0: + resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==} + + micromark-factory-label@2.0.0: + resolution: {integrity: sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==} + + micromark-factory-mdx-expression@2.0.1: + resolution: {integrity: sha512-F0ccWIUHRLRrYp5TC9ZYXmZo+p2AM13ggbsW4T0b5CRKP8KHVRB8t4pwtBgTxtjRmwrK0Irwm7vs2JOZabHZfg==} + + micromark-factory-space@2.0.0: + resolution: {integrity: sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==} + + micromark-factory-title@2.0.0: + resolution: {integrity: sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==} + + micromark-factory-whitespace@2.0.0: + resolution: {integrity: sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==} + + micromark-util-character@2.1.0: + resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==} + + micromark-util-chunked@2.0.0: + resolution: {integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==} + + micromark-util-classify-character@2.0.0: + resolution: {integrity: sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==} + + micromark-util-combine-extensions@2.0.0: + resolution: {integrity: sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==} + + micromark-util-decode-numeric-character-reference@2.0.1: + resolution: {integrity: sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==} + + micromark-util-decode-string@2.0.0: + resolution: {integrity: sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==} + + micromark-util-encode@2.0.0: + resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} + + micromark-util-events-to-acorn@2.0.2: + resolution: {integrity: sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA==} + + micromark-util-html-tag-name@2.0.0: + resolution: {integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==} + + micromark-util-normalize-identifier@2.0.0: + resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==} + + micromark-util-resolve-all@2.0.0: + resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==} + + micromark-util-sanitize-uri@2.0.0: + resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} + + micromark-util-subtokenize@2.0.0: + resolution: {integrity: sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==} + + micromark-util-symbol@2.0.0: + resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} + + micromark-util-types@2.0.0: + resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} + + micromark@4.0.0: + resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} + + micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + mkdirp2@1.0.5: + resolution: {integrity: sha512-xOE9xbICroUDmG1ye2h4bZ8WBie9EGmACaco8K8cx6RlkJJrxGIqjGqztAI+NMhexXBcdGbSEzI6N3EJPevxZw==} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + mlly@1.6.1: + resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanostores@0.10.0: + resolution: {integrity: sha512-Poy5+9wFXOD0jAstn4kv9n686U2BFw48z/W8lms8cS8lcbRz7BU20JxZ3e/kkKQVfRrkm4yLWCUA6GQINdvJCQ==} + engines: {node: ^18.0.0 || >=20.0.0} + + napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + nlcst-to-string@3.1.1: + resolution: {integrity: sha512-63mVyqaqt0cmn2VcI2aH6kxe1rLAmSROqHMA0i4qqg1tidkfExgpb0FGMikMCn86mw5dFtBtEANfmSSK7TjNHw==} + + nlcst-to-string@4.0.0: + resolution: {integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==} + + node-abi@3.56.0: + resolution: {integrity: sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q==} + engines: {node: '>=10'} + + node-addon-api@6.1.0: + resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + node-html-parser@6.1.12: + resolution: {integrity: sha512-/bT/Ncmv+fbMGX96XG9g05vFt43m/+SYKIs9oAemQVYyVcZmDAI2Xq/SbNcpOA35eF0Zk2av3Ksf+Xk8Vt8abA==} + + node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.1: + resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object-to-spawn-args@2.0.1: + resolution: {integrity: sha512-6FuKFQ39cOID+BMZ3QaphcC8Y4cw6LXBLyIgPU+OhIYwviJamPAn+4mITapnSBQrejB+NNp+FMskhD8Cq+Ys3w==} + engines: {node: '>=8.0.0'} + + object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + engines: {node: '>= 0.4'} + + object.entries@1.1.8: + resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.hasown@1.1.3: + resolution: {integrity: sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==} + + object.values@1.2.0: + resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} + engines: {node: '>= 0.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + optionator@0.9.3: + resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} + engines: {node: '>= 0.8.0'} + + ora@7.0.1: + resolution: {integrity: sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==} + engines: {node: '>=16'} + + organize-imports-cli@0.10.0: + resolution: {integrity: sha512-cVyNEeiDxX/zA6gdK1QS2rr3TK1VymIkT0LagnAk4f6eE0IC0bo3BeUkMzm3q3GnCJzYC+6lfuMpBE0Cequ7Vg==} + hasBin: true + + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-queue@6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} + + p-queue@8.0.1: + resolution: {integrity: sha512-NXzu9aQJTAzbBqOt2hwsR63ea7yvxJc0PwN/zobNAudYfb1B7R08SzB4TsLeSbUCuG467NhnoT0oO6w1qRO+BA==} + engines: {node: '>=18'} + + p-retry@6.2.0: + resolution: {integrity: sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==} + engines: {node: '>=16.17'} + + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + + p-timeout@6.1.2: + resolution: {integrity: sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==} + engines: {node: '>=14.16'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-entities@4.0.1: + resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==} + + parse-latin@5.0.1: + resolution: {integrity: sha512-b/K8ExXaWC9t34kKeDV8kGXBkXZ1HCSAZRYE7HR14eA1GlXX5L8iWhs8USJNhQU9q5ci413jCKF0gOyovvyRBg==} + + parse-latin@7.0.0: + resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} + + parse-numeric-range@1.3.0: + resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==} + + parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + + parse5@7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-to-regexp@6.2.1: + resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + + periscopic@3.1.0: + resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + + picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + pkg-types@1.0.3: + resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + + plantuml-encoder@1.4.0: + resolution: {integrity: sha512-sxMwpDw/ySY1WB2CE3+IdMuEcWibJ72DDOsXLkSmEaSzwEUaYBT6DWgOfBiHGCux4q433X6+OEFWjlVqp7gL6g==} + + possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + + postcss-nested@6.0.1: + resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.0.16: + resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==} + engines: {node: '>=4'} + + postcss@8.4.37: + resolution: {integrity: sha512-7iB/v/r7Woof0glKLH8b1SPHrsX7uhdO+Geb41QpF/+mWZHU3uxxSlN+UXGVit1PawOYDToO+AbZzhBzWRDwbQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + engines: {node: ^10 || ^12 || >=14} + + preact-render-to-string@6.3.1: + resolution: {integrity: sha512-NQ28WrjLtWY6lKDlTxnFpKHZdpjfF+oE6V4tZ0rTrunHrtZp6Dm0oFrcJalt/5PNeqJz4j1DuZDS0Y6rCBoqDA==} + peerDependencies: + preact: '>=10' + + preact-ssr-prepass@1.2.1: + resolution: {integrity: sha512-bLgbUfy8nL+PZghAPpyk9MF+cmXjdwEnxYPaJBmwbzFQqzIz8dQVBqjwB60RqZ9So/vIf6BRfHCiwFGuMCyfbQ==} + peerDependencies: + preact: '>=10 || ^10.0.0-beta.0 || ^10.0.0-alpha.0' + + preact@10.19.7: + resolution: {integrity: sha512-IJOW6cQN1fwfC17HfNOqUtAGyB8wAYshuC+jG1JiL/1+sC4yVyuA3IcF0N9vdodMJjW/lbuEF5qFsJqGNcbHbw==} + + prebuild-install@7.1.2: + resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} + engines: {node: '>=10'} + hasBin: true + + preferred-pm@3.1.3: + resolution: {integrity: sha512-MkXsENfftWSRpzCzImcp4FRsCc3y1opwB73CfCNWyzMqArju2CrlMHlqB7VexKiPEOjGMbttv1r9fSCn5S610w==} + engines: {node: '>=10'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-plugin-astro@0.13.0: + resolution: {integrity: sha512-5HrJNnPmZqTUNoA97zn4gNQv9BgVhv+et03314WpQ9H9N8m2L9OSV798olwmG2YLXPl1iSstlJCR1zB3x5xG4g==} + engines: {node: ^14.15.0 || >=16.0.0} + + prettier@3.2.5: + resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + pretty-format@3.8.0: + resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + + prismjs@1.29.0: + resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} + engines: {node: '>=6'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + property-information@6.4.1: + resolution: {integrity: sha512-OHYtXfu5aI2sS2LWFSN5rgJjrQ4pCy8i1jubJLe2QvMF8JJ++HXTUIVWFLfXJoaOfvYYjk2SN8J2wFUWIGXT4w==} + + pseudomap@1.0.2: + resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + + pump@3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + queue-tick@1.0.1: + resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + reflect.getprototypeof@1.0.6: + resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} + engines: {node: '>= 0.4'} + + rehype-autolink-headings@7.1.0: + resolution: {integrity: sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==} + + rehype-parse@9.0.0: + resolution: {integrity: sha512-WG7nfvmWWkCR++KEkZevZb/uw41E8TsH4DsY9UxsTbIXCVGbAs4S+r8FrQ+OtH5EEQAs+5UxKC42VinkmpA1Yw==} + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + rehype-slug@6.0.0: + resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==} + + rehype-stringify@10.0.0: + resolution: {integrity: sha512-1TX1i048LooI9QoecrXy7nGFFbFSufxVRAfc6Y9YMRAi56l+oB0zP51mLSV312uRuvVLPV1opSlJmslozR1XHQ==} + + rehype@13.0.1: + resolution: {integrity: sha512-AcSLS2mItY+0fYu9xKxOu1LhUZeBZZBx8//5HKzF+0XP+eP8+6a5MXn2+DW2kfXR6Dtp1FEXMVrjyKAcvcU8vg==} + + remark-directive@3.0.0: + resolution: {integrity: sha512-l1UyWJ6Eg1VPU7Hm/9tt0zKtReJQNOA4+iDMAxTyZNWnJnFlbS/7zhiel/rogTLQ2vMYwDzSJa4BiVNqGlqIMA==} + + remark-expressive-code@0.33.5: + resolution: {integrity: sha512-E4CZq3AuUXLu6or0AaDKkgsHYqmnm4ZL8/+1/8YgwtKcogHwTMRJfQtxkZpth90QQoNUpsapvm5x5n3Np2OC9w==} + + remark-gfm@4.0.0: + resolution: {integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==} + + remark-mdx@3.0.1: + resolution: {integrity: sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.0: + resolution: {integrity: sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==} + + remark-smartypants@2.1.0: + resolution: {integrity: sha512-qoF6Vz3BjU2tP6OfZqHOvCU0ACmu/6jhGaINSQRI9mM7wCxNQTKB3JUAN4SVoN2ybElEDTxBIABRep7e569iJw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + remark-smartypants@3.0.1: + resolution: {integrity: sha512-qyshfCl2eLO0i0558e79ZJsfojC5wjnYLByjt0FmjJQN6aYwcRxpoj784LZJSoWCdnA2ubh5rLNGb8Uur/wDng==} + engines: {node: '>=16.0.0'} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + remark@15.0.1: + resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==} + + remove-markdown@0.5.0: + resolution: {integrity: sha512-x917M80K97K5IN1L8lUvFehsfhR8cYjGQ/yAMRI9E7JIKivtl5Emo5iD13DhMr+VojzMCiYk8V2byNPwT/oapg==} + + request-light@0.7.0: + resolution: {integrity: sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + requizzle@0.2.4: + resolution: {integrity: sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + + restore-cursor@4.0.0: + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + retext-latin@3.1.0: + resolution: {integrity: sha512-5MrD1tuebzO8ppsja5eEu+ZbBeUNCjoEarn70tkXOS7Bdsdf6tNahsv2bY0Z8VooFF6cw7/6S+d3yI/TMlMVVQ==} + + retext-latin@4.0.0: + resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==} + + retext-smartypants@5.2.0: + resolution: {integrity: sha512-Do8oM+SsjrbzT2UNIKgheP0hgUQTDDQYyZaIY3kfq0pdFzoPk+ZClYJ+OERNXveog4xf1pZL4PfRxNoVL7a/jw==} + + retext-smartypants@6.1.0: + resolution: {integrity: sha512-LDPXg95346bqFZnDMHo0S7Rq5p64+B+N8Vz733+wPMDtwb9rCOs9LIdIEhrUOU+TAywX9St+ocQWJt8wrzivcQ==} + + retext-stringify@3.1.0: + resolution: {integrity: sha512-767TLOaoXFXyOnjx/EggXlb37ZD2u4P1n0GJqVdpipqACsQP+20W+BNpMYrlJkq7hxffnFk+jc6mAK9qrbuB8w==} + + retext-stringify@4.0.0: + resolution: {integrity: sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==} + + retext@8.1.0: + resolution: {integrity: sha512-N9/Kq7YTn6ZpzfiGW45WfEGJqFf1IM1q8OsRa1CGzIebCJBNCANDRmOrholiDRGKo/We7ofKR4SEvcGAWEMD3Q==} + + retext@9.0.0: + resolution: {integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==} + + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.3.1: + resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + + rollup@4.13.0: + resolution: {integrity: sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + s.color@0.0.15: + resolution: {integrity: sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==} + + safe-array-concat@1.1.2: + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + engines: {node: '>= 0.4'} + + sass-formatter@0.7.9: + resolution: {integrity: sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==} + + sass@1.72.0: + resolution: {integrity: sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==} + engines: {node: '>=14.0.0'} + hasBin: true + + sax@1.3.0: + resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} + + search-insights@2.13.0: + resolution: {integrity: sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==} + + section-matter@1.0.0: + resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} + engines: {node: '>=4'} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.6.0: + resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + sharp@0.32.6: + resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==} + engines: {node: '>=14.15.0'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shiki@1.2.0: + resolution: {integrity: sha512-xLhiTMOIUXCv5DqJ4I70GgQCtdlzsTqFLZWcMHHG3TAieBUbvEGthdrlPDlX4mL/Wszx9C6rEcxU6kMlg4YlxA==} + + shiki@1.2.1: + resolution: {integrity: sha512-u+XW6o0vCkUNlneZb914dLO+AayEIwK5tI62WeS//R5HIXBFiYaj/Hc5xcq27Yh83Grr4JbNtUBV8W6zyK4hWg==} + + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + sigmund@1.0.1: + resolution: {integrity: sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + simple-git@3.23.0: + resolution: {integrity: sha512-P9ggTW8vb/21CAL/AmnACAhqBDfnqSSZVpV7WuFtsFR9HLunf5IqQvk+OXAQTfkcZep8pKnt3DV3o7w3TegEkQ==} + + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + sitemap@7.1.1: + resolution: {integrity: sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==} + engines: {node: '>=12.0.0', npm: '>=5.6.0'} + hasBin: true + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + + slice-ansi@7.1.0: + resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} + engines: {node: '>=18'} + + source-map-js@1.1.0: + resolution: {integrity: sha512-9vC2SfsJzlej6MAaMPLu8HiBSHGdRAJ9hVFYN1ibZoNkeanmDmLUcIrj6G9DGL7XMJ54AKg/G75akXl1/izTOw==} + engines: {node: '>=0.10.0'} + + source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stack-trace@1.0.0-pre2: + resolution: {integrity: sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==} + engines: {node: '>=16'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + + stdin-discarder@0.1.0: + resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + stream-connect@1.0.2: + resolution: {integrity: sha512-68Kl+79cE0RGKemKkhxTSg8+6AGrqBt+cbZAXevg2iJ6Y3zX4JhA/sZeGzLpxW9cXhmqAcE7KnJCisUmIUfnFQ==} + engines: {node: '>=0.10.0'} + + stream-replace-string@2.0.0: + resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==} + + stream-via@1.0.4: + resolution: {integrity: sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==} + engines: {node: '>=0.10.0'} + + streamx@2.16.1: + resolution: {integrity: sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string-width@6.1.0: + resolution: {integrity: sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==} + engines: {node: '>=16'} + + string-width@7.1.0: + resolution: {integrity: sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==} + engines: {node: '>=18'} + + string.prototype.matchall@4.0.10: + resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} + + string.prototype.trim@1.2.9: + resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.8: + resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} + + string.prototype.trimstart@1.0.7: + resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + stringify-entities@4.0.3: + resolution: {integrity: sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom-string@1.0.0: + resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} + engines: {node: '>=0.10.0'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-literal@2.0.0: + resolution: {integrity: sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==} + + style-to-object@0.4.4: + resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} + + style-to-object@1.0.6: + resolution: {integrity: sha512-khxq+Qm3xEyZfKd/y9L3oIWQimxuc4STrQKtQn8aSDRHb8mFgpukgX1hdzfrMEW6JCjyJ8p89x+IUMVnCBI1PA==} + + suf-log@2.5.3: + resolution: {integrity: sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + synckit@0.9.0: + resolution: {integrity: sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==} + engines: {node: ^14.18.0 || >=16.0.0} + + tar-fs@2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + + tar-fs@3.0.5: + resolution: {integrity: sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + + temp-path@1.0.0: + resolution: {integrity: sha512-TvmyH7kC6ZVTYkqCODjJIbgvu0FKiwQpZ4D1aknE7xpcDf/qEOB8KZEK5ef2pfbVoiBhNWs3yx4y+ESMtNYmlg==} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + tinybench@2.6.0: + resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==} + + tinypool@0.8.2: + resolution: {integrity: sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==} + engines: {node: '>=14.0.0'} + + tinyspy@2.2.1: + resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + engines: {node: '>=14.0.0'} + + to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + ts-api-utils@1.3.0: + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-morph@15.1.0: + resolution: {integrity: sha512-RBsGE2sDzUXFTnv8Ba22QfeuKbgvAGJFuTN7HfmIRUkgT/NaVLfDM/8OFm2NlFkGlWEXdpW5OaFIp1jvqdDuOg==} + + tsconfck@3.0.3: + resolution: {integrity: sha512-4t0noZX9t6GcPTfBAbIbbIU4pfpCwh0ueq3S4O/5qXI1VwK1outmxhe9dOiEWqMz3MW2LKgDTpqWV+37IWuVbA==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + tsconfig@7.0.0: + resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==} + + tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + + tsm@2.3.0: + resolution: {integrity: sha512-++0HFnmmR+gMpDtKTnW3XJ4yv9kVGi20n+NfyQWB9qwJvTaIWY9kBmzek2YUQK5APTQ/1DTrXmm4QtFPmW9Rzw==} + engines: {node: '>=12'} + hasBin: true + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + tunnel@0.0.6: + resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} + engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + + type-fest@3.13.1: + resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} + engines: {node: '>=14.16'} + + typed-array-buffer@1.0.2: + resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.1: + resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.2: + resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.5: + resolution: {integrity: sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==} + engines: {node: '>= 0.4'} + + typesafe-path@0.2.2: + resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} + + typescript-auto-import-cache@0.3.2: + resolution: {integrity: sha512-+laqe5SFL1vN62FPOOJSUDTZxtgsoOXjneYOXIpx5rQ4UMiN89NAtJLpqLqyebv9fgQ/IMeeTX+mQyRnwvJzvg==} + + typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + engines: {node: '>=14.17'} + hasBin: true + + typical@2.6.1: + resolution: {integrity: sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==} + + uc.micro@1.0.6: + resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} + + ufo@1.5.2: + resolution: {integrity: sha512-eiutMaL0J2MKdhcOM1tUy13pIrYnyR87fEd8STJQFrrAwImwvlXkxlZEjaKah8r2viPohld08lt73QfLG1NxMg==} + + unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + + underscore@1.13.6: + resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==} + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + undici@5.28.4: + resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} + engines: {node: '>=14.0'} + + unherit@3.0.1: + resolution: {integrity: sha512-akOOQ/Yln8a2sgcLj4U0Jmx0R5jpIg2IUyRrWOzmEbjBtGzBdHtSeFKgoEcoH4KYIG/Pb8GQ/BwtYm0GCq1Sqg==} + + unified@10.1.2: + resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} + + unified@11.0.4: + resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==} + + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-is@4.1.0: + resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} + + unist-util-is@5.2.1: + resolution: {integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==} + + unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + + unist-util-modify-children@3.1.1: + resolution: {integrity: sha512-yXi4Lm+TG5VG+qvokP6tpnk+r1EPwyYL04JWDxLvgvPV40jANh7nm3udk65OOWquvbMDe+PL9+LmkxDpTv/7BA==} + + unist-util-modify-children@4.0.0: + resolution: {integrity: sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-position@4.0.4: + resolution: {integrity: sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-remove@4.0.0: + resolution: {integrity: sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==} + + unist-util-stringify-position@3.0.3: + resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-children@2.0.2: + resolution: {integrity: sha512-+LWpMFqyUwLGpsQxpumsQ9o9DG2VGLFrpz+rpVXYIEdPy57GSy5HioC0g3bg/8WP9oCLlapQtklOzQ8uLS496Q==} + + unist-util-visit-children@3.0.0: + resolution: {integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==} + + unist-util-visit-parents@3.1.1: + resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} + + unist-util-visit-parents@5.1.3: + resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==} + + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} -packages: + unist-util-visit@2.0.3: + resolution: {integrity: sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==} - /@11ty/eleventy-fetch@4.0.1: - resolution: {integrity: sha512-yIiLM5ziBmg86i4TlXpBdcIygJHvh/GgPJyAiFOckO9H4y9cQDM8eIcJCUQ4Mum0NEVui/OjhEut2R08xw0vlQ==} - engines: {node: '>=14'} + unist-util-visit@4.1.2: + resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + unist-util-walker@1.0.0: + resolution: {integrity: sha512-XxadVB7qdSH6LBwhyHozj1VltpnK9m3/Zt/E/WFLaEt9eRQ0RkbsUb0lP9e1anQCEOXxf4X3NYtZQSpzqzTptw==} + + update-browserslist-db@1.0.13: + resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + vfile-location@4.1.0: + resolution: {integrity: sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==} + + vfile-location@5.0.2: + resolution: {integrity: sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==} + + vfile-message@3.1.4: + resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} + + vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + + vfile@5.3.7: + resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} + + vfile@6.0.1: + resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} + + vite-node@1.4.0: + resolution: {integrity: sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.2.8: + resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitefu@0.2.5: + resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + vite: + optional: true + + vitest@1.4.0: + resolution: {integrity: sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 1.4.0 + '@vitest/ui': 1.4.0 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + volar-service-css@0.0.34: + resolution: {integrity: sha512-C7ua0j80ZD7bsgALAz/cA1bykPehoIa5n+3+Ccr+YLpj0fypqw9iLUmGLX11CqzqNCO2XFGe/1eXB/c+SWrF/g==} + peerDependencies: + '@volar/language-service': ~2.1.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-emmet@0.0.34: + resolution: {integrity: sha512-ubQvMCmHPp8Ic82LMPkgrp9ot+u2p/RDd0RyT0EykRkZpWsagHUF5HWkVheLfiMyx2rFuWx/+7qZPOgypx6h6g==} + peerDependencies: + '@volar/language-service': ~2.1.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-html@0.0.34: + resolution: {integrity: sha512-kMEneea1tQbiRcyKavqdrSVt8zV06t+0/3pGkjO3gV6sikXTNShIDkdtB4Tq9vE2cQdM50TuS7utVV7iysUxHw==} + peerDependencies: + '@volar/language-service': ~2.1.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-prettier@0.0.34: + resolution: {integrity: sha512-BNfJ8FwfPi1Wm/JkuzNjraOLdtKieGksNT/bDyquygVawv1QUzO2HB1hiMKfZGdcSFG5ZL9R0j7bBfRTfXA2gg==} + peerDependencies: + '@volar/language-service': ~2.1.0 + prettier: ^2.2 || ^3.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + prettier: + optional: true + + volar-service-typescript-twoslash-queries@0.0.34: + resolution: {integrity: sha512-XAY2YtWKUp6ht89gxt3L5Dr46LU45d/VlBkj1KXUwNlinpoWiGN4Nm3B6DRF3VoBThAnQgm4c7WD0S+5yTzh+w==} + peerDependencies: + '@volar/language-service': ~2.1.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-typescript@0.0.34: + resolution: {integrity: sha512-NbAry0w8ZXFgGsflvMwmPDCzgJGx3C+eYxFEbldaumkpTAJiywECWiUbPIOfmEHgpOllUKSnhwtLlWFK4YnfQg==} + peerDependencies: + '@volar/language-service': ~2.1.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + vscode-css-languageservice@6.2.14: + resolution: {integrity: sha512-5UPQ9Y1sUTnuMyaMBpO7LrBkqjhEJb5eAwdUlDp+Uez8lry+Tspnk3+3p2qWS4LlNsr4p3v9WkZxUf1ltgFpgw==} + + vscode-html-languageservice@5.2.0: + resolution: {integrity: sha512-cdNMhyw57/SQzgUUGSIMQ66jikqEN6nBNyhx5YuOyj9310+eY9zw8Q0cXpiKzDX8aHYFewQEXRnigl06j/TVwQ==} + + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.11: + resolution: {integrity: sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + + vscode-nls@5.2.0: + resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==} + + vscode-uri@2.1.2: + resolution: {integrity: sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==} + + vscode-uri@3.0.8: + resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + + walk-back@5.1.0: + resolution: {integrity: sha512-Uhxps5yZcVNbLEAnb+xaEEMdgTXl9qAQDzKYejG2AZ7qPwRQ81lozY9ECDbjLPNWm7YsO1IK5rsP1KoQzXAcGA==} + engines: {node: '>=12.17'} + + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + + which-builtin-type@1.1.3: + resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-pm-runs@1.1.0: + resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} + engines: {node: '>=4'} + + which-pm@2.0.0: + resolution: {integrity: sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==} + engines: {node: '>=8.15'} + + which-pm@2.1.1: + resolution: {integrity: sha512-xzzxNw2wMaoCWXiGE8IJ9wuPMU+EYhFksjHxrRT8kMT5SnocBPRg69YAMtyV4D12fP582RA+k3P8H9J5EMdIxQ==} + engines: {node: '>=8.15'} + + which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + + widest-line@4.0.1: + resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} + engines: {node: '>=12'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrap-ansi@9.0.0: + resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xmlcreate@2.0.4: + resolution: {integrity: sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@2.1.2: + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yaml@2.3.4: + resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} + engines: {node: '>= 14'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yocto-queue@1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + + zod-to-json-schema@3.22.4: + resolution: {integrity: sha512-2Ed5dJ+n/O3cU383xSY28cuVi0BCQhF8nYqWU5paEpl7fVdqdAmiLdqLyfblbNdfOFwFfi/mqU4O1pwc60iBhQ==} + peerDependencies: + zod: ^3.22.4 + + zod@3.22.4: + resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@11ty/eleventy-fetch@4.0.1': dependencies: debug: 4.3.4 flat-cache: 3.2.0 @@ -258,274 +4384,187 @@ packages: transitivePeerDependencies: - encoding - supports-color - dev: true - /@aashutoshrathi/word-wrap@1.2.6: - resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} - engines: {node: '>=0.10.0'} - dev: true + '@aashutoshrathi/word-wrap@1.2.6': {} - /@actions/core@1.10.1: - resolution: {integrity: sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==} + '@actions/core@1.10.1': dependencies: '@actions/http-client': 2.2.1 uuid: 8.3.2 - dev: true - /@actions/http-client@2.2.1: - resolution: {integrity: sha512-KhC/cZsq7f8I4LfZSJKgCvEwfkE8o1538VoBeoGzokVLLnbFDEAdFD3UhoMklxo2un9NJVBdANOresx7vTHlHw==} + '@actions/http-client@2.2.1': dependencies: tunnel: 0.0.6 undici: 5.28.4 - dev: true - /@akebifiky/remark-simple-plantuml@1.0.2: - resolution: {integrity: sha512-y5rWgQvU+DMpLKx1KlXCsgUeqVooqQm1S3hePLF9iecZy6YhKRybznFdvAvoAoiV2GoGhObQDHnneAl93llIcg==} + '@akebifiky/remark-simple-plantuml@1.0.2': dependencies: plantuml-encoder: 1.4.0 unist-util-visit: 2.0.3 - dev: false - /@algolia/autocomplete-core@1.9.3(@algolia/client-search@4.23.2)(algoliasearch@4.22.1)(search-insights@2.13.0): - resolution: {integrity: sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==} + '@algolia/autocomplete-core@1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.22.1)(search-insights@2.13.0)': dependencies: - '@algolia/autocomplete-plugin-algolia-insights': 1.9.3(@algolia/client-search@4.23.2)(algoliasearch@4.22.1)(search-insights@2.13.0) - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.23.2)(algoliasearch@4.22.1) + '@algolia/autocomplete-plugin-algolia-insights': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.22.1)(search-insights@2.13.0) + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.22.1) transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - search-insights - dev: true - /@algolia/autocomplete-plugin-algolia-insights@1.9.3(@algolia/client-search@4.23.2)(algoliasearch@4.22.1)(search-insights@2.13.0): - resolution: {integrity: sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==} - peerDependencies: - search-insights: '>= 1 < 3' + '@algolia/autocomplete-plugin-algolia-insights@1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.22.1)(search-insights@2.13.0)': dependencies: - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.23.2)(algoliasearch@4.22.1) + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.22.1) search-insights: 2.13.0 transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - dev: true - /@algolia/autocomplete-preset-algolia@1.9.3(@algolia/client-search@4.23.2)(algoliasearch@4.22.1): - resolution: {integrity: sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==} - peerDependencies: - '@algolia/client-search': '>= 4.9.1 < 6' - algoliasearch: '>= 4.9.1 < 6' + '@algolia/autocomplete-preset-algolia@1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.22.1)': dependencies: - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.23.2)(algoliasearch@4.22.1) - '@algolia/client-search': 4.23.2 + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.22.1) + '@algolia/client-search': 4.23.3 algoliasearch: 4.22.1 - dev: true - /@algolia/autocomplete-shared@1.9.3(@algolia/client-search@4.23.2)(algoliasearch@4.22.1): - resolution: {integrity: sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==} - peerDependencies: - '@algolia/client-search': '>= 4.9.1 < 6' - algoliasearch: '>= 4.9.1 < 6' + '@algolia/autocomplete-shared@1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.22.1)': dependencies: - '@algolia/client-search': 4.23.2 + '@algolia/client-search': 4.23.3 algoliasearch: 4.22.1 - dev: true - /@algolia/cache-browser-local-storage@4.22.1: - resolution: {integrity: sha512-Sw6IAmOCvvP6QNgY9j+Hv09mvkvEIDKjYW8ow0UDDAxSXy664RBNQk3i/0nt7gvceOJ6jGmOTimaZoY1THmU7g==} + '@algolia/cache-browser-local-storage@4.22.1': dependencies: '@algolia/cache-common': 4.22.1 - dev: true - /@algolia/cache-common@4.22.1: - resolution: {integrity: sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA==} - dev: true + '@algolia/cache-common@4.22.1': {} - /@algolia/cache-common@4.23.2: - resolution: {integrity: sha512-OUK/6mqr6CQWxzl/QY0/mwhlGvS6fMtvEPyn/7AHUx96NjqDA4X4+Ju7aXFQKh+m3jW9VPB0B9xvEQgyAnRPNw==} - dev: true + '@algolia/cache-common@4.23.3': {} - /@algolia/cache-in-memory@4.22.1: - resolution: {integrity: sha512-ve+6Ac2LhwpufuWavM/aHjLoNz/Z/sYSgNIXsinGofWOysPilQZPUetqLj8vbvi+DHZZaYSEP9H5SRVXnpsNNw==} + '@algolia/cache-in-memory@4.22.1': dependencies: '@algolia/cache-common': 4.22.1 - dev: true - /@algolia/client-account@4.22.1: - resolution: {integrity: sha512-k8m+oegM2zlns/TwZyi4YgCtyToackkOpE+xCaKCYfBfDtdGOaVZCM5YvGPtK+HGaJMIN/DoTL8asbM3NzHonw==} + '@algolia/client-account@4.22.1': dependencies: '@algolia/client-common': 4.22.1 '@algolia/client-search': 4.22.1 '@algolia/transporter': 4.22.1 - dev: true - /@algolia/client-analytics@4.22.1: - resolution: {integrity: sha512-1ssi9pyxyQNN4a7Ji9R50nSdISIumMFDwKNuwZipB6TkauJ8J7ha/uO60sPJFqQyqvvI+px7RSNRQT3Zrvzieg==} + '@algolia/client-analytics@4.22.1': dependencies: '@algolia/client-common': 4.22.1 '@algolia/client-search': 4.22.1 '@algolia/requester-common': 4.22.1 '@algolia/transporter': 4.22.1 - dev: true - /@algolia/client-common@4.22.1: - resolution: {integrity: sha512-IvaL5v9mZtm4k4QHbBGDmU3wa/mKokmqNBqPj0K7lcR8ZDKzUorhcGp/u8PkPC/e0zoHSTvRh7TRkGX3Lm7iOQ==} + '@algolia/client-common@4.22.1': dependencies: '@algolia/requester-common': 4.22.1 '@algolia/transporter': 4.22.1 - dev: true - /@algolia/client-common@4.23.2: - resolution: {integrity: sha512-Q2K1FRJBern8kIfZ0EqPvUr3V29ICxCm/q42zInV+VJRjldAD9oTsMGwqUQ26GFMdFYmqkEfCbY4VGAiQhh22g==} + '@algolia/client-common@4.23.3': dependencies: - '@algolia/requester-common': 4.23.2 - '@algolia/transporter': 4.23.2 - dev: true + '@algolia/requester-common': 4.23.3 + '@algolia/transporter': 4.23.3 - /@algolia/client-personalization@4.22.1: - resolution: {integrity: sha512-sl+/klQJ93+4yaqZ7ezOttMQ/nczly/3GmgZXJ1xmoewP5jmdP/X/nV5U7EHHH3hCUEHeN7X1nsIhGPVt9E1cQ==} + '@algolia/client-personalization@4.22.1': dependencies: '@algolia/client-common': 4.22.1 '@algolia/requester-common': 4.22.1 '@algolia/transporter': 4.22.1 - dev: true - /@algolia/client-search@4.22.1: - resolution: {integrity: sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA==} + '@algolia/client-search@4.22.1': dependencies: '@algolia/client-common': 4.22.1 '@algolia/requester-common': 4.22.1 '@algolia/transporter': 4.22.1 - dev: true - /@algolia/client-search@4.23.2: - resolution: {integrity: sha512-CxSB29OVGSE7l/iyoHvamMonzq7Ev8lnk/OkzleODZ1iBcCs3JC/XgTIKzN/4RSTrJ9QybsnlrN/bYCGufo7qw==} + '@algolia/client-search@4.23.3': dependencies: - '@algolia/client-common': 4.23.2 - '@algolia/requester-common': 4.23.2 - '@algolia/transporter': 4.23.2 - dev: true + '@algolia/client-common': 4.23.3 + '@algolia/requester-common': 4.23.3 + '@algolia/transporter': 4.23.3 - /@algolia/logger-common@4.22.1: - resolution: {integrity: sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg==} - dev: true + '@algolia/logger-common@4.22.1': {} - /@algolia/logger-common@4.23.2: - resolution: {integrity: sha512-jGM49Q7626cXZ7qRAWXn0jDlzvoA1FvN4rKTi1g0hxKsTTSReyYk0i1ADWjChDPl3Q+nSDhJuosM2bBUAay7xw==} - dev: true + '@algolia/logger-common@4.23.3': {} - /@algolia/logger-console@4.22.1: - resolution: {integrity: sha512-O99rcqpVPKN1RlpgD6H3khUWylU24OXlzkavUAMy6QZd1776QAcauE3oP8CmD43nbaTjBexZj2nGsBH9Tc0FVA==} + '@algolia/logger-console@4.22.1': dependencies: '@algolia/logger-common': 4.22.1 - dev: true - /@algolia/requester-browser-xhr@4.22.1: - resolution: {integrity: sha512-dtQGYIg6MteqT1Uay3J/0NDqD+UciHy3QgRbk7bNddOJu+p3hzjTRYESqEnoX/DpEkaNYdRHUKNylsqMpgwaEw==} + '@algolia/requester-browser-xhr@4.22.1': dependencies: '@algolia/requester-common': 4.22.1 - dev: true - /@algolia/requester-common@4.22.1: - resolution: {integrity: sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg==} - dev: true + '@algolia/requester-common@4.22.1': {} - /@algolia/requester-common@4.23.2: - resolution: {integrity: sha512-3EfpBS0Hri0lGDB5H/BocLt7Vkop0bTTLVUBB844HH6tVycwShmsV6bDR7yXbQvFP1uNpgePRD3cdBCjeHmk6Q==} - dev: true + '@algolia/requester-common@4.23.3': {} - /@algolia/requester-node-http@4.22.1: - resolution: {integrity: sha512-JfmZ3MVFQkAU+zug8H3s8rZ6h0ahHZL/SpMaSasTCGYR5EEJsCc8SI5UZ6raPN2tjxa5bxS13BRpGSBUens7EA==} + '@algolia/requester-node-http@4.22.1': dependencies: '@algolia/requester-common': 4.22.1 - dev: true - /@algolia/transporter@4.22.1: - resolution: {integrity: sha512-kzWgc2c9IdxMa3YqA6TN0NW5VrKYYW/BELIn7vnLyn+U/RFdZ4lxxt9/8yq3DKV5snvoDzzO4ClyejZRdV3lMQ==} + '@algolia/transporter@4.22.1': dependencies: '@algolia/cache-common': 4.22.1 '@algolia/logger-common': 4.22.1 '@algolia/requester-common': 4.22.1 - dev: true - /@algolia/transporter@4.23.2: - resolution: {integrity: sha512-GY3aGKBy+8AK4vZh8sfkatDciDVKad5rTY2S10Aefyjh7e7UGBP4zigf42qVXwU8VOPwi7l/L7OACGMOFcjB0Q==} + '@algolia/transporter@4.23.3': dependencies: - '@algolia/cache-common': 4.23.2 - '@algolia/logger-common': 4.23.2 - '@algolia/requester-common': 4.23.2 - dev: true + '@algolia/cache-common': 4.23.3 + '@algolia/logger-common': 4.23.3 + '@algolia/requester-common': 4.23.3 - /@ampproject/remapping@2.3.0: - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - dev: true - /@astrojs/check@0.5.9(prettier-plugin-astro@0.13.0)(prettier@3.2.5)(typescript@5.4.2): - resolution: {integrity: sha512-+QsQMtYq4oso+gmilJC9HLmdi0glZ+04V/VyyTTPry7n21jqjX9SfgDpLGxMk5cwPC/vwZMkn6ORGPnkZS/L5w==} - hasBin: true - peerDependencies: - typescript: ^5.0.0 + '@astrojs/check@0.5.10(prettier-plugin-astro@0.13.0)(prettier@3.2.5)(typescript@5.4.5)': dependencies: - '@astrojs/language-server': 2.8.3(prettier-plugin-astro@0.13.0)(prettier@3.2.5)(typescript@5.4.2) + '@astrojs/language-server': 2.8.4(prettier-plugin-astro@0.13.0)(prettier@3.2.5)(typescript@5.4.5) chokidar: 3.6.0 fast-glob: 3.3.2 kleur: 4.1.5 - typescript: 5.4.2 + typescript: 5.4.5 yargs: 17.7.2 transitivePeerDependencies: - prettier - prettier-plugin-astro - dev: false - /@astrojs/compiler@1.8.2: - resolution: {integrity: sha512-o/ObKgtMzl8SlpIdzaxFnt7SATKPxu4oIP/1NL+HDJRzxfJcAkOTAb/ZKMRyULbz4q+1t2/DAebs2Z1QairkZw==} + '@astrojs/compiler@1.8.2': {} - /@astrojs/compiler@2.7.0: - resolution: {integrity: sha512-XpC8MAaWjD1ff6/IfkRq/5k1EFj6zhCNqXRd5J43SVJEBj/Bsmizkm8N0xOYscGcDFQkRgEw6/eKnI5x/1l6aA==} + '@astrojs/compiler@2.7.0': {} - /@astrojs/internal-helpers@0.3.0: - resolution: {integrity: sha512-tGmHvrhpzuz0JBHaJX8GywN9g4rldVNHtkoVDC3m/DdzBO70jGoVuc0uuNVglRYnsdwkbG0K02Iw3nOOR3/Y4g==} - dev: true + '@astrojs/compiler@2.7.1': {} - /@astrojs/language-server@2.8.3(prettier-plugin-astro@0.13.0)(prettier@3.2.5)(typescript@5.4.2): - resolution: {integrity: sha512-tO47Lcue7OPXfIDbKVDcshwpC13yaWaTVLyiSOnQ2Yng2Z2SgcJf06Cj4xMpJqGp6s7/o/gcQWYUTl2bpkWKig==} - hasBin: true - peerDependencies: - prettier: ^3.0.0 - prettier-plugin-astro: '>=0.11.0' - peerDependenciesMeta: - prettier: - optional: true - prettier-plugin-astro: - optional: true + '@astrojs/internal-helpers@0.3.0': {} + + '@astrojs/language-server@2.8.4(prettier-plugin-astro@0.13.0)(prettier@3.2.5)(typescript@5.4.5)': dependencies: - '@astrojs/compiler': 2.7.0 + '@astrojs/compiler': 2.7.1 '@jridgewell/sourcemap-codec': 1.4.15 - '@volar/kit': 2.1.2(typescript@5.4.2) - '@volar/language-core': 2.1.2 - '@volar/language-server': 2.1.2 - '@volar/language-service': 2.1.2 - '@volar/typescript': 2.1.2 + '@volar/kit': 2.1.6(typescript@5.4.5) + '@volar/language-core': 2.1.6 + '@volar/language-server': 2.1.6 + '@volar/language-service': 2.1.6 + '@volar/typescript': 2.1.6 fast-glob: 3.3.2 + volar-service-css: 0.0.34(@volar/language-service@2.1.6) + volar-service-emmet: 0.0.34(@volar/language-service@2.1.6) + volar-service-html: 0.0.34(@volar/language-service@2.1.6) + volar-service-prettier: 0.0.34(@volar/language-service@2.1.6)(prettier@3.2.5) + volar-service-typescript: 0.0.34(@volar/language-service@2.1.6) + volar-service-typescript-twoslash-queries: 0.0.34(@volar/language-service@2.1.6) + vscode-html-languageservice: 5.2.0 + vscode-uri: 3.0.8 + optionalDependencies: prettier: 3.2.5 prettier-plugin-astro: 0.13.0 - volar-service-css: 0.0.34(@volar/language-service@2.1.2) - volar-service-emmet: 0.0.34(@volar/language-service@2.1.2) - volar-service-html: 0.0.34(@volar/language-service@2.1.2) - volar-service-prettier: 0.0.34(@volar/language-service@2.1.2)(prettier@3.2.5) - volar-service-typescript: 0.0.34(@volar/language-service@2.1.2) - volar-service-typescript-twoslash-queries: 0.0.34(@volar/language-service@2.1.2) - vscode-html-languageservice: 5.1.2 - vscode-uri: 3.0.8 transitivePeerDependencies: - typescript - dev: false - /@astrojs/markdown-remark@4.3.0: - resolution: {integrity: sha512-iZOgYj/yNDvBRfKqkGuAvjeONhjQPq8Uk3HjyIgcTK5valq03NiUgSc5Ovq00yUVBeYJ/5EDx23c8xqtkkBlPw==} + '@astrojs/markdown-remark@4.3.0': dependencies: '@astrojs/prism': 3.0.0 github-slugger: 2.0.0 @@ -547,10 +4586,8 @@ packages: vfile: 6.0.1 transitivePeerDependencies: - supports-color - dev: true - /@astrojs/markdown-remark@4.3.2: - resolution: {integrity: sha512-4Oa4VaYiBd0MatB+rWIU/0A8pZH/sK3c2QkRYb+OO2lPl+qzevJtWaZY8hAQc4qurIOlRdn6B6ofDAGhWw+DSg==} + '@astrojs/markdown-remark@4.3.2': dependencies: '@astrojs/prism': 3.0.0 github-slugger: 2.0.0 @@ -572,18 +4609,13 @@ packages: vfile: 6.0.1 transitivePeerDependencies: - supports-color - dev: true - /@astrojs/mdx@2.2.2(astro@4.5.7): - resolution: {integrity: sha512-5SIFtOctC813HFyqJwBf5TBvlT9sbiOOv+bdvzAoiBSab95dC7PZhss22EvUEx+897c81YoIZ4F9fg4ZkxBFIw==} - engines: {node: '>=18.14.1'} - peerDependencies: - astro: ^4.0.0 + '@astrojs/mdx@2.2.2(astro@4.5.7(@types/node@20.12.7)(sass@1.72.0)(typescript@5.4.5))': dependencies: '@astrojs/markdown-remark': 4.3.2 '@mdx-js/mdx': 3.0.1 acorn: 8.11.3 - astro: 4.5.7(@types/node@20.11.30)(sass@1.72.0)(typescript@5.4.2) + astro: 4.5.7(@types/node@20.12.7)(sass@1.72.0)(typescript@5.4.5) es-module-lexer: 1.5.0 estree-util-visit: 2.0.0 github-slugger: 2.0.0 @@ -598,17 +4630,12 @@ packages: vfile: 6.0.1 transitivePeerDependencies: - supports-color - dev: true - /@astrojs/preact@3.1.1(@babel/core@7.24.3)(preact@10.19.7): - resolution: {integrity: sha512-ASgmVzh4wLyIyynp5CIfDwE45Vg/tIP+Y+5SnQtURmCP1qZpjdUbsw+bGQ0wCSXtjIbzCBa7Kw7Qn0g6WE2W2w==} - engines: {node: '>=18.14.1'} - peerDependencies: - preact: ^10.6.5 + '@astrojs/preact@3.1.1(@babel/core@7.24.3)(preact@10.19.7)(vite@5.2.8(@types/node@20.12.7)(sass@1.72.0))': dependencies: '@babel/plugin-transform-react-jsx': 7.23.4(@babel/core@7.24.3) '@babel/plugin-transform-react-jsx-development': 7.22.5(@babel/core@7.24.3) - '@preact/preset-vite': 2.8.2(@babel/core@7.24.3)(preact@10.19.7) + '@preact/preset-vite': 2.8.2(@babel/core@7.24.3)(preact@10.19.7)(vite@5.2.8(@types/node@20.12.7)(sass@1.72.0)) '@preact/signals': 1.2.2(preact@10.19.7) babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.24.3) preact: 10.19.7 @@ -618,25 +4645,18 @@ packages: - '@babel/core' - supports-color - vite - dev: true - /@astrojs/prism@3.0.0: - resolution: {integrity: sha512-g61lZupWq1bYbcBnYZqdjndShr/J3l/oFobBKPA3+qMat146zce3nz2kdO4giGbhYDt4gYdhmoBz0vZJ4sIurQ==} - engines: {node: '>=18.14.1'} + '@astrojs/prism@3.0.0': dependencies: prismjs: 1.29.0 - dev: true - /@astrojs/sitemap@3.1.2: - resolution: {integrity: sha512-FxOJldIl5ltZ5CNjocQxHkAO9orwHBjqtaU28o4smobp9vowS0nbGp+I9CrPxkzWdl1crSDm9vjL9tnvG1DSug==} + '@astrojs/sitemap@3.1.4': dependencies: sitemap: 7.1.1 + stream-replace-string: 2.0.0 zod: 3.22.4 - dev: true - /@astrojs/telemetry@3.0.4: - resolution: {integrity: sha512-A+0c7k/Xy293xx6odsYZuXiaHO0PL+bnDoXOc47sGDF5ffIKdKQGRPFl2NMlCF4L0NqN4Ynbgnaip+pPF0s7pQ==} - engines: {node: '>=18.14.1'} + '@astrojs/telemetry@3.0.4': dependencies: ci-info: 3.9.0 debug: 4.3.4 @@ -647,32 +4667,20 @@ packages: which-pm-runs: 1.1.0 transitivePeerDependencies: - supports-color - dev: true - /@babel/code-frame@7.24.1: - resolution: {integrity: sha512-bC49z4spJQR3j8vFtJBLqzyzFV0ciuL5HYX7qfSl3KEqeMVV+eTquRvmXxpvB0AMubRrvv7y5DILiLLPi57Ewg==} - engines: {node: '>=6.9.0'} + '@babel/code-frame@7.24.1': dependencies: '@babel/highlight': 7.24.1 picocolors: 1.0.0 - dev: true - /@babel/code-frame@7.24.2: - resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} - engines: {node: '>=6.9.0'} + '@babel/code-frame@7.24.2': dependencies: '@babel/highlight': 7.24.2 picocolors: 1.0.0 - dev: true - /@babel/compat-data@7.24.1: - resolution: {integrity: sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==} - engines: {node: '>=6.9.0'} - dev: true + '@babel/compat-data@7.24.1': {} - /@babel/core@7.24.3: - resolution: {integrity: sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==} - engines: {node: '>=6.9.0'} + '@babel/core@7.24.3': dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.24.2 @@ -691,75 +4699,46 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color - dev: true - /@babel/generator@7.24.1: - resolution: {integrity: sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==} - engines: {node: '>=6.9.0'} + '@babel/generator@7.24.1': dependencies: '@babel/types': 7.24.0 '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 - dev: true - /@babel/helper-annotate-as-pure@7.22.5: - resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} - engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.22.5': dependencies: '@babel/types': 7.24.0 - dev: true - /@babel/helper-compilation-targets@7.23.6: - resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} - engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.23.6': dependencies: '@babel/compat-data': 7.24.1 '@babel/helper-validator-option': 7.23.5 browserslist: 4.23.0 lru-cache: 5.1.1 semver: 6.3.1 - dev: true - /@babel/helper-environment-visitor@7.22.20: - resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} - engines: {node: '>=6.9.0'} - dev: true + '@babel/helper-environment-visitor@7.22.20': {} - /@babel/helper-function-name@7.23.0: - resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} - engines: {node: '>=6.9.0'} + '@babel/helper-function-name@7.23.0': dependencies: '@babel/template': 7.24.0 '@babel/types': 7.24.0 - dev: true - /@babel/helper-hoist-variables@7.22.5: - resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} - engines: {node: '>=6.9.0'} + '@babel/helper-hoist-variables@7.22.5': dependencies: '@babel/types': 7.24.0 - dev: true - /@babel/helper-module-imports@7.24.1: - resolution: {integrity: sha512-HfEWzysMyOa7xI5uQHc/OcZf67/jc+xe/RZlznWQHhbb8Pg1SkRdbK4yEi61aY8wxQA7PkSfoojtLQP/Kpe3og==} - engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.24.1': dependencies: '@babel/types': 7.24.0 - dev: true - /@babel/helper-module-imports@7.24.3: - resolution: {integrity: sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==} - engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.24.3': dependencies: '@babel/types': 7.24.0 - dev: true - /@babel/helper-module-transforms@7.23.3(@babel/core@7.24.3): - resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.23.3(@babel/core@7.24.3)': dependencies: '@babel/core': 7.24.3 '@babel/helper-environment-visitor': 7.22.20 @@ -767,103 +4746,60 @@ packages: '@babel/helper-simple-access': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-validator-identifier': 7.22.20 - dev: true - /@babel/helper-plugin-utils@7.24.0: - resolution: {integrity: sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==} - engines: {node: '>=6.9.0'} - dev: true + '@babel/helper-plugin-utils@7.24.0': {} - /@babel/helper-simple-access@7.22.5: - resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} - engines: {node: '>=6.9.0'} + '@babel/helper-simple-access@7.22.5': dependencies: '@babel/types': 7.24.0 - dev: true - /@babel/helper-split-export-declaration@7.22.6: - resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} - engines: {node: '>=6.9.0'} + '@babel/helper-split-export-declaration@7.22.6': dependencies: '@babel/types': 7.24.0 - dev: true - /@babel/helper-string-parser@7.24.1: - resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} - engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.24.1': {} - /@babel/helper-validator-identifier@7.22.20: - resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} - engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.22.20': {} - /@babel/helper-validator-option@7.23.5: - resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} - engines: {node: '>=6.9.0'} - dev: true + '@babel/helper-validator-option@7.23.5': {} - /@babel/helpers@7.24.1: - resolution: {integrity: sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==} - engines: {node: '>=6.9.0'} + '@babel/helpers@7.24.1': dependencies: '@babel/template': 7.24.0 '@babel/traverse': 7.24.1 '@babel/types': 7.24.0 transitivePeerDependencies: - supports-color - dev: true - /@babel/highlight@7.24.1: - resolution: {integrity: sha512-EPmDPxidWe/Ex+HTFINpvXdPHRmgSF3T8hGvzondYjmgzTQ/0EbLpSxyt+w3zzlYSk9cNBQNF9k0dT5Z2NiBjw==} - engines: {node: '>=6.9.0'} + '@babel/highlight@7.24.1': dependencies: '@babel/helper-validator-identifier': 7.22.20 chalk: 2.4.2 js-tokens: 4.0.0 picocolors: 1.0.0 - dev: true - /@babel/highlight@7.24.2: - resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} - engines: {node: '>=6.9.0'} + '@babel/highlight@7.24.2': dependencies: '@babel/helper-validator-identifier': 7.22.20 chalk: 2.4.2 js-tokens: 4.0.0 picocolors: 1.0.0 - dev: true - /@babel/parser@7.24.1: - resolution: {integrity: sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==} - engines: {node: '>=6.0.0'} - hasBin: true + '@babel/parser@7.24.1': dependencies: '@babel/types': 7.24.0 - /@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.24.3): - resolution: {integrity: sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.24.3)': dependencies: '@babel/core': 7.24.3 '@babel/helper-plugin-utils': 7.24.0 - dev: true - /@babel/plugin-transform-react-jsx-development@7.22.5(@babel/core@7.24.3): - resolution: {integrity: sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-development@7.22.5(@babel/core@7.24.3)': dependencies: '@babel/core': 7.24.3 '@babel/plugin-transform-react-jsx': 7.23.4(@babel/core@7.24.3) - dev: true - /@babel/plugin-transform-react-jsx@7.23.4(@babel/core@7.24.3): - resolution: {integrity: sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx@7.23.4(@babel/core@7.24.3)': dependencies: '@babel/core': 7.24.3 '@babel/helper-annotate-as-pure': 7.22.5 @@ -871,20 +4807,14 @@ packages: '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.24.3) '@babel/types': 7.24.0 - dev: true - /@babel/template@7.24.0: - resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} - engines: {node: '>=6.9.0'} + '@babel/template@7.24.0': dependencies: '@babel/code-frame': 7.24.2 '@babel/parser': 7.24.1 '@babel/types': 7.24.0 - dev: true - /@babel/traverse@7.24.1: - resolution: {integrity: sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==} - engines: {node: '>=6.9.0'} + '@babel/traverse@7.24.1': dependencies: '@babel/code-frame': 7.24.1 '@babel/generator': 7.24.1 @@ -898,517 +4828,190 @@ packages: globals: 11.12.0 transitivePeerDependencies: - supports-color - dev: true - /@babel/types@7.24.0: - resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} - engines: {node: '>=6.9.0'} + '@babel/types@7.24.0': dependencies: '@babel/helper-string-parser': 7.24.1 '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 - /@ctrl/tinycolor@3.6.1: - resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} - engines: {node: '>=10'} - dev: true + '@ctrl/tinycolor@3.6.1': {} - /@docsearch/css@3.6.0: - resolution: {integrity: sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==} - dev: true + '@docsearch/css@3.6.0': {} - /@docsearch/react@3.6.0(@algolia/client-search@4.23.2)(search-insights@2.13.0): - resolution: {integrity: sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==} - peerDependencies: - '@types/react': '>= 16.8.0 < 19.0.0' - react: '>= 16.8.0 < 19.0.0' - react-dom: '>= 16.8.0 < 19.0.0' - search-insights: '>= 1 < 3' - peerDependenciesMeta: - '@types/react': - optional: true - react: - optional: true - react-dom: - optional: true - search-insights: - optional: true + '@docsearch/react@3.6.0(@algolia/client-search@4.23.3)(search-insights@2.13.0)': dependencies: - '@algolia/autocomplete-core': 1.9.3(@algolia/client-search@4.23.2)(algoliasearch@4.22.1)(search-insights@2.13.0) - '@algolia/autocomplete-preset-algolia': 1.9.3(@algolia/client-search@4.23.2)(algoliasearch@4.22.1) + '@algolia/autocomplete-core': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.22.1)(search-insights@2.13.0) + '@algolia/autocomplete-preset-algolia': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.22.1) '@docsearch/css': 3.6.0 algoliasearch: 4.22.1 + optionalDependencies: search-insights: 2.13.0 transitivePeerDependencies: - '@algolia/client-search' - dev: true - /@emmetio/abbreviation@2.3.3: - resolution: {integrity: sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==} + '@emmetio/abbreviation@2.3.3': dependencies: '@emmetio/scanner': 1.0.4 - dev: false - /@emmetio/css-abbreviation@2.1.8: - resolution: {integrity: sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==} + '@emmetio/css-abbreviation@2.1.8': dependencies: '@emmetio/scanner': 1.0.4 - dev: false - /@emmetio/scanner@1.0.4: - resolution: {integrity: sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==} - dev: false + '@emmetio/scanner@1.0.4': {} - /@esbuild/aix-ppc64@0.19.12: - resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - requiresBuild: true - dev: true + '@esbuild/aix-ppc64@0.19.12': optional: true - /@esbuild/aix-ppc64@0.20.2: - resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - requiresBuild: true - dev: true + '@esbuild/aix-ppc64@0.20.2': optional: true - /@esbuild/android-arm64@0.19.12: - resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true + '@esbuild/android-arm64@0.19.12': optional: true - /@esbuild/android-arm64@0.20.2: - resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true + '@esbuild/android-arm64@0.20.2': optional: true - /@esbuild/android-arm@0.15.18: - resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true - dev: true + '@esbuild/android-arm@0.15.18': optional: true - /@esbuild/android-arm@0.19.12: - resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true - dev: true + '@esbuild/android-arm@0.19.12': optional: true - /@esbuild/android-arm@0.20.2: - resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true - dev: true + '@esbuild/android-arm@0.20.2': optional: true - /@esbuild/android-x64@0.19.12: - resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true + '@esbuild/android-x64@0.19.12': optional: true - /@esbuild/android-x64@0.20.2: - resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true + '@esbuild/android-x64@0.20.2': optional: true - /@esbuild/darwin-arm64@0.19.12: - resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true + '@esbuild/darwin-arm64@0.19.12': optional: true - /@esbuild/darwin-arm64@0.20.2: - resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true + '@esbuild/darwin-arm64@0.20.2': optional: true - /@esbuild/darwin-x64@0.19.12: - resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true + '@esbuild/darwin-x64@0.19.12': optional: true - /@esbuild/darwin-x64@0.20.2: - resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true + '@esbuild/darwin-x64@0.20.2': optional: true - /@esbuild/freebsd-arm64@0.19.12: - resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true + '@esbuild/freebsd-arm64@0.19.12': optional: true - /@esbuild/freebsd-arm64@0.20.2: - resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true + '@esbuild/freebsd-arm64@0.20.2': optional: true - /@esbuild/freebsd-x64@0.19.12: - resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true + '@esbuild/freebsd-x64@0.19.12': optional: true - /@esbuild/freebsd-x64@0.20.2: - resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true + '@esbuild/freebsd-x64@0.20.2': optional: true - /@esbuild/linux-arm64@0.19.12: - resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-arm64@0.19.12': optional: true - /@esbuild/linux-arm64@0.20.2: - resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-arm64@0.20.2': optional: true - /@esbuild/linux-arm@0.19.12: - resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-arm@0.19.12': optional: true - /@esbuild/linux-arm@0.20.2: - resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-arm@0.20.2': optional: true - /@esbuild/linux-ia32@0.19.12: - resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-ia32@0.19.12': optional: true - /@esbuild/linux-ia32@0.20.2: - resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-ia32@0.20.2': optional: true - /@esbuild/linux-loong64@0.15.18: - resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-loong64@0.15.18': optional: true - /@esbuild/linux-loong64@0.19.12: - resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-loong64@0.19.12': optional: true - - /@esbuild/linux-loong64@0.20.2: - resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true + + '@esbuild/linux-loong64@0.20.2': optional: true - /@esbuild/linux-mips64el@0.19.12: - resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-mips64el@0.19.12': optional: true - /@esbuild/linux-mips64el@0.20.2: - resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-mips64el@0.20.2': optional: true - /@esbuild/linux-ppc64@0.19.12: - resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-ppc64@0.19.12': optional: true - /@esbuild/linux-ppc64@0.20.2: - resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-ppc64@0.20.2': optional: true - /@esbuild/linux-riscv64@0.19.12: - resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-riscv64@0.19.12': optional: true - /@esbuild/linux-riscv64@0.20.2: - resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-riscv64@0.20.2': optional: true - /@esbuild/linux-s390x@0.19.12: - resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-s390x@0.19.12': optional: true - /@esbuild/linux-s390x@0.20.2: - resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-s390x@0.20.2': optional: true - /@esbuild/linux-x64@0.19.12: - resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-x64@0.19.12': optional: true - /@esbuild/linux-x64@0.20.2: - resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true + '@esbuild/linux-x64@0.20.2': optional: true - /@esbuild/netbsd-x64@0.19.12: - resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true + '@esbuild/netbsd-x64@0.19.12': optional: true - /@esbuild/netbsd-x64@0.20.2: - resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true + '@esbuild/netbsd-x64@0.20.2': optional: true - /@esbuild/openbsd-x64@0.19.12: - resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true + '@esbuild/openbsd-x64@0.19.12': optional: true - /@esbuild/openbsd-x64@0.20.2: - resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true + '@esbuild/openbsd-x64@0.20.2': optional: true - /@esbuild/sunos-x64@0.19.12: - resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true + '@esbuild/sunos-x64@0.19.12': optional: true - /@esbuild/sunos-x64@0.20.2: - resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true + '@esbuild/sunos-x64@0.20.2': optional: true - /@esbuild/win32-arm64@0.19.12: - resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true + '@esbuild/win32-arm64@0.19.12': optional: true - /@esbuild/win32-arm64@0.20.2: - resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true + '@esbuild/win32-arm64@0.20.2': optional: true - /@esbuild/win32-ia32@0.19.12: - resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true + '@esbuild/win32-ia32@0.19.12': optional: true - /@esbuild/win32-ia32@0.20.2: - resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true + '@esbuild/win32-ia32@0.20.2': optional: true - /@esbuild/win32-x64@0.19.12: - resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true + '@esbuild/win32-x64@0.19.12': optional: true - /@esbuild/win32-x64@0.20.2: - resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true + '@esbuild/win32-x64@0.20.2': optional: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': dependencies: eslint: 8.57.0 eslint-visitor-keys: 3.4.3 - dev: true - /@eslint-community/regexpp@4.10.0: - resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - dev: true + '@eslint-community/regexpp@4.10.0': {} - /@eslint/eslintrc@2.1.4: - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 debug: 4.3.4 @@ -1421,131 +5024,83 @@ packages: strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color - dev: true - /@eslint/js@8.57.0: - resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true + '@eslint/js@8.57.0': {} - /@expressive-code/core@0.33.5: - resolution: {integrity: sha512-KL0EkKAvd7SSIQL3ZIP19xqe4xNjBaQYNvcJC6RmoBUnQpvxaJNFwRxCBEF/X0ftJEMaSG7WTrabZ9c/zFeqmA==} + '@expressive-code/core@0.33.5': dependencies: '@ctrl/tinycolor': 3.6.1 hast-util-to-html: 8.0.4 hastscript: 7.2.0 postcss: 8.4.37 postcss-nested: 6.0.1(postcss@8.4.37) - dev: true - /@expressive-code/plugin-frames@0.33.5: - resolution: {integrity: sha512-lFt/gbnZscBSxHovg4XiWohp5nrxk4McS6RGABdj6+0gJcX8/YrFTM23GKBIkaDePxdDidVY0jQYGYDL/RrQHw==} + '@expressive-code/plugin-frames@0.33.5': dependencies: '@expressive-code/core': 0.33.5 hastscript: 7.2.0 - dev: true - /@expressive-code/plugin-shiki@0.33.5: - resolution: {integrity: sha512-LWgttQTUrIPE1X+Lya1qFWiX47tH2AS2hkbj/cZoWkdiSjn6zUvtTypK/2Xn6Rgn6z6ClzpgHvkXRqFn7nAB4A==} + '@expressive-code/plugin-shiki@0.33.5': dependencies: '@expressive-code/core': 0.33.5 shiki: 1.2.1 - dev: true - /@expressive-code/plugin-text-markers@0.33.5: - resolution: {integrity: sha512-JxSHL1MGrJAPNaUMjFXex3K+9NJDbfew9H6PmX8LQ+fm9VNQdtBYTAz/x7nqOk7bkTrtAZK5RfDqUfb8U5M+2A==} + '@expressive-code/plugin-text-markers@0.33.5': dependencies: '@expressive-code/core': 0.33.5 hastscript: 7.2.0 unist-util-visit-parents: 5.1.3 - dev: true - /@fastify/busboy@2.1.1: - resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} - engines: {node: '>=14'} - dev: true + '@fastify/busboy@2.1.1': {} - /@fontsource/ibm-plex-mono@5.0.12: - resolution: {integrity: sha512-RamYYYUQk7FX/yVbQqGxyMR+AfX5hfCZsLo5pr5BBUBNf2i3N4AjJ4AWfieqLx1Mdwt2ukzXYojlf9J0G/gaZQ==} - dev: false + '@fontsource/ibm-plex-mono@5.0.12': {} - /@humanwhocodes/config-array@0.11.14: - resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} - engines: {node: '>=10.10.0'} + '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.2 debug: 4.3.4 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - dev: true - /@humanwhocodes/module-importer@1.0.1: - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - dev: true + '@humanwhocodes/module-importer@1.0.1': {} - /@humanwhocodes/object-schema@2.0.2: - resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} - dev: true + '@humanwhocodes/object-schema@2.0.2': {} - /@jest/schemas@29.6.3: - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 - dev: true - /@jridgewell/gen-mapping@0.3.5: - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} - engines: {node: '>=6.0.0'} + '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/trace-mapping': 0.3.25 - dev: true - /@jridgewell/resolve-uri@3.1.2: - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - dev: true + '@jridgewell/resolve-uri@3.1.2': {} - /@jridgewell/set-array@1.2.1: - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - dev: true + '@jridgewell/set-array@1.2.1': {} - /@jridgewell/sourcemap-codec@1.4.15: - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@jridgewell/sourcemap-codec@1.4.15': {} - /@jridgewell/trace-mapping@0.3.25: - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - dev: true - /@jsdoc/salty@0.2.7: - resolution: {integrity: sha512-mh8LbS9d4Jq84KLw8pzho7XC2q2/IJGiJss3xwRoLD1A+EE16SjN4PfaG4jRCzKegTFLlN0Zd8SdUPE6XdoPFg==} - engines: {node: '>=v12.0.0'} + '@jsdoc/salty@0.2.7': dependencies: lodash: 4.17.21 - dev: false - /@kwsites/file-exists@1.1.1: - resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} + '@kwsites/file-exists@1.1.1': dependencies: debug: 4.3.4 transitivePeerDependencies: - supports-color - dev: true - /@kwsites/promise-deferred@1.1.1: - resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} - dev: true + '@kwsites/promise-deferred@1.1.1': {} - /@mdx-js/mdx@3.0.1: - resolution: {integrity: sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==} + '@mdx-js/mdx@3.0.1': dependencies: '@types/estree': 1.0.5 '@types/estree-jsx': 1.0.5 @@ -1572,55 +5127,32 @@ packages: vfile: 6.0.1 transitivePeerDependencies: - supports-color - dev: true - /@nanostores/preact@0.5.1(nanostores@0.10.0)(preact@10.19.7): - resolution: {integrity: sha512-kofyeDwzM3TrOd37ay+Xxgk3Cn6jih23dxELc7Mr9IJV55jmWATfNP9b7O/awwCL7CE5z5PfzFnNk/W+tMaWGw==} - engines: {node: ^18.0.0 || >=20.0.0} - peerDependencies: - nanostores: ^0.9.0 || ^0.10.0 - preact: '>=10.0.0' + '@nanostores/preact@0.5.1(nanostores@0.10.0)(preact@10.19.7)': dependencies: nanostores: 0.10.0 preact: 10.19.7 - dev: false - /@nodelib/fs.scandir@2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - /@nodelib/fs.stat@2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} + '@nodelib/fs.stat@2.0.5': {} - /@nodelib/fs.walk@1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} + '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - /@pkgr/core@0.1.1: - resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - dev: true + '@pkgr/core@0.1.1': {} - /@preact/preset-vite@2.8.2(@babel/core@7.24.3)(preact@10.19.7): - resolution: {integrity: sha512-m3tl+M8IO8jgiHnk+7LSTFl8axdPXloewi7iGVLdmCwf34XOzEUur0bZVewW4DUbUipFjTS2CXu27+5f/oexBA==} - peerDependencies: - '@babel/core': 7.x - vite: 2.x || 3.x || 4.x || 5.x - peerDependenciesMeta: - vite: - optional: true + '@preact/preset-vite@2.8.2(@babel/core@7.24.3)(preact@10.19.7)(vite@5.2.8(@types/node@20.12.7)(sass@1.72.0))': dependencies: '@babel/core': 7.24.3 '@babel/plugin-transform-react-jsx': 7.23.4(@babel/core@7.24.3) '@babel/plugin-transform-react-jsx-development': 7.22.5(@babel/core@7.24.3) - '@prefresh/vite': 2.4.5(preact@10.19.7) + '@prefresh/vite': 2.4.5(preact@10.19.7)(vite@5.2.8(@types/node@20.12.7)(sass@1.72.0)) '@rollup/pluginutils': 4.2.1 babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.24.3) debug: 4.3.4 @@ -1630,48 +5162,27 @@ packages: resolve: 1.22.8 source-map: 0.7.4 stack-trace: 1.0.0-pre2 + vite: 5.2.8(@types/node@20.12.7)(sass@1.72.0) transitivePeerDependencies: - preact - supports-color - dev: true - /@preact/signals-core@1.5.1: - resolution: {integrity: sha512-dE6f+WCX5ZUDwXzUIWNMhhglmuLpqJhuy3X3xHrhZYI0Hm2LyQwOu0l9mdPiWrVNsE+Q7txOnJPgtIqHCYoBVA==} - dev: true + '@preact/signals-core@1.5.1': {} - /@preact/signals@1.2.2(preact@10.19.7): - resolution: {integrity: sha512-ColCqdo4cRP18bAuIR4Oik5rDpiyFtPIJIygaYPMEAwTnl4buWkBOflGBSzhYyPyJfKpkwlekrvK+1pzQ2ldWw==} - peerDependencies: - preact: 10.x + '@preact/signals@1.2.2(preact@10.19.7)': dependencies: '@preact/signals-core': 1.5.1 preact: 10.19.7 - dev: true - /@prefresh/babel-plugin@0.5.1: - resolution: {integrity: sha512-uG3jGEAysxWoyG3XkYfjYHgaySFrSsaEb4GagLzYaxlydbuREtaX+FTxuIidp241RaLl85XoHg9Ej6E4+V1pcg==} - dev: true + '@prefresh/babel-plugin@0.5.1': {} - /@prefresh/core@1.5.2(preact@10.19.7): - resolution: {integrity: sha512-A/08vkaM1FogrCII5PZKCrygxSsc11obExBScm3JF1CryK2uDS3ZXeni7FeKCx1nYdUkj4UcJxzPzc1WliMzZA==} - peerDependencies: - preact: ^10.0.0 + '@prefresh/core@1.5.2(preact@10.19.7)': dependencies: preact: 10.19.7 - dev: true - /@prefresh/utils@1.2.0: - resolution: {integrity: sha512-KtC/fZw+oqtwOLUFM9UtiitB0JsVX0zLKNyRTA332sqREqSALIIQQxdUCS1P3xR/jT1e2e8/5rwH6gdcMLEmsQ==} - dev: true + '@prefresh/utils@1.2.0': {} - /@prefresh/vite@2.4.5(preact@10.19.7): - resolution: {integrity: sha512-iForDVJ2M8gQYnm5pHumvTEJjGGc7YNYC0GVKnHFL+GvFfKHfH9Rpq67nUAzNbjuLEpqEOUuQVQajMazWu2ZNQ==} - peerDependencies: - preact: ^10.4.0 - vite: '>=2.0.0' - peerDependenciesMeta: - vite: - optional: true + '@prefresh/vite@2.4.5(preact@10.19.7)(vite@5.2.8(@types/node@20.12.7)(sass@1.72.0))': dependencies: '@babel/core': 7.24.3 '@prefresh/babel-plugin': 0.5.1 @@ -1679,312 +5190,176 @@ packages: '@prefresh/utils': 1.2.0 '@rollup/pluginutils': 4.2.1 preact: 10.19.7 + vite: 5.2.8(@types/node@20.12.7)(sass@1.72.0) transitivePeerDependencies: - supports-color - dev: true - /@rollup/pluginutils@4.2.1: - resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} - engines: {node: '>= 8.0.0'} + '@rollup/pluginutils@4.2.1': dependencies: estree-walker: 2.0.2 picomatch: 2.3.1 - dev: true - /@rollup/rollup-android-arm-eabi@4.13.0: - resolution: {integrity: sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==} - cpu: [arm] - os: [android] - requiresBuild: true - dev: true + '@rollup/rollup-android-arm-eabi@4.13.0': optional: true - /@rollup/rollup-android-arm64@4.13.0: - resolution: {integrity: sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true + '@rollup/rollup-android-arm64@4.13.0': optional: true - /@rollup/rollup-darwin-arm64@4.13.0: - resolution: {integrity: sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true + '@rollup/rollup-darwin-arm64@4.13.0': optional: true - /@rollup/rollup-darwin-x64@4.13.0: - resolution: {integrity: sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true + '@rollup/rollup-darwin-x64@4.13.0': optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.13.0: - resolution: {integrity: sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true + '@rollup/rollup-linux-arm-gnueabihf@4.13.0': optional: true - /@rollup/rollup-linux-arm64-gnu@4.13.0: - resolution: {integrity: sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true + '@rollup/rollup-linux-arm64-gnu@4.13.0': optional: true - /@rollup/rollup-linux-arm64-musl@4.13.0: - resolution: {integrity: sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true + '@rollup/rollup-linux-arm64-musl@4.13.0': optional: true - /@rollup/rollup-linux-riscv64-gnu@4.13.0: - resolution: {integrity: sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true + '@rollup/rollup-linux-riscv64-gnu@4.13.0': optional: true - /@rollup/rollup-linux-x64-gnu@4.13.0: - resolution: {integrity: sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true + '@rollup/rollup-linux-x64-gnu@4.13.0': optional: true - /@rollup/rollup-linux-x64-musl@4.13.0: - resolution: {integrity: sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true + '@rollup/rollup-linux-x64-musl@4.13.0': optional: true - /@rollup/rollup-win32-arm64-msvc@4.13.0: - resolution: {integrity: sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true + '@rollup/rollup-win32-arm64-msvc@4.13.0': optional: true - /@rollup/rollup-win32-ia32-msvc@4.13.0: - resolution: {integrity: sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true + '@rollup/rollup-win32-ia32-msvc@4.13.0': optional: true - /@rollup/rollup-win32-x64-msvc@4.13.0: - resolution: {integrity: sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true + '@rollup/rollup-win32-x64-msvc@4.13.0': optional: true - /@shikijs/core@1.2.0: - resolution: {integrity: sha512-OlFvx+nyr5C8zpcMBnSGir0YPD6K11uYhouqhNmm1qLiis4GA7SsGtu07r9gKS9omks8RtQqHrJL4S+lqWK01A==} - dev: true + '@shikijs/core@1.2.0': {} - /@shikijs/core@1.2.1: - resolution: {integrity: sha512-KaIS0H4EQ3KI2d++TjYqRNgwp8E3M/68e9veR4QtInzA7kKFgcjeiJqb80fuXW+blDy5fmd11PN9g9soz/3ANQ==} - dev: true + '@shikijs/core@1.2.1': {} - /@sinclair/typebox@0.27.8: - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - dev: true + '@sinclair/typebox@0.27.8': {} - /@ts-morph/common@0.16.0: - resolution: {integrity: sha512-SgJpzkTgZKLKqQniCjLaE3c2L2sdL7UShvmTmPBejAKd2OKV/yfMpQ2IWpAuA+VY5wy7PkSUaEObIqEK6afFuw==} + '@ts-morph/common@0.16.0': dependencies: fast-glob: 3.3.2 minimatch: 5.1.6 mkdirp: 1.0.4 path-browserify: 1.0.1 - dev: true - /@types/acorn@4.0.6: - resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} + '@types/acorn@4.0.6': dependencies: '@types/estree': 1.0.5 - dev: true - /@types/babel__core@7.20.5: - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.24.1 '@babel/types': 7.24.0 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.5 - dev: true - /@types/babel__generator@7.6.8: - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + '@types/babel__generator@7.6.8': dependencies: '@babel/types': 7.24.0 - dev: true - /@types/babel__template@7.4.4: - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + '@types/babel__template@7.4.4': dependencies: '@babel/parser': 7.24.1 '@babel/types': 7.24.0 - dev: true - /@types/babel__traverse@7.20.5: - resolution: {integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==} + '@types/babel__traverse@7.20.5': dependencies: '@babel/types': 7.24.0 - dev: true - /@types/canvas-confetti@1.6.4: - resolution: {integrity: sha512-fNyZ/Fdw/Y92X0vv7B+BD6ysHL4xVU5dJcgzgxLdGbn8O3PezZNIJpml44lKM0nsGur+o/6+NZbZeNTt00U1uA==} - dev: true + '@types/canvas-confetti@1.6.4': {} - /@types/debug@4.1.12: - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/debug@4.1.12': dependencies: '@types/ms': 0.7.34 - /@types/estree-jsx@1.0.5: - resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + '@types/estree-jsx@1.0.5': dependencies: '@types/estree': 1.0.5 - dev: true - /@types/estree@1.0.5: - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: true + '@types/estree@1.0.5': {} - /@types/hast@2.3.10: - resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} + '@types/hast@2.3.10': dependencies: '@types/unist': 2.0.10 - dev: true - /@types/hast@3.0.4: - resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.2 - /@types/html-escaper@3.0.2: - resolution: {integrity: sha512-A8vk09eyYzk8J/lFO4OUMKCmRN0rRzfZf4n3Olwapgox/PtTiU8zPYlL1UEkJ/WeHvV6v9Xnj3o/705PKz9r4Q==} - dev: true + '@types/html-escaper@3.0.2': {} - /@types/json-schema@7.0.15: - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - dev: true + '@types/json-schema@7.0.15': {} - /@types/linkify-it@3.0.5: - resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} - dev: false + '@types/linkify-it@3.0.5': {} - /@types/markdown-it@12.2.3: - resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==} + '@types/markdown-it@12.2.3': dependencies: '@types/linkify-it': 3.0.5 '@types/mdurl': 1.0.5 - dev: false - /@types/mdast@4.0.3: - resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==} + '@types/mdast@4.0.3': dependencies: '@types/unist': 3.0.2 - /@types/mdurl@1.0.5: - resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==} - dev: false + '@types/mdurl@1.0.5': {} - /@types/mdx@2.0.12: - resolution: {integrity: sha512-H9VZ9YqE+H28FQVchC83RCs5xQ2J7mAAv6qdDEaWmXEVl3OpdH+xfrSUzQ1lp7U7oSTRZ0RvW08ASPJsYBi7Cw==} - dev: true + '@types/mdx@2.0.12': {} - /@types/ms@0.7.34: - resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + '@types/ms@0.7.34': {} - /@types/nlcst@1.0.4: - resolution: {integrity: sha512-ABoYdNQ/kBSsLvZAekMhIPMQ3YUZvavStpKYs7BjLLuKVmIMA0LUgZ7b54zzuWJRbHF80v1cNf4r90Vd6eMQDg==} + '@types/nlcst@1.0.4': dependencies: '@types/unist': 2.0.10 - /@types/node@17.0.45: - resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} - dev: true - - /@types/node@18.19.26: - resolution: {integrity: sha512-+wiMJsIwLOYCvUqSdKTrfkS8mpTp+MPINe6+Np4TAGFWWRWiBQ5kSq9nZGCSPkzx9mvT+uEukzpX4MOSCydcvw==} + '@types/nlcst@2.0.3': dependencies: - undici-types: 5.26.5 - dev: true + '@types/unist': 3.0.2 - /@types/node@20.11.30: - resolution: {integrity: sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==} + '@types/node@17.0.45': {} + + '@types/node@18.19.26': dependencies: undici-types: 5.26.5 - dev: true - - /@types/parse5@6.0.3: - resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} - dev: true - - /@types/retry@0.12.2: - resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} - dev: true - /@types/sax@1.2.7: - resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} + '@types/node@20.12.7': dependencies: - '@types/node': 20.11.30 - dev: true - - /@types/semver@7.5.8: - resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} - dev: true - - /@types/strip-bom@3.0.0: - resolution: {integrity: sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==} - dev: true - - /@types/strip-json-comments@0.0.30: - resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==} - dev: true + undici-types: 5.26.5 - /@types/unist@2.0.10: - resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} + '@types/parse5@6.0.3': {} - /@types/unist@3.0.2: - resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} + '@types/retry@0.12.2': {} - /@typescript-eslint/eslint-plugin@7.4.0(@typescript-eslint/parser@7.4.0)(eslint@8.57.0)(typescript@5.4.2): - resolution: {integrity: sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@types/sax@1.2.7': + dependencies: + '@types/node': 20.12.7 + + '@types/semver@7.5.8': {} + + '@types/strip-bom@3.0.0': {} + + '@types/strip-json-comments@0.0.30': {} + + '@types/unist@2.0.10': {} + + '@types/unist@3.0.2': {} + + '@typescript-eslint/eslint-plugin@7.4.0(@typescript-eslint/parser@7.4.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 7.4.0(eslint@8.57.0)(typescript@5.4.2) + '@typescript-eslint/parser': 7.4.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/scope-manager': 7.4.0 - '@typescript-eslint/type-utils': 7.4.0(eslint@8.57.0)(typescript@5.4.2) - '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.4.2) + '@typescript-eslint/type-utils': 7.4.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/visitor-keys': 7.4.0 debug: 4.3.4 eslint: 8.57.0 @@ -1992,87 +5367,52 @@ packages: ignore: 5.3.1 natural-compare: 1.4.0 semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@5.4.2) - typescript: 5.4.2 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/parser@7.4.0(eslint@8.57.0)(typescript@5.4.2): - resolution: {integrity: sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/parser@7.4.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@typescript-eslint/scope-manager': 7.4.0 '@typescript-eslint/types': 7.4.0 - '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.2) + '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.5) '@typescript-eslint/visitor-keys': 7.4.0 debug: 4.3.4 eslint: 8.57.0 - typescript: 5.4.2 + optionalDependencies: + typescript: 5.4.5 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/scope-manager@5.62.0: - resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@typescript-eslint/scope-manager@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - dev: true - /@typescript-eslint/scope-manager@7.4.0: - resolution: {integrity: sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/scope-manager@7.4.0': dependencies: '@typescript-eslint/types': 7.4.0 '@typescript-eslint/visitor-keys': 7.4.0 - dev: true - /@typescript-eslint/type-utils@7.4.0(eslint@8.57.0)(typescript@5.4.2): - resolution: {integrity: sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/type-utils@7.4.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: - '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.2) - '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.4.2) + '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.5) + '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.4.5) debug: 4.3.4 eslint: 8.57.0 - ts-api-utils: 1.3.0(typescript@5.4.2) - typescript: 5.4.2 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/types@5.62.0: - resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true + '@typescript-eslint/types@5.62.0': {} - /@typescript-eslint/types@7.4.0: - resolution: {integrity: sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==} - engines: {node: ^18.18.0 || >=20.0.0} - dev: true + '@typescript-eslint/types@7.4.0': {} - /@typescript-eslint/typescript-estree@7.4.0(typescript@5.4.2): - resolution: {integrity: sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/typescript-estree@7.4.0(typescript@5.4.5)': dependencies: '@typescript-eslint/types': 7.4.0 '@typescript-eslint/visitor-keys': 7.4.0 @@ -2081,115 +5421,86 @@ packages: is-glob: 4.0.3 minimatch: 9.0.3 semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@5.4.2) - typescript: 5.4.2 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/utils@7.4.0(eslint@8.57.0)(typescript@5.4.2): - resolution: {integrity: sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 + '@typescript-eslint/utils@7.4.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 7.4.0 '@typescript-eslint/types': 7.4.0 - '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.2) + '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.5) eslint: 8.57.0 semver: 7.6.0 transitivePeerDependencies: - supports-color - typescript - dev: true - /@typescript-eslint/visitor-keys@5.62.0: - resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@typescript-eslint/visitor-keys@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 eslint-visitor-keys: 3.4.3 - dev: true - /@typescript-eslint/visitor-keys@7.4.0: - resolution: {integrity: sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/visitor-keys@7.4.0': dependencies: '@typescript-eslint/types': 7.4.0 eslint-visitor-keys: 3.4.3 - dev: true - /@ungap/structured-clone@1.2.0: - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@ungap/structured-clone@1.2.0': {} - /@vitest/expect@1.4.0: - resolution: {integrity: sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==} + '@vitest/expect@1.4.0': dependencies: '@vitest/spy': 1.4.0 '@vitest/utils': 1.4.0 chai: 4.4.1 - dev: true - /@vitest/runner@1.4.0: - resolution: {integrity: sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==} + '@vitest/runner@1.4.0': dependencies: '@vitest/utils': 1.4.0 p-limit: 5.0.0 pathe: 1.1.2 - dev: true - /@vitest/snapshot@1.4.0: - resolution: {integrity: sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==} + '@vitest/snapshot@1.4.0': dependencies: magic-string: 0.30.8 pathe: 1.1.2 pretty-format: 29.7.0 - dev: true - /@vitest/spy@1.4.0: - resolution: {integrity: sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==} + '@vitest/spy@1.4.0': dependencies: tinyspy: 2.2.1 - dev: true - /@vitest/utils@1.4.0: - resolution: {integrity: sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==} + '@vitest/utils@1.4.0': dependencies: diff-sequences: 29.6.3 estree-walker: 3.0.3 loupe: 2.3.7 pretty-format: 29.7.0 - dev: true - /@volar/kit@2.1.2(typescript@5.4.2): - resolution: {integrity: sha512-u20R1lCWCgFYBCHC+FR/e9J+P61vUNQpyWt4keAY+zpVHEHsSXVA2xWMJV1l1Iq5Dd0jBUSqrb1zsEya455AzA==} - peerDependencies: - typescript: '*' + '@volar/kit@2.1.6(typescript@5.4.5)': dependencies: - '@volar/language-service': 2.1.2 - '@volar/typescript': 2.1.2 + '@volar/language-service': 2.1.6 + '@volar/typescript': 2.1.6 typesafe-path: 0.2.2 - typescript: 5.4.2 + typescript: 5.4.5 vscode-languageserver-textdocument: 1.0.11 vscode-uri: 3.0.8 - dev: false - /@volar/language-core@2.1.2: - resolution: {integrity: sha512-5qsDp0Gf6fE09UWCeK7bkVn6NxMwC9OqFWQkMMkeej8h8XjyABPdRygC2RCrqDrfVdGijqlMQeXs6yRS+vfZYA==} + '@volar/language-core@2.1.6': dependencies: - '@volar/source-map': 2.1.2 - dev: false + '@volar/source-map': 2.1.6 - /@volar/language-server@2.1.2: - resolution: {integrity: sha512-5NR5Ztg+OxvDI4oRrjS0/4ZVPumWwhVq5acuK2BJbakG1kJXViYI9NOWiWITMjnliPvf12TEcSrVDBmIq54DOg==} + '@volar/language-server@2.1.6': dependencies: - '@volar/language-core': 2.1.2 - '@volar/language-service': 2.1.2 - '@volar/snapshot-document': 2.1.2 - '@volar/typescript': 2.1.2 + '@volar/language-core': 2.1.6 + '@volar/language-service': 2.1.6 + '@volar/snapshot-document': 2.1.6 + '@volar/typescript': 2.1.6 '@vscode/l10n': 0.0.16 path-browserify: 1.0.1 request-light: 0.7.0 @@ -2197,89 +5508,58 @@ packages: vscode-languageserver-protocol: 3.17.5 vscode-languageserver-textdocument: 1.0.11 vscode-uri: 3.0.8 - dev: false - /@volar/language-service@2.1.2: - resolution: {integrity: sha512-CmVbbKdqzVq+0FT67hfELdHpboqXhKXh6EjypypuFX5ptIRftHZdkaq3/lCCa46EHxS5tvE44jn+s7faN4iRDA==} + '@volar/language-service@2.1.6': dependencies: - '@volar/language-core': 2.1.2 + '@volar/language-core': 2.1.6 vscode-languageserver-protocol: 3.17.5 vscode-languageserver-textdocument: 1.0.11 vscode-uri: 3.0.8 - dev: false - /@volar/snapshot-document@2.1.2: - resolution: {integrity: sha512-ZpJIBZrdm/Gx4jC/zn8H+O6H5vZZwY7B5CMTxl9y8HvcqlePOyDi+VkX8pjQz1VFG9Z5Z+Bau/RL6exqkoVDDA==} + '@volar/snapshot-document@2.1.6': dependencies: vscode-languageserver-protocol: 3.17.5 vscode-languageserver-textdocument: 1.0.11 - dev: false - /@volar/source-map@2.1.2: - resolution: {integrity: sha512-yFJqsuLm1OaWrsz9E3yd3bJcYIlHqdZ8MbmIoZLrAzMYQDcoF26/INIhgziEXSdyHc8xd7rd/tJdSnUyh0gH4Q==} + '@volar/source-map@2.1.6': dependencies: muggle-string: 0.4.1 - dev: false - /@volar/typescript@2.1.2: - resolution: {integrity: sha512-lhTancZqamvaLvoz0u/uth8dpudENNt2LFZOWCw9JZiX14xRFhdhfzmphiCRb7am9E6qAJSbdS/gMt1utXAoHQ==} + '@volar/typescript@2.1.6': dependencies: - '@volar/language-core': 2.1.2 + '@volar/language-core': 2.1.6 path-browserify: 1.0.1 - dev: false - /@vscode/emmet-helper@2.9.2: - resolution: {integrity: sha512-MaGuyW+fa13q3aYsluKqclmh62Hgp0BpKIqS66fCxfOaBcVQ1OnMQxRRgQUYnCkxFISAQlkJ0qWWPyXjro1Qrg==} + '@vscode/emmet-helper@2.9.3': dependencies: emmet: 2.4.7 jsonc-parser: 2.3.1 vscode-languageserver-textdocument: 1.0.11 vscode-languageserver-types: 3.17.5 vscode-uri: 2.1.2 - dev: false - /@vscode/l10n@0.0.16: - resolution: {integrity: sha512-JT5CvrIYYCrmB+dCana8sUqJEcGB1ZDXNLMQ2+42bW995WmNoenijWMUdZfwmuQUTQcEVVIa2OecZzTYWUW9Cg==} - dev: false + '@vscode/l10n@0.0.16': {} - /@vscode/l10n@0.0.18: - resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} - dev: false + '@vscode/l10n@0.0.18': {} - /@webgpu/types@0.1.21: - resolution: {integrity: sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow==} - dev: true + '@webgpu/types@0.1.21': {} - /acorn-jsx@5.3.2(acorn@8.11.3): - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-jsx@5.3.2(acorn@8.11.3): dependencies: acorn: 8.11.3 - dev: true - /acorn-walk@8.3.2: - resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} - engines: {node: '>=0.4.0'} - dev: true + acorn-walk@8.3.2: {} - /acorn@8.11.3: - resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true + acorn@8.11.3: {} - /ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 - dev: true - /algoliasearch@4.22.1: - resolution: {integrity: sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg==} + algoliasearch@4.22.1: dependencies: '@algolia/cache-browser-local-storage': 4.22.1 '@algolia/cache-common': 4.22.1 @@ -2295,181 +5575,113 @@ packages: '@algolia/requester-common': 4.22.1 '@algolia/requester-node-http': 4.22.1 '@algolia/transporter': 4.22.1 - dev: true - /ansi-align@3.0.1: - resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + ansi-align@3.0.1: dependencies: string-width: 4.2.3 - dev: true - /ansi-escapes@6.2.0: - resolution: {integrity: sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==} - engines: {node: '>=14.16'} + ansi-escapes@6.2.0: dependencies: type-fest: 3.13.1 - dev: true - /ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} + ansi-regex@5.0.1: {} - /ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} - engines: {node: '>=12'} - dev: true + ansi-regex@6.0.1: {} - /ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} + ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 - dev: true - /ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - /ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - dev: true + ansi-styles@5.2.0: {} - /ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - dev: true + ansi-styles@6.2.1: {} - /anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 - /arg@5.0.2: - resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - dev: true + arg@5.0.2: {} - /argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 - dev: true - /argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + argparse@2.0.1: {} - /aria-query@5.3.0: - resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + aria-query@5.3.0: dependencies: dequal: 2.0.3 - dev: true - /array-back@1.0.4: - resolution: {integrity: sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==} - engines: {node: '>=0.12.0'} + array-back@1.0.4: dependencies: typical: 2.6.1 - dev: false - /array-back@4.0.2: - resolution: {integrity: sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==} - engines: {node: '>=8'} - dev: false + array-back@4.0.2: {} - /array-back@5.0.0: - resolution: {integrity: sha512-kgVWwJReZWmVuWOQKEOohXKJX+nD02JAZ54D1RRWlv8L0NebauKAaFxACKzB74RTclt1+WNz5KHaLRDAPZbDEw==} - engines: {node: '>=10'} - dev: false + array-back@5.0.0: {} - /array-back@6.2.2: - resolution: {integrity: sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==} - engines: {node: '>=12.17'} - dev: false + array-back@6.2.2: {} - /array-buffer-byte-length@1.0.1: - resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} - engines: {node: '>= 0.4'} + array-buffer-byte-length@1.0.1: dependencies: call-bind: 1.0.7 is-array-buffer: 3.0.4 - dev: true - /array-includes@3.1.7: - resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} - engines: {node: '>= 0.4'} + array-includes@3.1.7: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.22.5 get-intrinsic: 1.2.4 is-string: 1.0.7 - dev: true - /array-iterate@2.0.1: - resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==} + array-iterate@2.0.1: {} - /array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - dev: true + array-union@2.1.0: {} - /array.prototype.findlast@1.2.4: - resolution: {integrity: sha512-BMtLxpV+8BD+6ZPFIWmnUBpQoy+A+ujcg4rhp2iwCRJYA7PEh2MS4NL3lz8EiDlLrJPp2hg9qWihr5pd//jcGw==} - engines: {node: '>= 0.4'} + array.prototype.findlast@1.2.4: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.22.5 es-errors: 1.3.0 es-shim-unscopables: 1.0.2 - dev: true - /array.prototype.flat@1.3.2: - resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} - engines: {node: '>= 0.4'} + array.prototype.flat@1.3.2: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.22.5 es-shim-unscopables: 1.0.2 - dev: true - /array.prototype.flatmap@1.3.2: - resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} - engines: {node: '>= 0.4'} + array.prototype.flatmap@1.3.2: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.22.5 es-shim-unscopables: 1.0.2 - dev: true - /array.prototype.toreversed@1.1.2: - resolution: {integrity: sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==} + array.prototype.toreversed@1.1.2: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.22.5 es-shim-unscopables: 1.0.2 - dev: true - /array.prototype.tosorted@1.1.3: - resolution: {integrity: sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==} + array.prototype.tosorted@1.1.3: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.22.5 es-errors: 1.3.0 es-shim-unscopables: 1.0.2 - dev: true - /arraybuffer.prototype.slice@1.0.3: - resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} - engines: {node: '>= 0.4'} + arraybuffer.prototype.slice@1.0.3: dependencies: array-buffer-byte-length: 1.0.1 call-bind: 1.0.7 @@ -2479,31 +5691,18 @@ packages: get-intrinsic: 1.2.4 is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 - dev: true - /assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - dev: true + assertion-error@1.1.0: {} - /astring@1.8.6: - resolution: {integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==} - hasBin: true - dev: true + astring@1.8.6: {} - /astro-auto-import@0.4.2(astro@4.5.7): - resolution: {integrity: sha512-ZgWZQ58+EhbEym1+aoUnNyECOy0wsG5uRUs+rVp/7BzHtj1V76J2qkhjaTWLplgNb+8WrzhvTQNxytmXRCW+Ow==} - engines: {node: '>=16.0.0'} - peerDependencies: - astro: ^2.0.0 || ^3.0.0-beta || ^4.0.0-beta + astro-auto-import@0.4.2(astro@4.5.7(@types/node@20.12.7)(sass@1.72.0)(typescript@5.4.5)): dependencies: '@types/node': 18.19.26 acorn: 8.11.3 - astro: 4.5.7(@types/node@20.11.30)(sass@1.72.0)(typescript@5.4.2) - dev: true + astro: 4.5.7(@types/node@20.12.7)(sass@1.72.0)(typescript@5.4.5) - /astro-eslint-parser@0.16.3: - resolution: {integrity: sha512-CGaBseNtunAV2DCpwBXqTKq8+9Tw65XZetMaC0FsMoZuLj0gxNIkbCf2QyKYScVrNOU7/ayfNdVw8ZCSHBiqCg==} - engines: {node: ^14.18.0 || >=16.0.0} + astro-eslint-parser@0.16.3: dependencies: '@astrojs/compiler': 2.7.0 '@typescript-eslint/scope-manager': 5.62.0 @@ -2516,34 +5715,21 @@ packages: semver: 7.6.0 transitivePeerDependencies: - supports-color - dev: true - /astro-expressive-code@0.33.5(astro@4.5.7): - resolution: {integrity: sha512-9JAyllueMUN8JTl/h/yTdbKinNmfalEWcV11s3lSf/UJQbAZfWJuy+IlGcArZDI/CmD21GXhFHLqYthpdY33ug==} - peerDependencies: - astro: ^4.0.0-beta || ^3.3.0 + astro-expressive-code@0.33.5(astro@4.5.7(@types/node@20.12.7)(sass@1.72.0)(typescript@5.4.5)): dependencies: - astro: 4.5.7(@types/node@20.11.30)(sass@1.72.0)(typescript@5.4.2) + astro: 4.5.7(@types/node@20.12.7)(sass@1.72.0)(typescript@5.4.5) hast-util-to-html: 8.0.4 remark-expressive-code: 0.33.5 - dev: true - /astro-og-canvas@0.4.2(astro@4.5.7): - resolution: {integrity: sha512-OQsH6Gr2HX9ZRHdVy2OcXVBIPI65WvEtLG/60krnphh8d3ldhuAFunymYaNGcrdSZcYgXkHWejbPt//3qaRidA==} - engines: {node: '>=18.14.1'} - peerDependencies: - astro: ^3.0.0 || ^4.0.0 + astro-og-canvas@0.4.2(astro@4.5.7(@types/node@20.12.7)(sass@1.72.0)(typescript@5.4.5)): dependencies: - astro: 4.5.7(@types/node@20.11.30)(sass@1.72.0)(typescript@5.4.2) + astro: 4.5.7(@types/node@20.12.7)(sass@1.72.0)(typescript@5.4.5) canvaskit-wasm: 0.37.2 deterministic-object-hash: 2.0.2 entities: 4.5.0 - dev: true - /astro@4.5.7(@types/node@20.11.30)(sass@1.72.0)(typescript@5.4.2): - resolution: {integrity: sha512-Ioeg3TV42dOJvf6GlmykeR3EKZ8+JcnZyJ/X9qDPzVf2OREmtvW0182YCDXQBqwXFRHndZRcHLqinAWjzZYh/A==} - engines: {node: '>=18.14.1', npm: '>=6.14.0'} - hasBin: true + astro@4.5.7(@types/node@20.12.7)(sass@1.72.0)(typescript@5.4.5): dependencies: '@astrojs/compiler': 2.7.0 '@astrojs/internal-helpers': 0.3.0 @@ -2598,11 +5784,11 @@ packages: shiki: 1.2.0 string-width: 7.1.0 strip-ansi: 7.1.0 - tsconfck: 3.0.3(typescript@5.4.2) + tsconfck: 3.0.3(typescript@5.4.5) unist-util-visit: 5.0.0 vfile: 6.0.1 - vite: 5.2.8(@types/node@20.11.30)(sass@1.72.0) - vitefu: 0.2.5(vite@5.2.8) + vite: 5.2.8(@types/node@20.12.7)(sass@1.72.0) + vitefu: 0.2.5(vite@5.2.8(@types/node@20.12.7)(sass@1.72.0)) which-pm: 2.1.1 yargs-parser: 21.1.1 zod: 3.22.4 @@ -2619,142 +5805,94 @@ packages: - supports-color - terser - typescript - dev: true - /astrojs-compiler-sync@0.3.5(@astrojs/compiler@2.7.0): - resolution: {integrity: sha512-y420rhIIJ2HHDkYeqKArBHSdJNIIGMztLH90KGIX3zjcJyt/cr9Z2wYA8CP5J1w6KE7xqMh0DAkhfjhNDpQb2Q==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - '@astrojs/compiler': '>=0.27.0' + astrojs-compiler-sync@0.3.5(@astrojs/compiler@2.7.0): dependencies: '@astrojs/compiler': 2.7.0 synckit: 0.9.0 - dev: true - /available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.0.0 - dev: true - /axobject-query@4.0.0: - resolution: {integrity: sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==} + axobject-query@4.0.0: dependencies: dequal: 2.0.3 - dev: true - /b4a@1.6.6: - resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==} - requiresBuild: true - dev: true + b4a@1.6.6: optional: true - /babel-plugin-transform-hook-names@1.0.2(@babel/core@7.24.3): - resolution: {integrity: sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==} - peerDependencies: - '@babel/core': ^7.12.10 + babel-plugin-transform-hook-names@1.0.2(@babel/core@7.24.3): dependencies: '@babel/core': 7.24.3 - dev: true - /bail@2.0.2: - resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + bail@2.0.2: {} - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@1.0.2: {} - /bare-events@2.2.1: - resolution: {integrity: sha512-9GYPpsPFvrWBkelIhOhTWtkeZxVxZOdb3VnFTCzlOo3OjvmTvzLoZFUT8kNFACx0vJej6QPney1Cf9BvzCNE/A==} - requiresBuild: true - dev: true + bare-events@2.2.1: + optional: true + + bare-events@2.2.2: optional: true - /bare-fs@2.2.2: - resolution: {integrity: sha512-X9IqgvyB0/VA5OZJyb5ZstoN62AzD7YxVGog13kkfYWYqJYcK0kcqLZ6TrmH5qr4/8//ejVcX4x/a0UvaogXmA==} - requiresBuild: true + bare-fs@2.3.0: dependencies: bare-events: 2.2.1 - bare-os: 2.2.1 - bare-path: 2.1.0 - streamx: 2.16.1 - dev: true + bare-path: 2.1.2 + bare-stream: 1.0.0 optional: true - /bare-os@2.2.1: - resolution: {integrity: sha512-OwPyHgBBMkhC29Hl3O4/YfxW9n7mdTr2+SsO29XBWKKJsbgj3mnorDB80r5TiCQgQstgE5ga1qNYrpes6NvX2w==} - requiresBuild: true - dev: true + bare-os@2.2.1: optional: true - /bare-path@2.1.0: - resolution: {integrity: sha512-DIIg7ts8bdRKwJRJrUMy/PICEaQZaPGZ26lsSx9MJSwIhSrcdHn7/C8W+XmnG/rKi6BaRcz+JO00CjZteybDtw==} - requiresBuild: true + bare-path@2.1.2: dependencies: bare-os: 2.2.1 - dev: true optional: true - /base-64@1.0.0: - resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} - dev: true + bare-stream@1.0.0: + dependencies: + streamx: 2.16.1 + optional: true - /base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: true + base-64@1.0.0: {} - /bcp-47-match@2.0.3: - resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==} - dev: true + base64-js@1.5.1: {} - /bcp-47-normalize@2.3.0: - resolution: {integrity: sha512-8I/wfzqQvttUFz7HVJgIZ7+dj3vUaIyIxYXaTRP1YWoSDfzt6TUmxaKZeuXR62qBmYr+nvuWINFRl6pZ5DlN4Q==} + bcp-47-match@2.0.3: {} + + bcp-47-normalize@2.3.0: dependencies: bcp-47: 2.1.0 bcp-47-match: 2.0.3 - dev: true - /bcp-47@2.1.0: - resolution: {integrity: sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==} + bcp-47@2.1.0: dependencies: is-alphabetical: 2.0.1 is-alphanumerical: 2.0.1 is-decimal: 2.0.1 - dev: true - /binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} + binary-extensions@2.3.0: {} - /bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - requiresBuild: true + bl@4.1.0: dependencies: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 - dev: true optional: true - /bl@5.1.0: - resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} + bl@5.1.0: dependencies: buffer: 6.0.3 inherits: 2.0.4 readable-stream: 3.6.2 - dev: true - /bluebird@3.7.2: - resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - dev: false + bluebird@3.7.2: {} - /boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - dev: true + boolbase@1.0.0: {} - /boxen@7.1.1: - resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==} - engines: {node: '>=14.16'} + boxen@7.1.1: dependencies: ansi-align: 3.0.1 camelcase: 7.0.1 @@ -2764,118 +5902,75 @@ packages: type-fest: 2.19.0 widest-line: 4.0.1 wrap-ansi: 8.1.0 - dev: true - /brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - /brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 - /braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} + braces@3.0.2: dependencies: fill-range: 7.0.1 - /browserslist@4.23.0: - resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true + browserslist@4.23.0: dependencies: caniuse-lite: 1.0.30001599 electron-to-chromium: 1.4.711 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.23.0) - dev: true - /buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - requiresBuild: true + buffer@5.7.1: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - dev: true optional: true - /buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + buffer@6.0.3: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - dev: true - /cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - dev: true + cac@6.7.14: {} - /cache-point@2.0.0: - resolution: {integrity: sha512-4gkeHlFpSKgm3vm2gJN5sPqfmijYRFYCQ6tv5cLw0xVmT6r1z1vd4FNnpuOREco3cBs1G709sZ72LdgddKvL5w==} - engines: {node: '>=8'} + cache-point@2.0.0: dependencies: array-back: 4.0.2 fs-then-native: 2.0.0 mkdirp2: 1.0.5 - dev: false - /call-bind@1.0.7: - resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} - engines: {node: '>= 0.4'} + call-bind@1.0.7: dependencies: es-define-property: 1.0.0 es-errors: 1.3.0 function-bind: 1.1.2 get-intrinsic: 1.2.4 set-function-length: 1.2.2 - dev: true - /callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - dev: true + callsites@3.1.0: {} - /camelcase@7.0.1: - resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} - engines: {node: '>=14.16'} - dev: true + camelcase@7.0.1: {} - /caniuse-lite@1.0.30001599: - resolution: {integrity: sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==} - dev: true + caniuse-lite@1.0.30001599: {} - /canvas-confetti@1.9.2: - resolution: {integrity: sha512-6Xi7aHHzKwxZsem4mCKoqP6YwUG3HamaHHAlz1hTNQPCqXhARFpSXnkC9TWlahHY5CG6hSL5XexNjxK8irVErg==} - dev: false + canvas-confetti@1.9.2: {} - /canvaskit-wasm@0.37.2: - resolution: {integrity: sha512-212imazRF98gLOTiU4JAXM7xDvaknI7jaPtAg4ETXGW5rLQs6pomgIvVPUSfoKnQVTdGgzj+B4e+/u0Da20aGg==} - dev: true + canvaskit-wasm@0.37.2: {} - /canvaskit-wasm@0.39.1: - resolution: {integrity: sha512-Gy3lCmhUdKq+8bvDrs9t8+qf7RvcjuQn+we7vTVVyqgOVO1UVfHpsnBxkTZw+R4ApEJ3D5fKySl9TU11hmjl/A==} + canvaskit-wasm@0.39.1: dependencies: '@webgpu/types': 0.1.21 - dev: true - /catharsis@0.9.0: - resolution: {integrity: sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==} - engines: {node: '>= 10'} + catharsis@0.9.0: dependencies: lodash: 4.17.21 - dev: false - /ccount@2.0.1: - resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + ccount@2.0.1: {} - /chai@4.4.1: - resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} - engines: {node: '>=4'} + chai@4.4.1: dependencies: assertion-error: 1.1.0 check-error: 1.0.3 @@ -2884,54 +5979,33 @@ packages: loupe: 2.3.7 pathval: 1.1.1 type-detect: 4.0.8 - dev: true - /chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 supports-color: 5.5.0 - dev: true - /chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - dev: true - /chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - dev: true + chalk@5.3.0: {} - /character-entities-html4@2.1.0: - resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} - dev: true + character-entities-html4@2.1.0: {} - /character-entities-legacy@3.0.0: - resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - dev: true + character-entities-legacy@3.0.0: {} - /character-entities@2.0.2: - resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + character-entities@2.0.2: {} - /character-reference-invalid@2.0.1: - resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - dev: true + character-reference-invalid@2.0.1: {} - /check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + check-error@1.0.3: dependencies: get-func-name: 2.0.2 - dev: true - /chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 braces: 3.0.2 @@ -2943,426 +6017,242 @@ packages: optionalDependencies: fsevents: 2.3.3 - /chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - requiresBuild: true - dev: true + chownr@1.1.4: optional: true - /ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} - dev: true + ci-info@3.9.0: {} - /ci-info@4.0.0: - resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==} - engines: {node: '>=8'} - dev: true + ci-info@4.0.0: {} - /cli-boxes@3.0.0: - resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} - engines: {node: '>=10'} - dev: true + cli-boxes@3.0.0: {} - /cli-cursor@4.0.0: - resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + cli-cursor@4.0.0: dependencies: restore-cursor: 4.0.0 - dev: true - /cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - dev: true + cli-spinners@2.9.2: {} - /cli-truncate@4.0.0: - resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} - engines: {node: '>=18'} + cli-truncate@4.0.0: dependencies: slice-ansi: 5.0.0 string-width: 7.1.0 - dev: true - /cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} + cliui@8.0.1: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - dev: false - /clsx@2.1.0: - resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==} - engines: {node: '>=6'} - dev: true + clsx@2.1.0: {} - /code-block-writer@11.0.3: - resolution: {integrity: sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==} - dev: true + code-block-writer@11.0.3: {} - /collapse-white-space@2.1.0: - resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} - dev: true + collapse-white-space@2.1.0: {} - /collect-all@1.0.4: - resolution: {integrity: sha512-RKZhRwJtJEP5FWul+gkSMEnaK6H3AGPTTWOiRimCcs+rc/OmQE3Yhy1Q7A7KsdkG3ZXVdZq68Y6ONSdvkeEcKA==} - engines: {node: '>=0.10.0'} + collect-all@1.0.4: dependencies: stream-connect: 1.0.2 stream-via: 1.0.4 - dev: false - /color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + color-convert@1.9.3: dependencies: color-name: 1.1.3 - dev: true - /color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} + color-convert@2.0.1: dependencies: color-name: 1.1.4 - /color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - dev: true + color-name@1.1.3: {} - /color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - requiresBuild: true + color-name@1.1.4: {} - /color-string@1.9.1: - resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} - requiresBuild: true + color-string@1.9.1: dependencies: color-name: 1.1.4 simple-swizzle: 0.2.2 - dev: true optional: true - /color@4.2.3: - resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} - engines: {node: '>=12.5.0'} - requiresBuild: true + color@4.2.3: dependencies: color-convert: 2.0.1 color-string: 1.9.1 - dev: true optional: true - /colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - dev: true + colorette@2.0.20: {} - /comma-separated-tokens@2.0.3: - resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - dev: true + comma-separated-tokens@2.0.3: {} - /commander@11.1.0: - resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} - engines: {node: '>=16'} - dev: true + commander@11.1.0: {} - /commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - dev: true + commander@2.20.3: {} - /common-ancestor-path@1.0.1: - resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} - dev: true + common-ancestor-path@1.0.1: {} - /concat-map@0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + concat-map@0.0.1: {} - /convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - dev: true + convert-source-map@2.0.0: {} - /cookie@0.6.0: - resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} - engines: {node: '>= 0.6'} - dev: true + cookie@0.6.0: {} - /cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} + cross-spawn@7.0.3: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dev: true - /css-select@5.1.0: - resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + css-select@5.1.0: dependencies: boolbase: 1.0.0 css-what: 6.1.0 domhandler: 5.0.3 domutils: 3.1.0 nth-check: 2.1.1 - dev: true - /css-what@6.1.0: - resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} - engines: {node: '>= 6'} - dev: true + css-what@6.1.0: {} - /cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - dev: true + cssesc@3.0.0: {} - /data-uri-to-buffer@4.0.1: - resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} - engines: {node: '>= 12'} - dev: true + data-uri-to-buffer@4.0.1: {} - /data-view-buffer@1.0.1: - resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} - engines: {node: '>= 0.4'} + data-view-buffer@1.0.1: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 is-data-view: 1.0.1 - dev: true - /data-view-byte-length@1.0.1: - resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} - engines: {node: '>= 0.4'} + data-view-byte-length@1.0.1: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 is-data-view: 1.0.1 - dev: true - /data-view-byte-offset@1.0.0: - resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} - engines: {node: '>= 0.4'} + data-view-byte-offset@1.0.0: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 is-data-view: 1.0.1 - dev: true - /debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + debug@4.3.4: dependencies: ms: 2.1.2 - /decode-named-character-reference@1.0.2: - resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + decode-named-character-reference@1.0.2: dependencies: character-entities: 2.0.2 - /decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - requiresBuild: true + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 - dev: true optional: true - /dedent-js@1.0.1: - resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==} - dev: true + dedent-js@1.0.1: {} - /deep-eql@4.1.3: - resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} - engines: {node: '>=6'} + deep-eql@4.1.3: dependencies: type-detect: 4.0.8 - dev: true - /deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - requiresBuild: true - dev: true + deep-extend@0.6.0: optional: true - /deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true + deep-is@0.1.4: {} - /define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.0 es-errors: 1.3.0 gopd: 1.0.1 - dev: true - /define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 has-property-descriptors: 1.0.2 object-keys: 1.1.1 - dev: true - /dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} + dequal@2.0.3: {} - /detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} - engines: {node: '>=8'} - requiresBuild: true - dev: true + detect-libc@2.0.3: optional: true - /deterministic-object-hash@2.0.2: - resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} - engines: {node: '>=18'} + deterministic-object-hash@2.0.2: dependencies: base-64: 1.0.0 - dev: true - /devalue@4.3.2: - resolution: {integrity: sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==} - dev: true + devalue@4.3.2: {} - /devlop@1.1.0: - resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + devlop@1.1.0: dependencies: dequal: 2.0.3 - /diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true + diff-sequences@29.6.3: {} - /diff@5.2.0: - resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} - engines: {node: '>=0.3.1'} - dev: true + diff@5.2.0: {} - /dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 - dev: true - /dlv@1.1.3: - resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - dev: true + dlv@1.1.3: {} - /doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} + doctrine@2.1.0: dependencies: esutils: 2.0.3 - dev: true - /doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} + doctrine@3.0.0: dependencies: esutils: 2.0.3 - dev: true - /dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 entities: 4.5.0 - dev: true - /domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - dev: true + domelementtype@2.3.0: {} - /domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} + domhandler@5.0.3: dependencies: domelementtype: 2.3.0 - dev: true - /domutils@3.1.0: - resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + domutils@3.1.0: dependencies: dom-serializer: 2.0.0 domelementtype: 2.3.0 domhandler: 5.0.3 - dev: true - /dset@3.1.3: - resolution: {integrity: sha512-20TuZZHCEZ2O71q9/+8BwKwZ0QtD9D8ObhrihJPr+vLLYlSuAU3/zL4cSlgbfeoGHTjCSJBa7NGcrF9/Bx/WJQ==} - engines: {node: '>=4'} - dev: true + dset@3.1.3: {} - /eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true + eastasianwidth@0.2.0: {} - /editorconfig@0.15.3: - resolution: {integrity: sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==} - hasBin: true + editorconfig@0.15.3: dependencies: commander: 2.20.3 lru-cache: 4.1.5 semver: 5.7.2 sigmund: 1.0.1 - dev: true - /electron-to-chromium@1.4.711: - resolution: {integrity: sha512-hRg81qzvUEibX2lDxnFlVCHACa+LtrCPIsWAxo161LDYIB3jauf57RGsMZV9mvGwE98yGH06icj3zBEoOkxd/w==} - dev: true + electron-to-chromium@1.4.711: {} - /emmet@2.4.7: - resolution: {integrity: sha512-O5O5QNqtdlnQM2bmKHtJgyChcrFMgQuulI+WdiOw2NArzprUqqxUW6bgYtKvzKgrsYpuLWalOkdhNP+1jluhCA==} + emmet@2.4.7: dependencies: '@emmetio/abbreviation': 2.3.3 '@emmetio/css-abbreviation': 2.1.8 - dev: false - /emoji-regex@10.3.0: - resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} - dev: true + emoji-regex@10.3.0: {} - /emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@8.0.0: {} - /emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - dev: true + emoji-regex@9.2.2: {} - /end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - requiresBuild: true + end-of-stream@1.4.4: dependencies: once: 1.4.0 - dev: true optional: true - /entities@2.1.0: - resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==} - dev: false + entities@2.1.0: {} - /entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - dev: true + entities@4.5.0: {} - /es-abstract@1.22.5: - resolution: {integrity: sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==} - engines: {node: '>= 0.4'} + es-abstract@1.22.5: dependencies: array-buffer-byte-length: 1.0.1 arraybuffer.prototype.slice: 1.0.3 @@ -3405,11 +6295,8 @@ packages: typed-array-length: 1.0.5 unbox-primitive: 1.0.2 which-typed-array: 1.1.15 - dev: true - /es-abstract@1.23.2: - resolution: {integrity: sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w==} - engines: {node: '>= 0.4'} + es-abstract@1.23.2: dependencies: array-buffer-byte-length: 1.0.1 arraybuffer.prototype.slice: 1.0.3 @@ -3457,23 +6344,14 @@ packages: typed-array-length: 1.0.5 unbox-primitive: 1.0.2 which-typed-array: 1.1.15 - dev: true - /es-define-property@1.0.0: - resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} - engines: {node: '>= 0.4'} + es-define-property@1.0.0: dependencies: get-intrinsic: 1.2.4 - dev: true - /es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - dev: true + es-errors@1.3.0: {} - /es-iterator-helpers@1.0.18: - resolution: {integrity: sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA==} - engines: {node: '>= 0.4'} + es-iterator-helpers@1.0.18: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 @@ -3489,232 +6367,92 @@ packages: internal-slot: 1.0.7 iterator.prototype: 1.1.2 safe-array-concat: 1.1.2 - dev: true - /es-module-lexer@1.4.2: - resolution: {integrity: sha512-7nOqkomXZEaxUDJw21XZNtRk739QvrPSoZoRtbsEfcii00vdzZUh6zh1CQwHhrib8MdEtJfv5rJiGeb4KuV/vw==} - dev: true + es-module-lexer@1.4.2: {} - /es-module-lexer@1.5.0: - resolution: {integrity: sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==} - dev: true + es-module-lexer@1.5.0: {} - /es-object-atoms@1.0.0: - resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} - engines: {node: '>= 0.4'} + es-object-atoms@1.0.0: dependencies: es-errors: 1.3.0 - dev: true - /es-set-tostringtag@2.0.3: - resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} - engines: {node: '>= 0.4'} + es-set-tostringtag@2.0.3: dependencies: get-intrinsic: 1.2.4 has-tostringtag: 1.0.2 hasown: 2.0.2 - dev: true - /es-shim-unscopables@1.0.2: - resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + es-shim-unscopables@1.0.2: dependencies: hasown: 2.0.2 - dev: true - /es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} - engines: {node: '>= 0.4'} + es-to-primitive@1.2.1: dependencies: is-callable: 1.2.7 is-date-object: 1.0.5 is-symbol: 1.0.4 - dev: true - /esbuild-android-64@0.15.18: - resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true + esbuild-android-64@0.15.18: optional: true - /esbuild-android-arm64@0.15.18: - resolution: {integrity: sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true + esbuild-android-arm64@0.15.18: optional: true - /esbuild-darwin-64@0.15.18: - resolution: {integrity: sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true + esbuild-darwin-64@0.15.18: optional: true - /esbuild-darwin-arm64@0.15.18: - resolution: {integrity: sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true + esbuild-darwin-arm64@0.15.18: optional: true - /esbuild-freebsd-64@0.15.18: - resolution: {integrity: sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true + esbuild-freebsd-64@0.15.18: optional: true - /esbuild-freebsd-arm64@0.15.18: - resolution: {integrity: sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true + esbuild-freebsd-arm64@0.15.18: optional: true - /esbuild-linux-32@0.15.18: - resolution: {integrity: sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true + esbuild-linux-32@0.15.18: optional: true - /esbuild-linux-64@0.15.18: - resolution: {integrity: sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true + esbuild-linux-64@0.15.18: optional: true - /esbuild-linux-arm64@0.15.18: - resolution: {integrity: sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true + esbuild-linux-arm64@0.15.18: optional: true - /esbuild-linux-arm@0.15.18: - resolution: {integrity: sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true + esbuild-linux-arm@0.15.18: optional: true - /esbuild-linux-mips64le@0.15.18: - resolution: {integrity: sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true + esbuild-linux-mips64le@0.15.18: optional: true - /esbuild-linux-ppc64le@0.15.18: - resolution: {integrity: sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true + esbuild-linux-ppc64le@0.15.18: optional: true - /esbuild-linux-riscv64@0.15.18: - resolution: {integrity: sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true + esbuild-linux-riscv64@0.15.18: optional: true - /esbuild-linux-s390x@0.15.18: - resolution: {integrity: sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true + esbuild-linux-s390x@0.15.18: optional: true - /esbuild-netbsd-64@0.15.18: - resolution: {integrity: sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true + esbuild-netbsd-64@0.15.18: optional: true - /esbuild-openbsd-64@0.15.18: - resolution: {integrity: sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true + esbuild-openbsd-64@0.15.18: optional: true - /esbuild-sunos-64@0.15.18: - resolution: {integrity: sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true + esbuild-sunos-64@0.15.18: optional: true - /esbuild-windows-32@0.15.18: - resolution: {integrity: sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true + esbuild-windows-32@0.15.18: optional: true - /esbuild-windows-64@0.15.18: - resolution: {integrity: sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true + esbuild-windows-64@0.15.18: optional: true - /esbuild-windows-arm64@0.15.18: - resolution: {integrity: sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true + esbuild-windows-arm64@0.15.18: optional: true - /esbuild@0.15.18: - resolution: {integrity: sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true + esbuild@0.15.18: optionalDependencies: '@esbuild/android-arm': 0.15.18 '@esbuild/linux-loong64': 0.15.18 @@ -3738,13 +6476,8 @@ packages: esbuild-windows-32: 0.15.18 esbuild-windows-64: 0.15.18 esbuild-windows-arm64: 0.15.18 - dev: true - /esbuild@0.19.12: - resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true + esbuild@0.19.12: optionalDependencies: '@esbuild/aix-ppc64': 0.19.12 '@esbuild/android-arm': 0.19.12 @@ -3769,13 +6502,8 @@ packages: '@esbuild/win32-arm64': 0.19.12 '@esbuild/win32-ia32': 0.19.12 '@esbuild/win32-x64': 0.19.12 - dev: true - /esbuild@0.20.2: - resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true + esbuild@0.20.2: optionalDependencies: '@esbuild/aix-ppc64': 0.20.2 '@esbuild/android-arm': 0.20.2 @@ -3800,46 +6528,23 @@ packages: '@esbuild/win32-arm64': 0.20.2 '@esbuild/win32-ia32': 0.20.2 '@esbuild/win32-x64': 0.20.2 - dev: true - /escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} - engines: {node: '>=6'} + escalade@3.1.2: {} - /escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - dev: true + escape-string-regexp@1.0.5: {} - /escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - dev: false + escape-string-regexp@2.0.0: {} - /escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - dev: true + escape-string-regexp@4.0.0: {} - /escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} + escape-string-regexp@5.0.0: {} - /eslint-compat-utils@0.5.0(eslint@8.57.0): - resolution: {integrity: sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==} - engines: {node: '>=12'} - peerDependencies: - eslint: '>=6.0.0' + eslint-compat-utils@0.5.0(eslint@8.57.0): dependencies: eslint: 8.57.0 semver: 7.6.0 - dev: true - /eslint-plugin-astro@0.33.1(eslint@8.57.0): - resolution: {integrity: sha512-wVyxAf8Ulmljv5qJQLgspWe17LR4hLXcksIENtUlEC3W7rleBVEKXS+hIqzBfCbpkBLZpl1tsYes1AGpYHd13w==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - eslint: '>=7.0.0' + eslint-plugin-astro@0.33.1(eslint@8.57.0): dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@jridgewell/sourcemap-codec': 1.4.15 @@ -3852,13 +6557,8 @@ packages: postcss-selector-parser: 6.0.16 transitivePeerDependencies: - supports-color - dev: true - /eslint-plugin-react@7.34.1(eslint@8.57.0): - resolution: {integrity: sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + eslint-plugin-react@7.34.1(eslint@8.57.0): dependencies: array-includes: 3.1.7 array.prototype.findlast: 1.2.4 @@ -3879,25 +6579,15 @@ packages: resolve: 2.0.0-next.5 semver: 6.3.1 string.prototype.matchall: 4.0.10 - dev: true - /eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-scope@7.2.2: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 - dev: true - /eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true + eslint-visitor-keys@3.4.3: {} - /eslint@8.57.0: - resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true + eslint@8.57.0: dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@eslint-community/regexpp': 4.10.0 @@ -3939,102 +6629,62 @@ packages: text-table: 0.2.0 transitivePeerDependencies: - supports-color - dev: true - /espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + espree@9.6.1: dependencies: acorn: 8.11.3 acorn-jsx: 5.3.2(acorn@8.11.3) eslint-visitor-keys: 3.4.3 - dev: true - /esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - dev: true + esprima@4.0.1: {} - /esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} - engines: {node: '>=0.10'} + esquery@1.5.0: dependencies: estraverse: 5.3.0 - dev: true - /esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 - dev: true - /estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - dev: true + estraverse@5.3.0: {} - /estree-util-attach-comments@3.0.0: - resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + estree-util-attach-comments@3.0.0: dependencies: '@types/estree': 1.0.5 - dev: true - /estree-util-build-jsx@3.0.1: - resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + estree-util-build-jsx@3.0.1: dependencies: '@types/estree-jsx': 1.0.5 devlop: 1.1.0 estree-util-is-identifier-name: 3.0.0 estree-walker: 3.0.3 - dev: true - /estree-util-is-identifier-name@3.0.0: - resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} - dev: true + estree-util-is-identifier-name@3.0.0: {} - /estree-util-to-js@2.0.0: - resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + estree-util-to-js@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 astring: 1.8.6 source-map: 0.7.4 - dev: true - /estree-util-visit@2.0.0: - resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + estree-util-visit@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 '@types/unist': 3.0.2 - dev: true - /estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - dev: true + estree-walker@2.0.2: {} - /estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.5 - dev: true - /esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - dev: true + esutils@2.0.3: {} - /eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - dev: true + eventemitter3@4.0.7: {} - /eventemitter3@5.0.1: - resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - dev: true + eventemitter3@5.0.1: {} - /execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} + execa@8.0.1: dependencies: cross-spawn: 7.0.3 get-stream: 8.0.1 @@ -4045,47 +6695,29 @@ packages: onetime: 6.0.0 signal-exit: 4.1.0 strip-final-newline: 3.0.0 - dev: true - /expand-template@2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} - requiresBuild: true - dev: true + expand-template@2.0.3: optional: true - /expressive-code@0.33.5: - resolution: {integrity: sha512-UPg2jSvZEfXPiCa4MKtMoMQ5Hwiv7In5/LSCa/ukhjzZqPO48iVsCcEBgXWEUmEAQ02P0z00/xFfBmVnUKH+Zw==} + expressive-code@0.33.5: dependencies: '@expressive-code/core': 0.33.5 '@expressive-code/plugin-frames': 0.33.5 '@expressive-code/plugin-shiki': 0.33.5 '@expressive-code/plugin-text-markers': 0.33.5 - dev: true - /extend-shallow@2.0.1: - resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} - engines: {node: '>=0.10.0'} + extend-shallow@2.0.1: dependencies: is-extendable: 0.1.1 - dev: true - /extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + extend@3.0.2: {} - /fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: true + fast-deep-equal@3.1.3: {} - /fast-fifo@1.3.2: - resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - requiresBuild: true - dev: true + fast-fifo@1.3.2: optional: true - /fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 @@ -4093,209 +6725,124 @@ packages: merge2: 1.4.1 micromatch: 4.0.5 - /fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - dev: true + fast-json-stable-stringify@2.1.0: {} - /fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true + fast-levenshtein@2.0.6: {} - /fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fastq@1.17.1: dependencies: reusify: 1.0.4 - /fetch-blob@3.2.0: - resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} - engines: {node: ^12.20 || >= 14.13} + fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 web-streams-polyfill: 3.3.3 - dev: true - /file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 - dev: true - /file-set@4.0.2: - resolution: {integrity: sha512-fuxEgzk4L8waGXaAkd8cMr73Pm0FxOVkn8hztzUW7BAHhOGH90viQNXbiOsnecCWmfInqU6YmAMwxRMdKETceQ==} - engines: {node: '>=10'} + file-set@4.0.2: dependencies: array-back: 5.0.0 glob: 7.2.3 - dev: false - /fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} + fill-range@7.0.1: dependencies: to-regex-range: 5.0.1 - /find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} + find-up@4.1.0: dependencies: locate-path: 5.0.0 path-exists: 4.0.0 - dev: true - /find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + find-up@5.0.0: dependencies: locate-path: 6.0.0 path-exists: 4.0.0 - dev: true - /find-yarn-workspace-root2@1.2.16: - resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} + find-yarn-workspace-root2@1.2.16: dependencies: micromatch: 4.0.5 pkg-dir: 4.2.0 - dev: true - /flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} + flat-cache@3.2.0: dependencies: flatted: 3.3.1 keyv: 4.5.4 rimraf: 3.0.2 - dev: true - /flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - dev: true + flatted@3.3.1: {} - /flattie@1.1.1: - resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} - engines: {node: '>=8'} - dev: true + flattie@1.1.1: {} - /for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + for-each@0.3.3: dependencies: is-callable: 1.2.7 - dev: true - /formdata-polyfill@4.0.10: - resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} - engines: {node: '>=12.20.0'} + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 - dev: true - /fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - requiresBuild: true - dev: true + fs-constants@1.0.0: optional: true - /fs-then-native@2.0.0: - resolution: {integrity: sha512-X712jAOaWXkemQCAmWeg5rOT2i+KOpWz1Z/txk/cW0qlOu2oQ9H61vc5w3X/iyuUEfq/OyaFJ78/cZAQD1/bgA==} - engines: {node: '>=4.0.0'} - dev: false + fs-then-native@2.0.0: {} - /fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fs.realpath@1.0.0: {} - /fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true + fsevents@2.3.3: optional: true - /function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - dev: true + function-bind@1.1.2: {} - /function.prototype.name@1.1.6: - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} - engines: {node: '>= 0.4'} + function.prototype.name@1.1.6: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.22.5 functions-have-names: 1.2.3 - dev: true - /functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - dev: true + functions-have-names@1.2.3: {} - /gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - dev: true + gensync@1.0.0-beta.2: {} - /get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - dev: false + get-caller-file@2.0.5: {} - /get-east-asian-width@1.2.0: - resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==} - engines: {node: '>=18'} - dev: true + get-east-asian-width@1.2.0: {} - /get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - dev: true + get-func-name@2.0.2: {} - /get-intrinsic@1.2.4: - resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} - engines: {node: '>= 0.4'} + get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 has-proto: 1.0.3 has-symbols: 1.0.3 hasown: 2.0.2 - dev: true - /get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - dev: true + get-stream@8.0.1: {} - /get-symbol-description@1.0.2: - resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} - engines: {node: '>= 0.4'} + get-symbol-description@1.0.2: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 get-intrinsic: 1.2.4 - dev: true - /github-from-package@0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - requiresBuild: true - dev: true + github-from-package@0.0.0: optional: true - /github-slugger@2.0.0: - resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + github-slugger@2.0.0: {} - /glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 - /glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 - dev: true - /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + glob@7.2.3: dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -4304,28 +6851,17 @@ packages: once: 1.4.0 path-is-absolute: 1.0.1 - /globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - dev: true + globals@11.12.0: {} - /globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} + globals@13.24.0: dependencies: type-fest: 0.20.2 - dev: true - /globalthis@1.0.3: - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} - engines: {node: '>= 0.4'} + globalthis@1.0.3: dependencies: define-properties: 1.2.1 - dev: true - /globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} + globby@11.1.0: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 @@ -4333,77 +6869,45 @@ packages: ignore: 5.3.1 merge2: 1.4.1 slash: 3.0.0 - dev: true - /gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + gopd@1.0.1: dependencies: get-intrinsic: 1.2.4 - dev: true - /graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graceful-fs@4.2.11: {} - /graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - dev: true + graphemer@1.4.0: {} - /gray-matter@4.0.3: - resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} - engines: {node: '>=6.0'} + gray-matter@4.0.3: dependencies: js-yaml: 3.14.1 kind-of: 6.0.3 section-matter: 1.0.0 strip-bom-string: 1.0.0 - dev: true - /has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - dev: true + has-bigints@1.0.2: {} - /has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - dev: true + has-flag@3.0.0: {} - /has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - dev: true + has-flag@4.0.0: {} - /has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.0 - dev: true - /has-proto@1.0.3: - resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} - engines: {node: '>= 0.4'} - dev: true + has-proto@1.0.3: {} - /has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} - dev: true + has-symbols@1.0.3: {} - /has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} + has-tostringtag@1.0.2: dependencies: has-symbols: 1.0.3 - dev: true - /hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} + hasown@2.0.2: dependencies: function-bind: 1.1.2 - dev: true - /hast-util-from-html@2.0.1: - resolution: {integrity: sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==} + hast-util-from-html@2.0.1: dependencies: '@types/hast': 3.0.4 devlop: 1.1.0 @@ -4411,10 +6915,8 @@ packages: parse5: 7.1.2 vfile: 6.0.1 vfile-message: 4.0.2 - dev: true - /hast-util-from-parse5@7.1.2: - resolution: {integrity: sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==} + hast-util-from-parse5@7.1.2: dependencies: '@types/hast': 2.3.10 '@types/unist': 2.0.10 @@ -4423,10 +6925,8 @@ packages: vfile: 5.3.7 vfile-location: 4.1.0 web-namespaces: 2.0.1 - dev: true - /hast-util-from-parse5@8.0.1: - resolution: {integrity: sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==} + hast-util-from-parse5@8.0.1: dependencies: '@types/hast': 3.0.4 '@types/unist': 3.0.2 @@ -4436,33 +6936,24 @@ packages: vfile: 6.0.1 vfile-location: 5.0.2 web-namespaces: 2.0.1 - dev: true - /hast-util-heading-rank@3.0.0: - resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} + hast-util-heading-rank@3.0.0: dependencies: '@types/hast': 3.0.4 - dev: false - /hast-util-is-element@3.0.0: - resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + hast-util-is-element@3.0.0: dependencies: '@types/hast': 3.0.4 - /hast-util-parse-selector@3.1.1: - resolution: {integrity: sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==} + hast-util-parse-selector@3.1.1: dependencies: '@types/hast': 2.3.10 - dev: true - /hast-util-parse-selector@4.0.0: - resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + hast-util-parse-selector@4.0.0: dependencies: '@types/hast': 3.0.4 - dev: true - /hast-util-raw@7.2.3: - resolution: {integrity: sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==} + hast-util-raw@7.2.3: dependencies: '@types/hast': 2.3.10 '@types/parse5': 6.0.3 @@ -4475,10 +6966,8 @@ packages: vfile: 5.3.7 web-namespaces: 2.0.1 zwitch: 2.0.4 - dev: true - /hast-util-raw@9.0.2: - resolution: {integrity: sha512-PldBy71wO9Uq1kyaMch9AHIghtQvIwxBUkv823pKmkTM3oV1JxtsTNYdevMxvUHqcnOAuO65JKU2+0NOxc2ksA==} + hast-util-raw@9.0.2: dependencies: '@types/hast': 3.0.4 '@types/unist': 3.0.2 @@ -4493,10 +6982,8 @@ packages: vfile: 6.0.1 web-namespaces: 2.0.1 zwitch: 2.0.4 - dev: true - /hast-util-to-estree@3.1.0: - resolution: {integrity: sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==} + hast-util-to-estree@3.1.0: dependencies: '@types/estree': 1.0.5 '@types/estree-jsx': 1.0.5 @@ -4516,10 +7003,8 @@ packages: zwitch: 2.0.4 transitivePeerDependencies: - supports-color - dev: true - /hast-util-to-html@8.0.4: - resolution: {integrity: sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==} + hast-util-to-html@8.0.4: dependencies: '@types/hast': 2.3.10 '@types/unist': 2.0.10 @@ -4532,10 +7017,8 @@ packages: space-separated-tokens: 2.0.2 stringify-entities: 4.0.3 zwitch: 2.0.4 - dev: true - /hast-util-to-html@9.0.0: - resolution: {integrity: sha512-IVGhNgg7vANuUA2XKrT6sOIIPgaYZnmLx3l/CCOAK0PtgfoHrZwX7jCSYyFxHTrGmC6S9q8aQQekjp4JPZF+cw==} + hast-util-to-html@9.0.0: dependencies: '@types/hast': 3.0.4 '@types/unist': 3.0.2 @@ -4549,10 +7032,8 @@ packages: space-separated-tokens: 2.0.2 stringify-entities: 4.0.3 zwitch: 2.0.4 - dev: true - /hast-util-to-jsx-runtime@2.3.0: - resolution: {integrity: sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==} + hast-util-to-jsx-runtime@2.3.0: dependencies: '@types/estree': 1.0.5 '@types/hast': 3.0.4 @@ -4571,10 +7052,8 @@ packages: vfile-message: 4.0.2 transitivePeerDependencies: - supports-color - dev: true - /hast-util-to-parse5@7.1.0: - resolution: {integrity: sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==} + hast-util-to-parse5@7.1.0: dependencies: '@types/hast': 2.3.10 comma-separated-tokens: 2.0.3 @@ -4582,10 +7061,8 @@ packages: space-separated-tokens: 2.0.2 web-namespaces: 2.0.1 zwitch: 2.0.4 - dev: true - /hast-util-to-parse5@8.0.0: - resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} + hast-util-to-parse5@8.0.0: dependencies: '@types/hast': 3.0.4 comma-separated-tokens: 2.0.3 @@ -4594,484 +7071,280 @@ packages: space-separated-tokens: 2.0.2 web-namespaces: 2.0.1 zwitch: 2.0.4 - dev: true - /hast-util-to-string@3.0.0: - resolution: {integrity: sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==} + hast-util-to-string@3.0.0: dependencies: '@types/hast': 3.0.4 - /hast-util-to-text@4.0.0: - resolution: {integrity: sha512-EWiE1FSArNBPUo1cKWtzqgnuRQwEeQbQtnFJRYV1hb1BWDgrAlBU0ExptvZMM/KSA82cDpm2sFGf3Dmc5Mza3w==} + hast-util-to-text@4.0.0: dependencies: '@types/hast': 3.0.4 '@types/unist': 3.0.2 hast-util-is-element: 3.0.0 unist-util-find-after: 5.0.0 - dev: true - /hast-util-whitespace@2.0.1: - resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} - dev: true + hast-util-whitespace@2.0.1: {} - /hast-util-whitespace@3.0.0: - resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hast-util-whitespace@3.0.0: dependencies: '@types/hast': 3.0.4 - dev: true - /hastscript@7.2.0: - resolution: {integrity: sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==} + hastscript@7.2.0: dependencies: '@types/hast': 2.3.10 comma-separated-tokens: 2.0.3 hast-util-parse-selector: 3.1.1 property-information: 6.4.1 space-separated-tokens: 2.0.2 - dev: true - /hastscript@8.0.0: - resolution: {integrity: sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==} + hastscript@8.0.0: dependencies: '@types/hast': 3.0.4 comma-separated-tokens: 2.0.3 hast-util-parse-selector: 4.0.0 property-information: 6.4.1 space-separated-tokens: 2.0.2 - dev: true - /hastscript@9.0.0: - resolution: {integrity: sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw==} + hastscript@9.0.0: dependencies: '@types/hast': 3.0.4 comma-separated-tokens: 2.0.3 hast-util-parse-selector: 4.0.0 property-information: 6.4.1 space-separated-tokens: 2.0.2 - dev: true - /he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - dev: true + he@1.2.0: {} - /html-escaper@3.0.3: - resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} - dev: true + html-escaper@3.0.3: {} - /html-void-elements@2.0.1: - resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} - dev: true + html-void-elements@2.0.1: {} - /html-void-elements@3.0.0: - resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - dev: true + html-void-elements@3.0.0: {} - /htmlparser2@9.1.0: - resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==} + htmlparser2@9.1.0: dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 domutils: 3.1.0 entities: 4.5.0 - dev: true - /http-cache-semantics@4.1.1: - resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} - dev: true + http-cache-semantics@4.1.1: {} - /human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - dev: true + human-signals@5.0.0: {} - /husky@9.0.11: - resolution: {integrity: sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==} - engines: {node: '>=18'} - hasBin: true - dev: true + husky@9.0.11: {} - /ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: true + ieee754@1.2.1: {} - /ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} - engines: {node: '>= 4'} - dev: true + ignore@5.3.1: {} - /immutable@4.3.5: - resolution: {integrity: sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==} + immutable@4.3.5: {} - /import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} + import-fresh@3.3.0: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - dev: true - /import-meta-resolve@4.0.0: - resolution: {integrity: sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==} - dev: true + import-meta-resolve@4.0.0: {} - /imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - dev: true + imurmurhash@0.1.4: {} - /inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + inflight@1.0.6: dependencies: once: 1.4.0 wrappy: 1.0.2 - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + inherits@2.0.4: {} - /ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - requiresBuild: true - dev: true + ini@1.3.8: optional: true - /inline-style-parser@0.1.1: - resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} - dev: true + inline-style-parser@0.1.1: {} - /inline-style-parser@0.2.3: - resolution: {integrity: sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==} - dev: true + inline-style-parser@0.2.3: {} - /internal-slot@1.0.7: - resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} - engines: {node: '>= 0.4'} + internal-slot@1.0.7: dependencies: es-errors: 1.3.0 hasown: 2.0.2 side-channel: 1.0.6 - dev: true - /is-alphabetical@2.0.1: - resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} - dev: true + is-alphabetical@2.0.1: {} - /is-alphanumerical@2.0.1: - resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-alphanumerical@2.0.1: dependencies: is-alphabetical: 2.0.1 is-decimal: 2.0.1 - dev: true - /is-array-buffer@3.0.4: - resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} - engines: {node: '>= 0.4'} + is-array-buffer@3.0.4: dependencies: call-bind: 1.0.7 get-intrinsic: 1.2.4 - dev: true - /is-arrayish@0.3.2: - resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} - requiresBuild: true - dev: true + is-arrayish@0.3.2: optional: true - /is-async-function@2.0.0: - resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} - engines: {node: '>= 0.4'} + is-async-function@2.0.0: dependencies: has-tostringtag: 1.0.2 - dev: true - /is-bigint@1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + is-bigint@1.0.4: dependencies: has-bigints: 1.0.2 - dev: true - /is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 - /is-boolean-object@1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} - engines: {node: '>= 0.4'} + is-boolean-object@1.1.2: dependencies: call-bind: 1.0.7 has-tostringtag: 1.0.2 - dev: true - /is-buffer@2.0.5: - resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} - engines: {node: '>=4'} + is-buffer@2.0.5: {} - /is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - dev: true + is-callable@1.2.7: {} - /is-core-module@2.13.1: - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + is-core-module@2.13.1: dependencies: hasown: 2.0.2 - dev: true - /is-data-view@1.0.1: - resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} - engines: {node: '>= 0.4'} + is-data-view@1.0.1: dependencies: is-typed-array: 1.1.13 - dev: true - /is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} - engines: {node: '>= 0.4'} + is-date-object@1.0.5: dependencies: has-tostringtag: 1.0.2 - dev: true - /is-decimal@2.0.1: - resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} - dev: true + is-decimal@2.0.1: {} - /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-docker@3.0.0: {} - /is-extendable@0.1.1: - resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} - engines: {node: '>=0.10.0'} - dev: true + is-extendable@0.1.1: {} - /is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} + is-extglob@2.1.1: {} - /is-finalizationregistry@1.0.2: - resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} + is-finalizationregistry@1.0.2: dependencies: call-bind: 1.0.7 - dev: true - /is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} + is-fullwidth-code-point@3.0.0: {} - /is-fullwidth-code-point@4.0.0: - resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} - engines: {node: '>=12'} - dev: true + is-fullwidth-code-point@4.0.0: {} - /is-fullwidth-code-point@5.0.0: - resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} - engines: {node: '>=18'} + is-fullwidth-code-point@5.0.0: dependencies: get-east-asian-width: 1.2.0 - dev: true - /is-generator-function@1.0.10: - resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} - engines: {node: '>= 0.4'} + is-generator-function@1.0.10: dependencies: has-tostringtag: 1.0.2 - dev: true - /is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 - /is-hexadecimal@2.0.1: - resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - dev: true + is-hexadecimal@2.0.1: {} - /is-inside-container@1.0.0: - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} - engines: {node: '>=14.16'} - hasBin: true + is-inside-container@1.0.0: dependencies: is-docker: 3.0.0 - dev: true - /is-interactive@2.0.0: - resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} - engines: {node: '>=12'} - dev: true + is-interactive@2.0.0: {} - /is-map@2.0.3: - resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} - engines: {node: '>= 0.4'} - dev: true + is-map@2.0.3: {} - /is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - dev: true + is-negative-zero@2.0.3: {} - /is-network-error@1.1.0: - resolution: {integrity: sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==} - engines: {node: '>=16'} - dev: true + is-network-error@1.1.0: {} - /is-number-object@1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} - engines: {node: '>= 0.4'} + is-number-object@1.0.7: dependencies: has-tostringtag: 1.0.2 - dev: true - /is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} + is-number@7.0.0: {} - /is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - dev: true + is-path-inside@3.0.3: {} - /is-plain-obj@4.1.0: - resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} - engines: {node: '>=12'} + is-plain-obj@4.1.0: {} - /is-reference@3.0.2: - resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + is-reference@3.0.2: dependencies: '@types/estree': 1.0.5 - dev: true - /is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} - engines: {node: '>= 0.4'} + is-regex@1.1.4: dependencies: call-bind: 1.0.7 has-tostringtag: 1.0.2 - dev: true - /is-set@2.0.3: - resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} - engines: {node: '>= 0.4'} - dev: true + is-set@2.0.3: {} - /is-shared-array-buffer@1.0.3: - resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} - engines: {node: '>= 0.4'} + is-shared-array-buffer@1.0.3: dependencies: call-bind: 1.0.7 - dev: true - /is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true + is-stream@3.0.0: {} - /is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} - engines: {node: '>= 0.4'} + is-string@1.0.7: dependencies: has-tostringtag: 1.0.2 - dev: true - /is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} - engines: {node: '>= 0.4'} + is-symbol@1.0.4: dependencies: has-symbols: 1.0.3 - dev: true - /is-typed-array@1.1.13: - resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} - engines: {node: '>= 0.4'} + is-typed-array@1.1.13: dependencies: which-typed-array: 1.1.15 - dev: true - /is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} - dev: true + is-unicode-supported@1.3.0: {} - /is-weakmap@2.0.2: - resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} - engines: {node: '>= 0.4'} - dev: true + is-weakmap@2.0.2: {} - /is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + is-weakref@1.0.2: dependencies: call-bind: 1.0.7 - dev: true - /is-weakset@2.0.3: - resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} - engines: {node: '>= 0.4'} + is-weakset@2.0.3: dependencies: call-bind: 1.0.7 get-intrinsic: 1.2.4 - dev: true - /is-wsl@3.1.0: - resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} - engines: {node: '>=16'} + is-wsl@3.1.0: dependencies: is-inside-container: 1.0.0 - dev: true - /isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - dev: true + isarray@2.0.5: {} - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true + isexe@2.0.0: {} - /iterator.prototype@1.1.2: - resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} + iterator.prototype@1.1.2: dependencies: define-properties: 1.2.1 get-intrinsic: 1.2.4 has-symbols: 1.0.3 reflect.getprototypeof: 1.0.6 set-function-name: 2.0.2 - dev: true - /js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: true + js-tokens@4.0.0: {} - /js-tokens@8.0.3: - resolution: {integrity: sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==} - dev: true + js-tokens@8.0.3: {} - /js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true + js-yaml@3.14.1: dependencies: argparse: 1.0.10 esprima: 4.0.1 - dev: true - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true + js-yaml@4.1.0: dependencies: argparse: 2.0.1 - dev: true - /js2xmlparser@4.0.2: - resolution: {integrity: sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==} + js2xmlparser@4.0.2: dependencies: xmlcreate: 2.0.4 - dev: false - /jsdoc-api@8.0.0: - resolution: {integrity: sha512-Rnhor0suB1Ds1abjmFkFfKeD+kSMRN9oHMTMZoJVUrmtCGDwXty+sWMA9sa4xbe4UyxuPjhC7tavZ40mDKK6QQ==} - engines: {node: '>=12.17'} + jsdoc-api@8.0.0: dependencies: array-back: 6.2.2 cache-point: 2.0.0 @@ -5082,12 +7355,8 @@ packages: object-to-spawn-args: 2.0.1 temp-path: 1.0.0 walk-back: 5.1.0 - dev: false - /jsdoc@4.0.2: - resolution: {integrity: sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==} - engines: {node: '>=12.0.0'} - hasBin: true + jsdoc@4.0.2: dependencies: '@babel/parser': 7.24.1 '@jsdoc/salty': 0.2.7 @@ -5104,103 +7373,56 @@ packages: requizzle: 0.2.4 strip-json-comments: 3.1.1 underscore: 1.13.6 - dev: false - /jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true - dev: true + jsesc@2.5.2: {} - /json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - dev: true + json-buffer@3.0.1: {} - /json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true + json-schema-traverse@0.4.1: {} - /json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - dev: true + json-stable-stringify-without-jsonify@1.0.1: {} - /json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - dev: true + json5@2.2.3: {} - /jsonc-parser@2.3.1: - resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} - dev: false + jsonc-parser@2.3.1: {} - /jsonc-parser@3.2.1: - resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} - dev: true + jsonc-parser@3.2.1: {} - /jsx-ast-utils@3.3.5: - resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} - engines: {node: '>=4.0'} + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.7 array.prototype.flat: 1.3.2 object.assign: 4.1.5 object.values: 1.2.0 - dev: true - /keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 - dev: true - /kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - dev: true + kind-of@6.0.3: {} - /klaw@3.0.0: - resolution: {integrity: sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==} + klaw@3.0.0: dependencies: graceful-fs: 4.2.11 - dev: false - /kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - dev: true + kleur@3.0.3: {} - /kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} + kleur@4.1.5: {} - /kolorist@1.8.0: - resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - dev: true + kolorist@1.8.0: {} - /levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 - dev: true - /lilconfig@3.0.0: - resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==} - engines: {node: '>=14'} - dev: true + lilconfig@3.0.0: {} - /linkify-it@3.0.3: - resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==} + linkify-it@3.0.3: dependencies: uc.micro: 1.0.6 - dev: false - /lint-staged@15.2.2: - resolution: {integrity: sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==} - engines: {node: '>=18.12.0'} - hasBin: true + lint-staged@15.2.2: dependencies: chalk: 5.3.0 commander: 11.1.0 @@ -5214,11 +7436,8 @@ packages: yaml: 2.3.4 transitivePeerDependencies: - supports-color - dev: true - /listr2@8.0.1: - resolution: {integrity: sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==} - engines: {node: '>=18.0.0'} + listr2@8.0.1: dependencies: cli-truncate: 4.0.0 colorette: 2.0.20 @@ -5226,161 +7445,101 @@ packages: log-update: 6.0.0 rfdc: 1.3.1 wrap-ansi: 9.0.0 - dev: true - /load-yaml-file@0.2.0: - resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} - engines: {node: '>=6'} + load-yaml-file@0.2.0: dependencies: graceful-fs: 4.2.11 js-yaml: 3.14.1 pify: 4.0.1 strip-bom: 3.0.0 - dev: true - /local-pkg@0.5.0: - resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} - engines: {node: '>=14'} + local-pkg@0.5.0: dependencies: mlly: 1.6.1 pkg-types: 1.0.3 - dev: true - /locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} + locate-path@5.0.0: dependencies: p-locate: 4.1.0 - dev: true - /locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 - dev: true - /lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true + lodash.merge@4.6.2: {} - /lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: false + lodash@4.17.21: {} - /log-symbols@5.1.0: - resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} - engines: {node: '>=12'} + log-symbols@5.1.0: dependencies: chalk: 5.3.0 is-unicode-supported: 1.3.0 - dev: true - /log-update@6.0.0: - resolution: {integrity: sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==} - engines: {node: '>=18'} + log-update@6.0.0: dependencies: ansi-escapes: 6.2.0 cli-cursor: 4.0.0 slice-ansi: 7.1.0 strip-ansi: 7.1.0 wrap-ansi: 9.0.0 - dev: true - /longest-streak@3.1.0: - resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + longest-streak@3.1.0: {} - /loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 - dev: true - /loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + loupe@2.3.7: dependencies: get-func-name: 2.0.2 - dev: true - /lru-cache@4.1.5: - resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + lru-cache@4.1.5: dependencies: pseudomap: 1.0.2 yallist: 2.1.2 - dev: true - /lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 - dev: true - /lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} + lru-cache@6.0.0: dependencies: yallist: 4.0.0 - /magic-string@0.30.5: - resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} - engines: {node: '>=12'} + magic-string@0.30.5: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 - dev: true - /magic-string@0.30.8: - resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} - engines: {node: '>=12'} + magic-string@0.30.8: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 - dev: true - /markdown-extensions@2.0.0: - resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} - engines: {node: '>=16'} - dev: true + markdown-extensions@2.0.0: {} - /markdown-it-anchor@8.6.7(@types/markdown-it@12.2.3)(markdown-it@12.3.2): - resolution: {integrity: sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==} - peerDependencies: - '@types/markdown-it': '*' - markdown-it: '*' + markdown-it-anchor@8.6.7(@types/markdown-it@12.2.3)(markdown-it@12.3.2): dependencies: '@types/markdown-it': 12.2.3 markdown-it: 12.3.2 - dev: false - /markdown-it@12.3.2: - resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==} - hasBin: true + markdown-it@12.3.2: dependencies: argparse: 2.0.1 entities: 2.1.0 linkify-it: 3.0.3 mdurl: 1.0.1 uc.micro: 1.0.6 - dev: false - /markdown-table@3.0.3: - resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} + markdown-table@3.0.3: {} - /marked@4.3.0: - resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} - engines: {node: '>= 12'} - hasBin: true - dev: false + marked@4.3.0: {} - /mdast-util-definitions@6.0.0: - resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==} + mdast-util-definitions@6.0.0: dependencies: '@types/mdast': 4.0.3 '@types/unist': 3.0.2 unist-util-visit: 5.0.0 - dev: true - /mdast-util-directive@3.0.0: - resolution: {integrity: sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q==} + mdast-util-directive@3.0.0: dependencies: '@types/mdast': 4.0.3 '@types/unist': 3.0.2 @@ -5392,18 +7551,15 @@ packages: unist-util-visit-parents: 6.0.1 transitivePeerDependencies: - supports-color - dev: true - /mdast-util-find-and-replace@3.0.1: - resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} + mdast-util-find-and-replace@3.0.1: dependencies: '@types/mdast': 4.0.3 escape-string-regexp: 5.0.0 unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 - /mdast-util-from-markdown@2.0.0: - resolution: {integrity: sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==} + mdast-util-from-markdown@2.0.0: dependencies: '@types/mdast': 4.0.3 '@types/unist': 3.0.2 @@ -5420,8 +7576,7 @@ packages: transitivePeerDependencies: - supports-color - /mdast-util-gfm-autolink-literal@2.0.0: - resolution: {integrity: sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==} + mdast-util-gfm-autolink-literal@2.0.0: dependencies: '@types/mdast': 4.0.3 ccount: 2.0.1 @@ -5429,8 +7584,7 @@ packages: mdast-util-find-and-replace: 3.0.1 micromark-util-character: 2.1.0 - /mdast-util-gfm-footnote@2.0.0: - resolution: {integrity: sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==} + mdast-util-gfm-footnote@2.0.0: dependencies: '@types/mdast': 4.0.3 devlop: 1.1.0 @@ -5440,8 +7594,7 @@ packages: transitivePeerDependencies: - supports-color - /mdast-util-gfm-strikethrough@2.0.0: - resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + mdast-util-gfm-strikethrough@2.0.0: dependencies: '@types/mdast': 4.0.3 mdast-util-from-markdown: 2.0.0 @@ -5449,8 +7602,7 @@ packages: transitivePeerDependencies: - supports-color - /mdast-util-gfm-table@2.0.0: - resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + mdast-util-gfm-table@2.0.0: dependencies: '@types/mdast': 4.0.3 devlop: 1.1.0 @@ -5460,8 +7612,7 @@ packages: transitivePeerDependencies: - supports-color - /mdast-util-gfm-task-list-item@2.0.0: - resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + mdast-util-gfm-task-list-item@2.0.0: dependencies: '@types/mdast': 4.0.3 devlop: 1.1.0 @@ -5470,8 +7621,7 @@ packages: transitivePeerDependencies: - supports-color - /mdast-util-gfm@3.0.0: - resolution: {integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==} + mdast-util-gfm@3.0.0: dependencies: mdast-util-from-markdown: 2.0.0 mdast-util-gfm-autolink-literal: 2.0.0 @@ -5483,8 +7633,7 @@ packages: transitivePeerDependencies: - supports-color - /mdast-util-mdx-expression@2.0.0: - resolution: {integrity: sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==} + mdast-util-mdx-expression@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.4 @@ -5494,10 +7643,8 @@ packages: mdast-util-to-markdown: 2.1.0 transitivePeerDependencies: - supports-color - dev: true - /mdast-util-mdx-jsx@3.1.2: - resolution: {integrity: sha512-eKMQDeywY2wlHc97k5eD8VC+9ASMjN8ItEZQNGwJ6E0XWKiW/Z0V5/H8pvoXUf+y+Mj0VIgeRRbujBmFn4FTyA==} + mdast-util-mdx-jsx@3.1.2: dependencies: '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.4 @@ -5514,10 +7661,8 @@ packages: vfile-message: 4.0.2 transitivePeerDependencies: - supports-color - dev: true - /mdast-util-mdx@3.0.0: - resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + mdast-util-mdx@3.0.0: dependencies: mdast-util-from-markdown: 2.0.0 mdast-util-mdx-expression: 2.0.0 @@ -5526,10 +7671,8 @@ packages: mdast-util-to-markdown: 2.1.0 transitivePeerDependencies: - supports-color - dev: true - /mdast-util-mdxjs-esm@2.0.1: - resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + mdast-util-mdxjs-esm@2.0.1: dependencies: '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.4 @@ -5539,16 +7682,13 @@ packages: mdast-util-to-markdown: 2.1.0 transitivePeerDependencies: - supports-color - dev: true - /mdast-util-phrasing@4.1.0: - resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + mdast-util-phrasing@4.1.0: dependencies: '@types/mdast': 4.0.3 unist-util-is: 6.0.0 - /mdast-util-to-hast@13.1.0: - resolution: {integrity: sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA==} + mdast-util-to-hast@13.1.0: dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.3 @@ -5559,10 +7699,8 @@ packages: unist-util-position: 5.0.0 unist-util-visit: 5.0.0 vfile: 6.0.1 - dev: true - /mdast-util-to-markdown@2.1.0: - resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==} + mdast-util-to-markdown@2.1.0: dependencies: '@types/mdast': 4.0.3 '@types/unist': 3.0.2 @@ -5573,25 +7711,17 @@ packages: unist-util-visit: 5.0.0 zwitch: 2.0.4 - /mdast-util-to-string@4.0.0: - resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdast-util-to-string@4.0.0: dependencies: '@types/mdast': 4.0.3 - /mdurl@1.0.1: - resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} - dev: false + mdurl@1.0.1: {} - /merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: true + merge-stream@2.0.0: {} - /merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} + merge2@1.4.1: {} - /micromark-core-commonmark@2.0.0: - resolution: {integrity: sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==} + micromark-core-commonmark@2.0.0: dependencies: decode-named-character-reference: 1.0.2 devlop: 1.1.0 @@ -5610,8 +7740,7 @@ packages: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - /micromark-extension-directive@3.0.0: - resolution: {integrity: sha512-61OI07qpQrERc+0wEysLHMvoiO3s2R56x5u7glHq2Yqq6EHbH4dW25G9GfDdGCDYqA21KE6DWgNSzxSwHc2hSg==} + micromark-extension-directive@3.0.0: dependencies: devlop: 1.1.0 micromark-factory-space: 2.0.0 @@ -5620,18 +7749,15 @@ packages: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 parse-entities: 4.0.1 - dev: true - /micromark-extension-gfm-autolink-literal@2.0.0: - resolution: {integrity: sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==} + micromark-extension-gfm-autolink-literal@2.0.0: dependencies: micromark-util-character: 2.1.0 micromark-util-sanitize-uri: 2.0.0 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - /micromark-extension-gfm-footnote@2.0.0: - resolution: {integrity: sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==} + micromark-extension-gfm-footnote@2.0.0: dependencies: devlop: 1.1.0 micromark-core-commonmark: 2.0.0 @@ -5642,8 +7768,7 @@ packages: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - /micromark-extension-gfm-strikethrough@2.0.0: - resolution: {integrity: sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==} + micromark-extension-gfm-strikethrough@2.0.0: dependencies: devlop: 1.1.0 micromark-util-chunked: 2.0.0 @@ -5652,8 +7777,7 @@ packages: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - /micromark-extension-gfm-table@2.0.0: - resolution: {integrity: sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==} + micromark-extension-gfm-table@2.0.0: dependencies: devlop: 1.1.0 micromark-factory-space: 2.0.0 @@ -5661,13 +7785,11 @@ packages: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - /micromark-extension-gfm-tagfilter@2.0.0: - resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + micromark-extension-gfm-tagfilter@2.0.0: dependencies: micromark-util-types: 2.0.0 - /micromark-extension-gfm-task-list-item@2.0.1: - resolution: {integrity: sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==} + micromark-extension-gfm-task-list-item@2.0.1: dependencies: devlop: 1.1.0 micromark-factory-space: 2.0.0 @@ -5675,8 +7797,7 @@ packages: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - /micromark-extension-gfm@3.0.0: - resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + micromark-extension-gfm@3.0.0: dependencies: micromark-extension-gfm-autolink-literal: 2.0.0 micromark-extension-gfm-footnote: 2.0.0 @@ -5687,8 +7808,7 @@ packages: micromark-util-combine-extensions: 2.0.0 micromark-util-types: 2.0.0 - /micromark-extension-mdx-expression@3.0.0: - resolution: {integrity: sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==} + micromark-extension-mdx-expression@3.0.0: dependencies: '@types/estree': 1.0.5 devlop: 1.1.0 @@ -5698,10 +7818,8 @@ packages: micromark-util-events-to-acorn: 2.0.2 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - dev: true - /micromark-extension-mdx-jsx@3.0.0: - resolution: {integrity: sha512-uvhhss8OGuzR4/N17L1JwvmJIpPhAd8oByMawEKx6NVdBCbesjH4t+vjEp3ZXft9DwvlKSD07fCeI44/N0Vf2w==} + micromark-extension-mdx-jsx@3.0.0: dependencies: '@types/acorn': 4.0.6 '@types/estree': 1.0.5 @@ -5713,16 +7831,12 @@ packages: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 vfile-message: 4.0.2 - dev: true - /micromark-extension-mdx-md@2.0.0: - resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + micromark-extension-mdx-md@2.0.0: dependencies: micromark-util-types: 2.0.0 - dev: true - /micromark-extension-mdxjs-esm@3.0.0: - resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + micromark-extension-mdxjs-esm@3.0.0: dependencies: '@types/estree': 1.0.5 devlop: 1.1.0 @@ -5733,10 +7847,8 @@ packages: micromark-util-types: 2.0.0 unist-util-position-from-estree: 2.0.0 vfile-message: 4.0.2 - dev: true - /micromark-extension-mdxjs@3.0.0: - resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + micromark-extension-mdxjs@3.0.0: dependencies: acorn: 8.11.3 acorn-jsx: 5.3.2(acorn@8.11.3) @@ -5746,25 +7858,21 @@ packages: micromark-extension-mdxjs-esm: 3.0.0 micromark-util-combine-extensions: 2.0.0 micromark-util-types: 2.0.0 - dev: true - /micromark-factory-destination@2.0.0: - resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==} + micromark-factory-destination@2.0.0: dependencies: micromark-util-character: 2.1.0 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - /micromark-factory-label@2.0.0: - resolution: {integrity: sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==} + micromark-factory-label@2.0.0: dependencies: devlop: 1.1.0 micromark-util-character: 2.1.0 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - /micromark-factory-mdx-expression@2.0.1: - resolution: {integrity: sha512-F0ccWIUHRLRrYp5TC9ZYXmZo+p2AM13ggbsW4T0b5CRKP8KHVRB8t4pwtBgTxtjRmwrK0Irwm7vs2JOZabHZfg==} + micromark-factory-mdx-expression@2.0.1: dependencies: '@types/estree': 1.0.5 devlop: 1.1.0 @@ -5774,72 +7882,60 @@ packages: micromark-util-types: 2.0.0 unist-util-position-from-estree: 2.0.0 vfile-message: 4.0.2 - dev: true - /micromark-factory-space@2.0.0: - resolution: {integrity: sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==} + micromark-factory-space@2.0.0: dependencies: micromark-util-character: 2.1.0 micromark-util-types: 2.0.0 - /micromark-factory-title@2.0.0: - resolution: {integrity: sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==} + micromark-factory-title@2.0.0: dependencies: micromark-factory-space: 2.0.0 micromark-util-character: 2.1.0 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - /micromark-factory-whitespace@2.0.0: - resolution: {integrity: sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==} + micromark-factory-whitespace@2.0.0: dependencies: micromark-factory-space: 2.0.0 micromark-util-character: 2.1.0 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - /micromark-util-character@2.1.0: - resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==} + micromark-util-character@2.1.0: dependencies: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - /micromark-util-chunked@2.0.0: - resolution: {integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==} + micromark-util-chunked@2.0.0: dependencies: micromark-util-symbol: 2.0.0 - /micromark-util-classify-character@2.0.0: - resolution: {integrity: sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==} + micromark-util-classify-character@2.0.0: dependencies: micromark-util-character: 2.1.0 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - /micromark-util-combine-extensions@2.0.0: - resolution: {integrity: sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==} + micromark-util-combine-extensions@2.0.0: dependencies: micromark-util-chunked: 2.0.0 micromark-util-types: 2.0.0 - /micromark-util-decode-numeric-character-reference@2.0.1: - resolution: {integrity: sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==} + micromark-util-decode-numeric-character-reference@2.0.1: dependencies: micromark-util-symbol: 2.0.0 - /micromark-util-decode-string@2.0.0: - resolution: {integrity: sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==} + micromark-util-decode-string@2.0.0: dependencies: decode-named-character-reference: 1.0.2 micromark-util-character: 2.1.0 micromark-util-decode-numeric-character-reference: 2.0.1 micromark-util-symbol: 2.0.0 - /micromark-util-encode@2.0.0: - resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} + micromark-util-encode@2.0.0: {} - /micromark-util-events-to-acorn@2.0.2: - resolution: {integrity: sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA==} + micromark-util-events-to-acorn@2.0.2: dependencies: '@types/acorn': 4.0.6 '@types/estree': 1.0.5 @@ -5849,44 +7945,35 @@ packages: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 vfile-message: 4.0.2 - dev: true - /micromark-util-html-tag-name@2.0.0: - resolution: {integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==} + micromark-util-html-tag-name@2.0.0: {} - /micromark-util-normalize-identifier@2.0.0: - resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==} + micromark-util-normalize-identifier@2.0.0: dependencies: micromark-util-symbol: 2.0.0 - /micromark-util-resolve-all@2.0.0: - resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==} + micromark-util-resolve-all@2.0.0: dependencies: micromark-util-types: 2.0.0 - /micromark-util-sanitize-uri@2.0.0: - resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} + micromark-util-sanitize-uri@2.0.0: dependencies: micromark-util-character: 2.1.0 micromark-util-encode: 2.0.0 micromark-util-symbol: 2.0.0 - /micromark-util-subtokenize@2.0.0: - resolution: {integrity: sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==} + micromark-util-subtokenize@2.0.0: dependencies: devlop: 1.1.0 micromark-util-chunked: 2.0.0 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - /micromark-util-symbol@2.0.0: - resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} + micromark-util-symbol@2.0.0: {} - /micromark-util-types@2.0.0: - resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} + micromark-util-types@2.0.0: {} - /micromark@4.0.0: - resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} + micromark@4.0.0: dependencies: '@types/debug': 4.1.12 debug: 4.3.4 @@ -5908,272 +7995,159 @@ packages: transitivePeerDependencies: - supports-color - /micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} + micromatch@4.0.5: dependencies: braces: 3.0.2 picomatch: 2.3.1 - /mime@3.0.0: - resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} - engines: {node: '>=10.0.0'} - hasBin: true - dev: true + mime@3.0.0: {} - /mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - dev: true + mimic-fn@2.1.0: {} - /mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - dev: true + mimic-fn@4.0.0: {} - /mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - requiresBuild: true - dev: true + mimic-response@3.1.0: optional: true - /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 - /minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} + minimatch@5.1.6: dependencies: brace-expansion: 2.0.1 - dev: true - /minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} - engines: {node: '>=16 || 14 >=14.17'} + minimatch@9.0.3: dependencies: brace-expansion: 2.0.1 - /minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - requiresBuild: true - dev: true + minimist@1.2.8: optional: true - /mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - requiresBuild: true - dev: true + mkdirp-classic@0.5.3: optional: true - /mkdirp2@1.0.5: - resolution: {integrity: sha512-xOE9xbICroUDmG1ye2h4bZ8WBie9EGmACaco8K8cx6RlkJJrxGIqjGqztAI+NMhexXBcdGbSEzI6N3EJPevxZw==} - dev: false + mkdirp2@1.0.5: {} - /mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true + mkdirp@1.0.4: {} - /mlly@1.6.1: - resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==} + mlly@1.6.1: dependencies: acorn: 8.11.3 pathe: 1.1.2 pkg-types: 1.0.3 ufo: 1.5.2 - dev: true - /ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ms@2.1.2: {} - /muggle-string@0.4.1: - resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - dev: false + muggle-string@0.4.1: {} - /nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: true + nanoid@3.3.7: {} - /nanostores@0.10.0: - resolution: {integrity: sha512-Poy5+9wFXOD0jAstn4kv9n686U2BFw48z/W8lms8cS8lcbRz7BU20JxZ3e/kkKQVfRrkm4yLWCUA6GQINdvJCQ==} - engines: {node: ^18.0.0 || >=20.0.0} - dev: false + nanostores@0.10.0: {} - /napi-build-utils@1.0.2: - resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} - requiresBuild: true - dev: true + napi-build-utils@1.0.2: optional: true - /natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - dev: true + natural-compare@1.4.0: {} - /nlcst-to-string@3.1.1: - resolution: {integrity: sha512-63mVyqaqt0cmn2VcI2aH6kxe1rLAmSROqHMA0i4qqg1tidkfExgpb0FGMikMCn86mw5dFtBtEANfmSSK7TjNHw==} + nlcst-to-string@3.1.1: dependencies: '@types/nlcst': 1.0.4 - /node-abi@3.56.0: - resolution: {integrity: sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q==} - engines: {node: '>=10'} - requiresBuild: true + nlcst-to-string@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + + node-abi@3.56.0: dependencies: semver: 7.6.0 - dev: true optional: true - /node-addon-api@6.1.0: - resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} - requiresBuild: true - dev: true + node-addon-api@6.1.0: optional: true - /node-domexception@1.0.0: - resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} - engines: {node: '>=10.5.0'} - dev: true + node-domexception@1.0.0: {} - /node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 - dev: true - /node-fetch@3.3.2: - resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-fetch@3.3.2: dependencies: data-uri-to-buffer: 4.0.1 fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 - dev: true - /node-html-parser@6.1.12: - resolution: {integrity: sha512-/bT/Ncmv+fbMGX96XG9g05vFt43m/+SYKIs9oAemQVYyVcZmDAI2Xq/SbNcpOA35eF0Zk2av3Ksf+Xk8Vt8abA==} + node-html-parser@6.1.12: dependencies: css-select: 5.1.0 he: 1.2.0 - dev: true - /node-releases@2.0.14: - resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} - dev: true + node-releases@2.0.14: {} - /normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} + normalize-path@3.0.0: {} - /npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + npm-run-path@5.3.0: dependencies: path-key: 4.0.0 - dev: true - /nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nth-check@2.1.1: dependencies: boolbase: 1.0.0 - dev: true - /object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - dev: true + object-assign@4.1.1: {} - /object-inspect@1.13.1: - resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} - dev: true + object-inspect@1.13.1: {} - /object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - dev: true + object-keys@1.1.1: {} - /object-to-spawn-args@2.0.1: - resolution: {integrity: sha512-6FuKFQ39cOID+BMZ3QaphcC8Y4cw6LXBLyIgPU+OhIYwviJamPAn+4mITapnSBQrejB+NNp+FMskhD8Cq+Ys3w==} - engines: {node: '>=8.0.0'} - dev: false + object-to-spawn-args@2.0.1: {} - /object.assign@4.1.5: - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} - engines: {node: '>= 0.4'} + object.assign@4.1.5: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 has-symbols: 1.0.3 object-keys: 1.1.1 - dev: true - /object.entries@1.1.8: - resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} - engines: {node: '>= 0.4'} + object.entries@1.1.8: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-object-atoms: 1.0.0 - dev: true - /object.fromentries@2.0.8: - resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} - engines: {node: '>= 0.4'} + object.fromentries@2.0.8: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.23.2 es-object-atoms: 1.0.0 - dev: true - /object.hasown@1.1.3: - resolution: {integrity: sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==} + object.hasown@1.1.3: dependencies: define-properties: 1.2.1 es-abstract: 1.22.5 - dev: true - /object.values@1.2.0: - resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} - engines: {node: '>= 0.4'} + object.values@1.2.0: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-object-atoms: 1.0.0 - dev: true - /once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + once@1.4.0: dependencies: wrappy: 1.0.2 - /onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 - dev: true - /onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} + onetime@6.0.0: dependencies: mimic-fn: 4.0.0 - dev: true - /optionator@0.9.3: - resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} - engines: {node: '>= 0.8.0'} + optionator@0.9.3: dependencies: '@aashutoshrathi/word-wrap': 1.2.6 deep-is: 0.1.4 @@ -6181,11 +8155,8 @@ packages: levn: 0.4.1 prelude-ls: 1.2.1 type-check: 0.4.0 - dev: true - /ora@7.0.1: - resolution: {integrity: sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==} - engines: {node: '>=16'} + ora@7.0.1: dependencies: chalk: 5.3.0 cli-cursor: 4.0.0 @@ -6196,109 +8167,65 @@ packages: stdin-discarder: 0.1.0 string-width: 6.1.0 strip-ansi: 7.1.0 - dev: true - /organize-imports-cli@0.10.0: - resolution: {integrity: sha512-cVyNEeiDxX/zA6gdK1QS2rr3TK1VymIkT0LagnAk4f6eE0IC0bo3BeUkMzm3q3GnCJzYC+6lfuMpBE0Cequ7Vg==} - hasBin: true + organize-imports-cli@0.10.0: dependencies: chalk: 4.1.2 editorconfig: 0.15.3 ts-morph: 15.1.0 tsconfig: 7.0.0 - dev: true - /p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} - engines: {node: '>=4'} - dev: true + p-finally@1.0.0: {} - /p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} + p-limit@2.3.0: dependencies: p-try: 2.2.0 - dev: true - /p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 - dev: true - /p-limit@5.0.0: - resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} - engines: {node: '>=18'} + p-limit@5.0.0: dependencies: yocto-queue: 1.0.0 - dev: true - /p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} + p-locate@4.1.0: dependencies: p-limit: 2.3.0 - dev: true - /p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + p-locate@5.0.0: dependencies: p-limit: 3.1.0 - dev: true - /p-queue@6.6.2: - resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} - engines: {node: '>=8'} + p-queue@6.6.2: dependencies: eventemitter3: 4.0.7 p-timeout: 3.2.0 - dev: true - /p-queue@8.0.1: - resolution: {integrity: sha512-NXzu9aQJTAzbBqOt2hwsR63ea7yvxJc0PwN/zobNAudYfb1B7R08SzB4TsLeSbUCuG467NhnoT0oO6w1qRO+BA==} - engines: {node: '>=18'} + p-queue@8.0.1: dependencies: eventemitter3: 5.0.1 p-timeout: 6.1.2 - dev: true - /p-retry@6.2.0: - resolution: {integrity: sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==} - engines: {node: '>=16.17'} + p-retry@6.2.0: dependencies: '@types/retry': 0.12.2 is-network-error: 1.1.0 retry: 0.13.1 - dev: true - /p-timeout@3.2.0: - resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} - engines: {node: '>=8'} + p-timeout@3.2.0: dependencies: p-finally: 1.0.0 - dev: true - /p-timeout@6.1.2: - resolution: {integrity: sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==} - engines: {node: '>=14.16'} - dev: true + p-timeout@6.1.2: {} - /p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - dev: true + p-try@2.2.0: {} - /parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} + parent-module@1.0.1: dependencies: callsites: 3.1.0 - dev: true - /parse-entities@4.0.1: - resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==} + parse-entities@4.0.1: dependencies: '@types/unist': 2.0.10 character-entities: 2.0.2 @@ -6308,184 +8235,112 @@ packages: is-alphanumerical: 2.0.1 is-decimal: 2.0.1 is-hexadecimal: 2.0.1 - dev: true - /parse-latin@5.0.1: - resolution: {integrity: sha512-b/K8ExXaWC9t34kKeDV8kGXBkXZ1HCSAZRYE7HR14eA1GlXX5L8iWhs8USJNhQU9q5ci413jCKF0gOyovvyRBg==} + parse-latin@5.0.1: dependencies: nlcst-to-string: 3.1.1 unist-util-modify-children: 3.1.1 unist-util-visit-children: 2.0.2 - /parse-numeric-range@1.3.0: - resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==} - dev: true + parse-latin@7.0.0: + dependencies: + '@types/nlcst': 2.0.3 + '@types/unist': 3.0.2 + nlcst-to-string: 4.0.0 + unist-util-modify-children: 4.0.0 + unist-util-visit-children: 3.0.0 + vfile: 6.0.1 - /parse5@6.0.1: - resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} - dev: true + parse-numeric-range@1.3.0: {} - /parse5@7.1.2: - resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + parse5@6.0.1: {} + + parse5@7.1.2: dependencies: entities: 4.5.0 - dev: true - /path-browserify@1.0.1: - resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-browserify@1.0.1: {} - /path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - dev: true + path-exists@4.0.0: {} - /path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} + path-is-absolute@1.0.1: {} - /path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - dev: true + path-key@3.1.1: {} - /path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - dev: true + path-key@4.0.0: {} - /path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: true + path-parse@1.0.7: {} - /path-to-regexp@6.2.1: - resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} - dev: true + path-to-regexp@6.2.1: {} - /path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - dev: true + path-type@4.0.0: {} - /pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - dev: true + pathe@1.1.2: {} - /pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - dev: true + pathval@1.1.1: {} - /periscopic@3.1.0: - resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + periscopic@3.1.0: dependencies: '@types/estree': 1.0.5 estree-walker: 3.0.3 is-reference: 3.0.2 - dev: true - /picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: true + picocolors@1.0.0: {} - /picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} + picomatch@2.3.1: {} - /pidtree@0.6.0: - resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} - engines: {node: '>=0.10'} - hasBin: true - dev: true + pidtree@0.6.0: {} - /pify@4.0.1: - resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} - engines: {node: '>=6'} - dev: true + pify@4.0.1: {} - /pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} + pkg-dir@4.2.0: dependencies: find-up: 4.1.0 - dev: true - /pkg-types@1.0.3: - resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + pkg-types@1.0.3: dependencies: jsonc-parser: 3.2.1 mlly: 1.6.1 pathe: 1.1.2 - dev: true - /plantuml-encoder@1.4.0: - resolution: {integrity: sha512-sxMwpDw/ySY1WB2CE3+IdMuEcWibJ72DDOsXLkSmEaSzwEUaYBT6DWgOfBiHGCux4q433X6+OEFWjlVqp7gL6g==} - dev: false + plantuml-encoder@1.4.0: {} - /possible-typed-array-names@1.0.0: - resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} - engines: {node: '>= 0.4'} - dev: true + possible-typed-array-names@1.0.0: {} - /postcss-nested@6.0.1(postcss@8.4.37): - resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.2.14 + postcss-nested@6.0.1(postcss@8.4.37): dependencies: postcss: 8.4.37 postcss-selector-parser: 6.0.16 - dev: true - /postcss-selector-parser@6.0.16: - resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==} - engines: {node: '>=4'} + postcss-selector-parser@6.0.16: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - dev: true - /postcss@8.4.37: - resolution: {integrity: sha512-7iB/v/r7Woof0glKLH8b1SPHrsX7uhdO+Geb41QpF/+mWZHU3uxxSlN+UXGVit1PawOYDToO+AbZzhBzWRDwbQ==} - engines: {node: ^10 || ^12 || >=14} + postcss@8.4.37: dependencies: nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.2.0 - dev: true - /postcss@8.4.38: - resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} - engines: {node: ^10 || ^12 || >=14} + postcss@8.4.38: dependencies: nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.2.0 - dev: true - /preact-render-to-string@6.3.1(preact@10.19.7): - resolution: {integrity: sha512-NQ28WrjLtWY6lKDlTxnFpKHZdpjfF+oE6V4tZ0rTrunHrtZp6Dm0oFrcJalt/5PNeqJz4j1DuZDS0Y6rCBoqDA==} - peerDependencies: - preact: '>=10' + preact-render-to-string@6.3.1(preact@10.19.7): dependencies: preact: 10.19.7 pretty-format: 3.8.0 - dev: true - /preact-ssr-prepass@1.2.1(preact@10.19.7): - resolution: {integrity: sha512-bLgbUfy8nL+PZghAPpyk9MF+cmXjdwEnxYPaJBmwbzFQqzIz8dQVBqjwB60RqZ9So/vIf6BRfHCiwFGuMCyfbQ==} - peerDependencies: - preact: '>=10 || ^10.0.0-beta.0 || ^10.0.0-alpha.0' + preact-ssr-prepass@1.2.1(preact@10.19.7): dependencies: preact: 10.19.7 - dev: true - - /preact@10.19.7: - resolution: {integrity: sha512-IJOW6cQN1fwfC17HfNOqUtAGyB8wAYshuC+jG1JiL/1+sC4yVyuA3IcF0N9vdodMJjW/lbuEF5qFsJqGNcbHbw==} - /prebuild-install@7.1.2: - resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} - engines: {node: '>=10'} - hasBin: true - requiresBuild: true + preact@10.19.7: {} + + prebuild-install@7.1.2: dependencies: detect-libc: 2.0.3 expand-template: 2.0.3 @@ -6499,140 +8354,86 @@ packages: simple-get: 4.0.1 tar-fs: 2.1.1 tunnel-agent: 0.6.0 - dev: true optional: true - /preferred-pm@3.1.3: - resolution: {integrity: sha512-MkXsENfftWSRpzCzImcp4FRsCc3y1opwB73CfCNWyzMqArju2CrlMHlqB7VexKiPEOjGMbttv1r9fSCn5S610w==} - engines: {node: '>=10'} + preferred-pm@3.1.3: dependencies: find-up: 5.0.0 find-yarn-workspace-root2: 1.2.16 path-exists: 4.0.0 which-pm: 2.0.0 - dev: true - /prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - dev: true + prelude-ls@1.2.1: {} - /prettier-plugin-astro@0.13.0: - resolution: {integrity: sha512-5HrJNnPmZqTUNoA97zn4gNQv9BgVhv+et03314WpQ9H9N8m2L9OSV798olwmG2YLXPl1iSstlJCR1zB3x5xG4g==} - engines: {node: ^14.15.0 || >=16.0.0} + prettier-plugin-astro@0.13.0: dependencies: '@astrojs/compiler': 1.8.2 prettier: 3.2.5 sass-formatter: 0.7.9 - /prettier@3.2.5: - resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} - engines: {node: '>=14'} - hasBin: true + prettier@3.2.5: {} - /pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 react-is: 18.2.0 - dev: true - /pretty-format@3.8.0: - resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} - dev: true + pretty-format@3.8.0: {} - /prismjs@1.29.0: - resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} - engines: {node: '>=6'} - dev: true + prismjs@1.29.0: {} - /prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} + prompts@2.4.2: dependencies: kleur: 3.0.3 sisteransi: 1.0.5 - dev: true - /prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 - dev: true - /property-information@6.4.1: - resolution: {integrity: sha512-OHYtXfu5aI2sS2LWFSN5rgJjrQ4pCy8i1jubJLe2QvMF8JJ++HXTUIVWFLfXJoaOfvYYjk2SN8J2wFUWIGXT4w==} - dev: true + property-information@6.4.1: {} - /pseudomap@1.0.2: - resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} - dev: true + pseudomap@1.0.2: {} - /pump@3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} - requiresBuild: true + pump@3.0.0: dependencies: end-of-stream: 1.4.4 once: 1.4.0 - dev: true optional: true - /punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - dev: true + punycode@2.3.1: {} - /queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + queue-microtask@1.2.3: {} - /queue-tick@1.0.1: - resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} - requiresBuild: true - dev: true + queue-tick@1.0.1: optional: true - /rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true - requiresBuild: true + rc@1.2.8: dependencies: deep-extend: 0.6.0 ini: 1.3.8 minimist: 1.2.8 strip-json-comments: 2.0.1 - dev: true optional: true - /react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - dev: true + react-is@16.13.1: {} - /react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - dev: true + react-is@18.2.0: {} - /readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} + readable-stream@3.6.2: dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 - dev: true - /readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} + readdirp@3.6.0: dependencies: picomatch: 2.3.1 - /reflect.getprototypeof@1.0.6: - resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} - engines: {node: '>= 0.4'} + reflect.getprototypeof@1.0.6: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 @@ -6641,20 +8442,15 @@ packages: get-intrinsic: 1.2.4 globalthis: 1.0.3 which-builtin-type: 1.1.3 - dev: true - /regexp.prototype.flags@1.5.2: - resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} - engines: {node: '>= 0.4'} + regexp.prototype.flags@1.5.2: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-errors: 1.3.0 set-function-name: 2.0.2 - dev: true - /rehype-autolink-headings@7.1.0: - resolution: {integrity: sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==} + rehype-autolink-headings@7.1.0: dependencies: '@types/hast': 3.0.4 '@ungap/structured-clone': 1.2.0 @@ -6662,53 +8458,41 @@ packages: hast-util-is-element: 3.0.0 unified: 11.0.4 unist-util-visit: 5.0.0 - dev: false - /rehype-parse@9.0.0: - resolution: {integrity: sha512-WG7nfvmWWkCR++KEkZevZb/uw41E8TsH4DsY9UxsTbIXCVGbAs4S+r8FrQ+OtH5EEQAs+5UxKC42VinkmpA1Yw==} + rehype-parse@9.0.0: dependencies: '@types/hast': 3.0.4 hast-util-from-html: 2.0.1 unified: 11.0.4 - dev: true - /rehype-raw@7.0.0: - resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + rehype-raw@7.0.0: dependencies: '@types/hast': 3.0.4 hast-util-raw: 9.0.2 vfile: 6.0.1 - dev: true - /rehype-slug@6.0.0: - resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==} + rehype-slug@6.0.0: dependencies: '@types/hast': 3.0.4 github-slugger: 2.0.0 hast-util-heading-rank: 3.0.0 hast-util-to-string: 3.0.0 unist-util-visit: 5.0.0 - dev: false - /rehype-stringify@10.0.0: - resolution: {integrity: sha512-1TX1i048LooI9QoecrXy7nGFFbFSufxVRAfc6Y9YMRAi56l+oB0zP51mLSV312uRuvVLPV1opSlJmslozR1XHQ==} + rehype-stringify@10.0.0: dependencies: '@types/hast': 3.0.4 hast-util-to-html: 9.0.0 unified: 11.0.4 - dev: true - /rehype@13.0.1: - resolution: {integrity: sha512-AcSLS2mItY+0fYu9xKxOu1LhUZeBZZBx8//5HKzF+0XP+eP8+6a5MXn2+DW2kfXR6Dtp1FEXMVrjyKAcvcU8vg==} + rehype@13.0.1: dependencies: '@types/hast': 3.0.4 rehype-parse: 9.0.0 rehype-stringify: 10.0.0 unified: 11.0.4 - dev: true - /remark-directive@3.0.0: - resolution: {integrity: sha512-l1UyWJ6Eg1VPU7Hm/9tt0zKtReJQNOA4+iDMAxTyZNWnJnFlbS/7zhiel/rogTLQ2vMYwDzSJa4BiVNqGlqIMA==} + remark-directive@3.0.0: dependencies: '@types/mdast': 4.0.3 mdast-util-directive: 3.0.0 @@ -6716,18 +8500,14 @@ packages: unified: 11.0.4 transitivePeerDependencies: - supports-color - dev: true - /remark-expressive-code@0.33.5: - resolution: {integrity: sha512-E4CZq3AuUXLu6or0AaDKkgsHYqmnm4ZL8/+1/8YgwtKcogHwTMRJfQtxkZpth90QQoNUpsapvm5x5n3Np2OC9w==} + remark-expressive-code@0.33.5: dependencies: expressive-code: 0.33.5 hast-util-to-html: 8.0.4 unist-util-visit: 4.1.2 - dev: true - /remark-gfm@4.0.0: - resolution: {integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==} + remark-gfm@4.0.0: dependencies: '@types/mdast': 4.0.3 mdast-util-gfm: 3.0.0 @@ -6738,17 +8518,14 @@ packages: transitivePeerDependencies: - supports-color - /remark-mdx@3.0.1: - resolution: {integrity: sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==} + remark-mdx@3.0.1: dependencies: mdast-util-mdx: 3.0.0 micromark-extension-mdxjs: 3.0.0 transitivePeerDependencies: - supports-color - dev: true - /remark-parse@11.0.0: - resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.3 mdast-util-from-markdown: 2.0.0 @@ -6757,33 +8534,34 @@ packages: transitivePeerDependencies: - supports-color - /remark-rehype@11.1.0: - resolution: {integrity: sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==} + remark-rehype@11.1.0: dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.3 mdast-util-to-hast: 13.1.0 unified: 11.0.4 vfile: 6.0.1 - dev: true - /remark-smartypants@2.1.0: - resolution: {integrity: sha512-qoF6Vz3BjU2tP6OfZqHOvCU0ACmu/6jhGaINSQRI9mM7wCxNQTKB3JUAN4SVoN2ybElEDTxBIABRep7e569iJw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + remark-smartypants@2.1.0: dependencies: retext: 8.1.0 retext-smartypants: 5.2.0 unist-util-visit: 5.0.0 - /remark-stringify@11.0.0: - resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + remark-smartypants@3.0.1: + dependencies: + retext: 9.0.0 + retext-smartypants: 6.1.0 + unified: 11.0.4 + unist-util-visit: 5.0.0 + + remark-stringify@11.0.0: dependencies: '@types/mdast': 4.0.3 mdast-util-to-markdown: 2.1.0 unified: 11.0.4 - /remark@15.0.1: - resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==} + remark@15.0.1: dependencies: '@types/mdast': 4.0.3 remark-parse: 11.0.0 @@ -6791,113 +8569,99 @@ packages: unified: 11.0.4 transitivePeerDependencies: - supports-color - dev: true - /remove-markdown@0.5.0: - resolution: {integrity: sha512-x917M80K97K5IN1L8lUvFehsfhR8cYjGQ/yAMRI9E7JIKivtl5Emo5iD13DhMr+VojzMCiYk8V2byNPwT/oapg==} - dev: true + remove-markdown@0.5.0: {} - /request-light@0.7.0: - resolution: {integrity: sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==} - dev: false + request-light@0.7.0: {} - /require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - dev: false + require-directory@2.1.1: {} - /requizzle@0.2.4: - resolution: {integrity: sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==} + requizzle@0.2.4: dependencies: lodash: 4.17.21 - dev: false - /resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - dev: true + resolve-from@4.0.0: {} - /resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} - hasBin: true + resolve@1.22.8: dependencies: is-core-module: 2.13.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: true - /resolve@2.0.0-next.5: - resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} - hasBin: true + resolve@2.0.0-next.5: dependencies: is-core-module: 2.13.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: true - /restore-cursor@4.0.0: - resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + restore-cursor@4.0.0: dependencies: onetime: 5.1.2 signal-exit: 3.0.7 - dev: true - /retext-latin@3.1.0: - resolution: {integrity: sha512-5MrD1tuebzO8ppsja5eEu+ZbBeUNCjoEarn70tkXOS7Bdsdf6tNahsv2bY0Z8VooFF6cw7/6S+d3yI/TMlMVVQ==} + retext-latin@3.1.0: dependencies: '@types/nlcst': 1.0.4 parse-latin: 5.0.1 unherit: 3.0.1 unified: 10.1.2 - /retext-smartypants@5.2.0: - resolution: {integrity: sha512-Do8oM+SsjrbzT2UNIKgheP0hgUQTDDQYyZaIY3kfq0pdFzoPk+ZClYJ+OERNXveog4xf1pZL4PfRxNoVL7a/jw==} + retext-latin@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + parse-latin: 7.0.0 + unified: 11.0.4 + + retext-smartypants@5.2.0: dependencies: '@types/nlcst': 1.0.4 nlcst-to-string: 3.1.1 unified: 10.1.2 unist-util-visit: 4.1.2 - /retext-stringify@3.1.0: - resolution: {integrity: sha512-767TLOaoXFXyOnjx/EggXlb37ZD2u4P1n0GJqVdpipqACsQP+20W+BNpMYrlJkq7hxffnFk+jc6mAK9qrbuB8w==} + retext-smartypants@6.1.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unist-util-visit: 5.0.0 + + retext-stringify@3.1.0: dependencies: '@types/nlcst': 1.0.4 nlcst-to-string: 3.1.1 unified: 10.1.2 - /retext@8.1.0: - resolution: {integrity: sha512-N9/Kq7YTn6ZpzfiGW45WfEGJqFf1IM1q8OsRa1CGzIebCJBNCANDRmOrholiDRGKo/We7ofKR4SEvcGAWEMD3Q==} + retext-stringify@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unified: 11.0.4 + + retext@8.1.0: dependencies: '@types/nlcst': 1.0.4 retext-latin: 3.1.0 retext-stringify: 3.1.0 unified: 10.1.2 - /retry@0.13.1: - resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} - engines: {node: '>= 4'} - dev: true + retext@9.0.0: + dependencies: + '@types/nlcst': 2.0.3 + retext-latin: 4.0.0 + retext-stringify: 4.0.0 + unified: 11.0.4 - /reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + retry@0.13.1: {} - /rfdc@1.3.1: - resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==} - dev: true + reusify@1.0.4: {} - /rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true + rfdc@1.3.1: {} + + rimraf@3.0.2: dependencies: glob: 7.2.3 - dev: true - /rollup@4.13.0: - resolution: {integrity: sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true + rollup@4.13.0: dependencies: '@types/estree': 1.0.5 optionalDependencies: @@ -6915,90 +8679,56 @@ packages: '@rollup/rollup-win32-ia32-msvc': 4.13.0 '@rollup/rollup-win32-x64-msvc': 4.13.0 fsevents: 2.3.3 - dev: true - /run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - /s.color@0.0.15: - resolution: {integrity: sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==} + s.color@0.0.15: {} - /safe-array-concat@1.1.2: - resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} - engines: {node: '>=0.4'} + safe-array-concat@1.1.2: dependencies: call-bind: 1.0.7 get-intrinsic: 1.2.4 has-symbols: 1.0.3 isarray: 2.0.5 - dev: true - /safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - requiresBuild: true - dev: true + safe-buffer@5.2.1: {} - /safe-regex-test@1.0.3: - resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} - engines: {node: '>= 0.4'} + safe-regex-test@1.0.3: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 is-regex: 1.1.4 - dev: true - /sass-formatter@0.7.9: - resolution: {integrity: sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==} + sass-formatter@0.7.9: dependencies: suf-log: 2.5.3 - /sass@1.72.0: - resolution: {integrity: sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==} - engines: {node: '>=14.0.0'} - hasBin: true + sass@1.72.0: dependencies: chokidar: 3.6.0 immutable: 4.3.5 source-map-js: 1.1.0 - /sax@1.3.0: - resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} - dev: true + sax@1.3.0: {} - /search-insights@2.13.0: - resolution: {integrity: sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==} - dev: true + search-insights@2.13.0: {} - /section-matter@1.0.0: - resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} - engines: {node: '>=4'} + section-matter@1.0.0: dependencies: extend-shallow: 2.0.1 kind-of: 6.0.3 - dev: true - /semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true - dev: true + semver@5.7.2: {} - /semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - dev: true + semver@6.3.1: {} - /semver@7.6.0: - resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} - engines: {node: '>=10'} - hasBin: true + semver@7.6.0: dependencies: lru-cache: 6.0.0 - /set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 @@ -7006,22 +8736,15 @@ packages: get-intrinsic: 1.2.4 gopd: 1.0.1 has-property-descriptors: 1.0.2 - dev: true - /set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} + set-function-name@2.0.2: dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 - dev: true - /sharp@0.32.6: - resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==} - engines: {node: '>=14.15.0'} - requiresBuild: true + sharp@0.32.6: dependencies: color: 4.2.3 detect-libc: 2.0.3 @@ -7031,237 +8754,144 @@ packages: simple-get: 4.0.1 tar-fs: 3.0.5 tunnel-agent: 0.6.0 - dev: true optional: true - /shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - dev: true - /shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - dev: true + shebang-regex@3.0.0: {} - /shiki@1.2.0: - resolution: {integrity: sha512-xLhiTMOIUXCv5DqJ4I70GgQCtdlzsTqFLZWcMHHG3TAieBUbvEGthdrlPDlX4mL/Wszx9C6rEcxU6kMlg4YlxA==} + shiki@1.2.0: dependencies: '@shikijs/core': 1.2.0 - dev: true - /shiki@1.2.1: - resolution: {integrity: sha512-u+XW6o0vCkUNlneZb914dLO+AayEIwK5tI62WeS//R5HIXBFiYaj/Hc5xcq27Yh83Grr4JbNtUBV8W6zyK4hWg==} + shiki@1.2.1: dependencies: '@shikijs/core': 1.2.1 - dev: true - /side-channel@1.0.6: - resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} - engines: {node: '>= 0.4'} + side-channel@1.0.6: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 get-intrinsic: 1.2.4 object-inspect: 1.13.1 - dev: true - /siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - dev: true + siginfo@2.0.0: {} - /sigmund@1.0.1: - resolution: {integrity: sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==} - dev: true + sigmund@1.0.1: {} - /signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - dev: true + signal-exit@3.0.7: {} - /signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - dev: true + signal-exit@4.1.0: {} - /simple-concat@1.0.1: - resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - requiresBuild: true - dev: true + simple-concat@1.0.1: optional: true - /simple-get@4.0.1: - resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - requiresBuild: true + simple-get@4.0.1: dependencies: decompress-response: 6.0.0 once: 1.4.0 simple-concat: 1.0.1 - dev: true optional: true - /simple-git@3.23.0: - resolution: {integrity: sha512-P9ggTW8vb/21CAL/AmnACAhqBDfnqSSZVpV7WuFtsFR9HLunf5IqQvk+OXAQTfkcZep8pKnt3DV3o7w3TegEkQ==} + simple-git@3.23.0: dependencies: '@kwsites/file-exists': 1.1.1 '@kwsites/promise-deferred': 1.1.1 debug: 4.3.4 transitivePeerDependencies: - supports-color - dev: true - /simple-swizzle@0.2.2: - resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} - requiresBuild: true + simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 - dev: true optional: true - /sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - dev: true + sisteransi@1.0.5: {} - /sitemap@7.1.1: - resolution: {integrity: sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==} - engines: {node: '>=12.0.0', npm: '>=5.6.0'} - hasBin: true + sitemap@7.1.1: dependencies: '@types/node': 17.0.45 '@types/sax': 1.2.7 arg: 5.0.2 sax: 1.3.0 - dev: true - /slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - dev: true + slash@3.0.0: {} - /slice-ansi@5.0.0: - resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} - engines: {node: '>=12'} + slice-ansi@5.0.0: dependencies: ansi-styles: 6.2.1 is-fullwidth-code-point: 4.0.0 - dev: true - /slice-ansi@7.1.0: - resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} - engines: {node: '>=18'} + slice-ansi@7.1.0: dependencies: ansi-styles: 6.2.1 is-fullwidth-code-point: 5.0.0 - dev: true - /source-map-js@1.1.0: - resolution: {integrity: sha512-9vC2SfsJzlej6MAaMPLu8HiBSHGdRAJ9hVFYN1ibZoNkeanmDmLUcIrj6G9DGL7XMJ54AKg/G75akXl1/izTOw==} - engines: {node: '>=0.10.0'} + source-map-js@1.1.0: {} - /source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} - engines: {node: '>=0.10.0'} - dev: true + source-map-js@1.2.0: {} - /source-map@0.7.4: - resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} - engines: {node: '>= 8'} - dev: true + source-map@0.7.4: {} - /space-separated-tokens@2.0.2: - resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - dev: true + space-separated-tokens@2.0.2: {} - /sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - dev: true + sprintf-js@1.0.3: {} - /stack-trace@1.0.0-pre2: - resolution: {integrity: sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==} - engines: {node: '>=16'} - dev: true + stack-trace@1.0.0-pre2: {} - /stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - dev: true + stackback@0.0.2: {} - /std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} - dev: true + std-env@3.7.0: {} - /stdin-discarder@0.1.0: - resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + stdin-discarder@0.1.0: dependencies: bl: 5.1.0 - dev: true - /stream-connect@1.0.2: - resolution: {integrity: sha512-68Kl+79cE0RGKemKkhxTSg8+6AGrqBt+cbZAXevg2iJ6Y3zX4JhA/sZeGzLpxW9cXhmqAcE7KnJCisUmIUfnFQ==} - engines: {node: '>=0.10.0'} + stream-connect@1.0.2: dependencies: array-back: 1.0.4 - dev: false - /stream-via@1.0.4: - resolution: {integrity: sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==} - engines: {node: '>=0.10.0'} - dev: false + stream-replace-string@2.0.0: {} - /streamx@2.16.1: - resolution: {integrity: sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==} - requiresBuild: true + stream-via@1.0.4: {} + + streamx@2.16.1: dependencies: fast-fifo: 1.3.2 queue-tick: 1.0.1 optionalDependencies: - bare-events: 2.2.1 - dev: true + bare-events: 2.2.2 optional: true - /string-argv@0.3.2: - resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} - engines: {node: '>=0.6.19'} - dev: true + string-argv@0.3.2: {} - /string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - /string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} + string-width@5.1.2: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 - dev: true - /string-width@6.1.0: - resolution: {integrity: sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==} - engines: {node: '>=16'} + string-width@6.1.0: dependencies: eastasianwidth: 0.2.0 emoji-regex: 10.3.0 strip-ansi: 7.1.0 - dev: true - /string-width@7.1.0: - resolution: {integrity: sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==} - engines: {node: '>=18'} + string-width@7.1.0: dependencies: emoji-regex: 10.3.0 get-east-asian-width: 1.2.0 strip-ansi: 7.1.0 - dev: true - /string.prototype.matchall@4.0.10: - resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} + string.prototype.matchall@4.0.10: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 @@ -7272,337 +8902,199 @@ packages: regexp.prototype.flags: 1.5.2 set-function-name: 2.0.2 side-channel: 1.0.6 - dev: true - /string.prototype.trim@1.2.9: - resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} - engines: {node: '>= 0.4'} + string.prototype.trim@1.2.9: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.23.2 es-object-atoms: 1.0.0 - dev: true - /string.prototype.trimend@1.0.8: - resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} + string.prototype.trimend@1.0.8: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-object-atoms: 1.0.0 - dev: true - /string.prototype.trimstart@1.0.7: - resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} + string.prototype.trimstart@1.0.7: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.22.5 - dev: true - /string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - requiresBuild: true + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 - dev: true - /stringify-entities@4.0.3: - resolution: {integrity: sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==} + stringify-entities@4.0.3: dependencies: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 - dev: true - /strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - /strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} + strip-ansi@7.1.0: dependencies: ansi-regex: 6.0.1 - dev: true - /strip-bom-string@1.0.0: - resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} - engines: {node: '>=0.10.0'} - dev: true + strip-bom-string@1.0.0: {} - /strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - dev: true + strip-bom@3.0.0: {} - /strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - dev: true + strip-final-newline@3.0.0: {} - /strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - dev: true + strip-json-comments@2.0.1: {} - /strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} + strip-json-comments@3.1.1: {} - /strip-literal@2.0.0: - resolution: {integrity: sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==} + strip-literal@2.0.0: dependencies: js-tokens: 8.0.3 - dev: true - /style-to-object@0.4.4: - resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} + style-to-object@0.4.4: dependencies: inline-style-parser: 0.1.1 - dev: true - /style-to-object@1.0.6: - resolution: {integrity: sha512-khxq+Qm3xEyZfKd/y9L3oIWQimxuc4STrQKtQn8aSDRHb8mFgpukgX1hdzfrMEW6JCjyJ8p89x+IUMVnCBI1PA==} + style-to-object@1.0.6: dependencies: inline-style-parser: 0.2.3 - dev: true - /suf-log@2.5.3: - resolution: {integrity: sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==} + suf-log@2.5.3: dependencies: s.color: 0.0.15 - /supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} + supports-color@5.5.0: dependencies: has-flag: 3.0.0 - dev: true - /supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 - dev: true - /supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - dev: true + supports-preserve-symlinks-flag@1.0.0: {} - /synckit@0.9.0: - resolution: {integrity: sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==} - engines: {node: ^14.18.0 || >=16.0.0} + synckit@0.9.0: dependencies: '@pkgr/core': 0.1.1 tslib: 2.6.2 - dev: true - /tar-fs@2.1.1: - resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} - requiresBuild: true + tar-fs@2.1.1: dependencies: chownr: 1.1.4 mkdirp-classic: 0.5.3 pump: 3.0.0 tar-stream: 2.2.0 - dev: true optional: true - /tar-fs@3.0.5: - resolution: {integrity: sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==} - requiresBuild: true + tar-fs@3.0.5: dependencies: pump: 3.0.0 tar-stream: 3.1.7 optionalDependencies: - bare-fs: 2.2.2 - bare-path: 2.1.0 - dev: true + bare-fs: 2.3.0 + bare-path: 2.1.2 optional: true - /tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} - requiresBuild: true + tar-stream@2.2.0: dependencies: bl: 4.1.0 end-of-stream: 1.4.4 fs-constants: 1.0.0 inherits: 2.0.4 readable-stream: 3.6.2 - dev: true optional: true - /tar-stream@3.1.7: - resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} - requiresBuild: true + tar-stream@3.1.7: dependencies: b4a: 1.6.6 fast-fifo: 1.3.2 streamx: 2.16.1 - dev: true optional: true - /temp-path@1.0.0: - resolution: {integrity: sha512-TvmyH7kC6ZVTYkqCODjJIbgvu0FKiwQpZ4D1aknE7xpcDf/qEOB8KZEK5ef2pfbVoiBhNWs3yx4y+ESMtNYmlg==} - dev: false + temp-path@1.0.0: {} - /text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - dev: true + text-table@0.2.0: {} - /tinybench@2.6.0: - resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==} - dev: true + tinybench@2.6.0: {} - /tinypool@0.8.2: - resolution: {integrity: sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==} - engines: {node: '>=14.0.0'} - dev: true + tinypool@0.8.2: {} - /tinyspy@2.2.1: - resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} - engines: {node: '>=14.0.0'} - dev: true + tinyspy@2.2.1: {} - /to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} + to-fast-properties@2.0.0: {} - /to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - /tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - dev: true + tr46@0.0.3: {} - /trim-lines@3.0.1: - resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - dev: true + trim-lines@3.0.1: {} - /trough@2.2.0: - resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + trough@2.2.0: {} - /ts-api-utils@1.3.0(typescript@5.4.2): - resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' + ts-api-utils@1.3.0(typescript@5.4.5): dependencies: - typescript: 5.4.2 - dev: true + typescript: 5.4.5 - /ts-morph@15.1.0: - resolution: {integrity: sha512-RBsGE2sDzUXFTnv8Ba22QfeuKbgvAGJFuTN7HfmIRUkgT/NaVLfDM/8OFm2NlFkGlWEXdpW5OaFIp1jvqdDuOg==} + ts-morph@15.1.0: dependencies: '@ts-morph/common': 0.16.0 code-block-writer: 11.0.3 - dev: true - /tsconfck@3.0.3(typescript@5.4.2): - resolution: {integrity: sha512-4t0noZX9t6GcPTfBAbIbbIU4pfpCwh0ueq3S4O/5qXI1VwK1outmxhe9dOiEWqMz3MW2LKgDTpqWV+37IWuVbA==} - engines: {node: ^18 || >=20} - hasBin: true - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - dependencies: - typescript: 5.4.2 - dev: true + tsconfck@3.0.3(typescript@5.4.5): + optionalDependencies: + typescript: 5.4.5 - /tsconfig@7.0.0: - resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==} + tsconfig@7.0.0: dependencies: '@types/strip-bom': 3.0.0 '@types/strip-json-comments': 0.0.30 strip-bom: 3.0.0 strip-json-comments: 2.0.1 - dev: true - - /tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - dev: true - - /tsm@2.3.0: - resolution: {integrity: sha512-++0HFnmmR+gMpDtKTnW3XJ4yv9kVGi20n+NfyQWB9qwJvTaIWY9kBmzek2YUQK5APTQ/1DTrXmm4QtFPmW9Rzw==} - engines: {node: '>=12'} - hasBin: true + + tslib@2.6.2: {} + + tsm@2.3.0: dependencies: esbuild: 0.15.18 - dev: true - /tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - requiresBuild: true + tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 - dev: true optional: true - /tunnel@0.0.6: - resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} - engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} - dev: true + tunnel@0.0.6: {} - /type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - dev: true - /type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - dev: true + type-detect@4.0.8: {} - /type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - dev: true + type-fest@0.20.2: {} - /type-fest@2.19.0: - resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} - engines: {node: '>=12.20'} - dev: true + type-fest@2.19.0: {} - /type-fest@3.13.1: - resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} - engines: {node: '>=14.16'} - dev: true + type-fest@3.13.1: {} - /typed-array-buffer@1.0.2: - resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} - engines: {node: '>= 0.4'} + typed-array-buffer@1.0.2: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 is-typed-array: 1.1.13 - dev: true - /typed-array-byte-length@1.0.1: - resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} - engines: {node: '>= 0.4'} + typed-array-byte-length@1.0.1: dependencies: call-bind: 1.0.7 for-each: 0.3.3 gopd: 1.0.1 has-proto: 1.0.3 is-typed-array: 1.1.13 - dev: true - /typed-array-byte-offset@1.0.2: - resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} - engines: {node: '>= 0.4'} + typed-array-byte-offset@1.0.2: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.7 @@ -7610,11 +9102,8 @@ packages: gopd: 1.0.1 has-proto: 1.0.3 is-typed-array: 1.1.13 - dev: true - /typed-array-length@1.0.5: - resolution: {integrity: sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==} - engines: {node: '>= 0.4'} + typed-array-length@1.0.5: dependencies: call-bind: 1.0.7 for-each: 0.3.3 @@ -7622,64 +9111,39 @@ packages: has-proto: 1.0.3 is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 - dev: true - /typesafe-path@0.2.2: - resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} - dev: false + typesafe-path@0.2.2: {} - /typescript-auto-import-cache@0.3.2: - resolution: {integrity: sha512-+laqe5SFL1vN62FPOOJSUDTZxtgsoOXjneYOXIpx5rQ4UMiN89NAtJLpqLqyebv9fgQ/IMeeTX+mQyRnwvJzvg==} + typescript-auto-import-cache@0.3.2: dependencies: semver: 7.6.0 - dev: false - /typescript@5.4.2: - resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==} - engines: {node: '>=14.17'} - hasBin: true + typescript@5.4.5: {} - /typical@2.6.1: - resolution: {integrity: sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==} - dev: false + typical@2.6.1: {} - /uc.micro@1.0.6: - resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} - dev: false + uc.micro@1.0.6: {} - /ufo@1.5.2: - resolution: {integrity: sha512-eiutMaL0J2MKdhcOM1tUy13pIrYnyR87fEd8STJQFrrAwImwvlXkxlZEjaKah8r2viPohld08lt73QfLG1NxMg==} - dev: true + ufo@1.5.2: {} - /unbox-primitive@1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + unbox-primitive@1.0.2: dependencies: call-bind: 1.0.7 has-bigints: 1.0.2 has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 - dev: true - /underscore@1.13.6: - resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==} - dev: false + underscore@1.13.6: {} - /undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - dev: true + undici-types@5.26.5: {} - /undici@5.28.4: - resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} - engines: {node: '>=14.0'} + undici@5.28.4: dependencies: '@fastify/busboy': 2.1.1 - dev: true - /unherit@3.0.1: - resolution: {integrity: sha512-akOOQ/Yln8a2sgcLj4U0Jmx0R5jpIg2IUyRrWOzmEbjBtGzBdHtSeFKgoEcoH4KYIG/Pb8GQ/BwtYm0GCq1Sqg==} + unherit@3.0.1: {} - /unified@10.1.2: - resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} + unified@10.1.2: dependencies: '@types/unist': 2.0.10 bail: 2.0.2 @@ -7689,8 +9153,7 @@ packages: trough: 2.2.0 vfile: 5.3.7 - /unified@11.0.4: - resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==} + unified@11.0.4: dependencies: '@types/unist': 3.0.2 bail: 2.0.2 @@ -7700,206 +9163,162 @@ packages: trough: 2.2.0 vfile: 6.0.1 - /unist-util-find-after@5.0.0: - resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + unist-util-find-after@5.0.0: dependencies: '@types/unist': 3.0.2 unist-util-is: 6.0.0 - dev: true - /unist-util-is@4.1.0: - resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} - dev: false + unist-util-is@4.1.0: {} - /unist-util-is@5.2.1: - resolution: {integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==} + unist-util-is@5.2.1: dependencies: '@types/unist': 2.0.10 - /unist-util-is@6.0.0: - resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + unist-util-is@6.0.0: dependencies: '@types/unist': 3.0.2 - /unist-util-modify-children@3.1.1: - resolution: {integrity: sha512-yXi4Lm+TG5VG+qvokP6tpnk+r1EPwyYL04JWDxLvgvPV40jANh7nm3udk65OOWquvbMDe+PL9+LmkxDpTv/7BA==} + unist-util-modify-children@3.1.1: dependencies: '@types/unist': 2.0.10 array-iterate: 2.0.1 - /unist-util-position-from-estree@2.0.0: - resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + unist-util-modify-children@4.0.0: dependencies: '@types/unist': 3.0.2 - dev: true + array-iterate: 2.0.1 - /unist-util-position@4.0.4: - resolution: {integrity: sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==} + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.2 + + unist-util-position@4.0.4: dependencies: '@types/unist': 2.0.10 - dev: true - /unist-util-position@5.0.0: - resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + unist-util-position@5.0.0: dependencies: '@types/unist': 3.0.2 - dev: true - /unist-util-remove-position@5.0.0: - resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + unist-util-remove-position@5.0.0: dependencies: '@types/unist': 3.0.2 unist-util-visit: 5.0.0 - dev: true - /unist-util-remove@4.0.0: - resolution: {integrity: sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==} + unist-util-remove@4.0.0: dependencies: '@types/unist': 3.0.2 unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 - dev: true - /unist-util-stringify-position@3.0.3: - resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} + unist-util-stringify-position@3.0.3: dependencies: '@types/unist': 2.0.10 - /unist-util-stringify-position@4.0.0: - resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + unist-util-stringify-position@4.0.0: dependencies: '@types/unist': 3.0.2 - /unist-util-visit-children@2.0.2: - resolution: {integrity: sha512-+LWpMFqyUwLGpsQxpumsQ9o9DG2VGLFrpz+rpVXYIEdPy57GSy5HioC0g3bg/8WP9oCLlapQtklOzQ8uLS496Q==} + unist-util-visit-children@2.0.2: dependencies: '@types/unist': 2.0.10 - /unist-util-visit-parents@3.1.1: - resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} + unist-util-visit-children@3.0.0: + dependencies: + '@types/unist': 3.0.2 + + unist-util-visit-parents@3.1.1: dependencies: '@types/unist': 2.0.10 unist-util-is: 4.1.0 - dev: false - /unist-util-visit-parents@5.1.3: - resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==} + unist-util-visit-parents@5.1.3: dependencies: '@types/unist': 2.0.10 unist-util-is: 5.2.1 - /unist-util-visit-parents@6.0.1: - resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + unist-util-visit-parents@6.0.1: dependencies: '@types/unist': 3.0.2 unist-util-is: 6.0.0 - /unist-util-visit@2.0.3: - resolution: {integrity: sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==} + unist-util-visit@2.0.3: dependencies: '@types/unist': 2.0.10 unist-util-is: 4.1.0 unist-util-visit-parents: 3.1.1 - dev: false - /unist-util-visit@4.1.2: - resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==} + unist-util-visit@4.1.2: dependencies: '@types/unist': 2.0.10 unist-util-is: 5.2.1 unist-util-visit-parents: 5.1.3 - /unist-util-visit@5.0.0: - resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + unist-util-visit@5.0.0: dependencies: '@types/unist': 3.0.2 unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 - /unist-util-walker@1.0.0: - resolution: {integrity: sha512-XxadVB7qdSH6LBwhyHozj1VltpnK9m3/Zt/E/WFLaEt9eRQ0RkbsUb0lP9e1anQCEOXxf4X3NYtZQSpzqzTptw==} + unist-util-walker@1.0.0: dependencies: '@types/unist': 2.0.10 unified: 10.1.2 - dev: true - /update-browserslist-db@1.0.13(browserslist@4.23.0): - resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' + update-browserslist-db@1.0.13(browserslist@4.23.0): dependencies: browserslist: 4.23.0 escalade: 3.1.2 picocolors: 1.0.0 - dev: true - /uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + uri-js@4.4.1: dependencies: punycode: 2.3.1 - dev: true - /util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: true + util-deprecate@1.0.2: {} - /uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - dev: true + uuid@8.3.2: {} - /vfile-location@4.1.0: - resolution: {integrity: sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==} + vfile-location@4.1.0: dependencies: '@types/unist': 2.0.10 vfile: 5.3.7 - dev: true - /vfile-location@5.0.2: - resolution: {integrity: sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==} + vfile-location@5.0.2: dependencies: '@types/unist': 3.0.2 vfile: 6.0.1 - dev: true - /vfile-message@3.1.4: - resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} + vfile-message@3.1.4: dependencies: '@types/unist': 2.0.10 unist-util-stringify-position: 3.0.3 - /vfile-message@4.0.2: - resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + vfile-message@4.0.2: dependencies: '@types/unist': 3.0.2 unist-util-stringify-position: 4.0.0 - /vfile@5.3.7: - resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} + vfile@5.3.7: dependencies: '@types/unist': 2.0.10 is-buffer: 2.0.5 unist-util-stringify-position: 3.0.3 vfile-message: 3.1.4 - /vfile@6.0.1: - resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} + vfile@6.0.1: dependencies: '@types/unist': 3.0.2 unist-util-stringify-position: 4.0.0 vfile-message: 4.0.2 - /vite-node@1.4.0(@types/node@20.11.30)(sass@1.72.0): - resolution: {integrity: sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true + vite-node@1.4.0(@types/node@20.12.7)(sass@1.72.0): dependencies: cac: 6.7.14 debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.2.8(@types/node@20.11.30)(sass@1.72.0) + vite: 5.2.8(@types/node@20.12.7)(sass@1.72.0) transitivePeerDependencies: - '@types/node' - less @@ -7909,82 +9328,23 @@ packages: - sugarss - supports-color - terser - dev: true - /vite@5.2.8(@types/node@20.11.30)(sass@1.72.0): - resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true + vite@5.2.8(@types/node@20.12.7)(sass@1.72.0): dependencies: - '@types/node': 20.11.30 esbuild: 0.20.2 postcss: 8.4.38 rollup: 4.13.0 - sass: 1.72.0 optionalDependencies: + '@types/node': 20.12.7 fsevents: 2.3.3 - dev: true + sass: 1.72.0 - /vitefu@0.2.5(vite@5.2.8): - resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==} - peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 - peerDependenciesMeta: - vite: - optional: true - dependencies: - vite: 5.2.8(@types/node@20.11.30)(sass@1.72.0) - dev: true + vitefu@0.2.5(vite@5.2.8(@types/node@20.12.7)(sass@1.72.0)): + optionalDependencies: + vite: 5.2.8(@types/node@20.12.7)(sass@1.72.0) - /vitest@1.4.0(@types/node@20.11.30)(sass@1.72.0): - resolution: {integrity: sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.4.0 - '@vitest/ui': 1.4.0 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true + vitest@1.4.0(@types/node@20.12.7)(sass@1.72.0): dependencies: - '@types/node': 20.11.30 '@vitest/expect': 1.4.0 '@vitest/runner': 1.4.0 '@vitest/snapshot': 1.4.0 @@ -8002,9 +9362,11 @@ packages: strip-literal: 2.0.0 tinybench: 2.6.0 tinypool: 0.8.2 - vite: 5.2.8(@types/node@20.11.30)(sass@1.72.0) - vite-node: 1.4.0(@types/node@20.11.30)(sass@1.72.0) + vite: 5.2.8(@types/node@20.12.7)(sass@1.72.0) + vite-node: 1.4.0(@types/node@20.12.7)(sass@1.72.0) why-is-node-running: 2.2.2 + optionalDependencies: + '@types/node': 20.12.7 transitivePeerDependencies: - less - lightningcss @@ -8013,187 +9375,108 @@ packages: - sugarss - supports-color - terser - dev: true - /volar-service-css@0.0.34(@volar/language-service@2.1.2): - resolution: {integrity: sha512-C7ua0j80ZD7bsgALAz/cA1bykPehoIa5n+3+Ccr+YLpj0fypqw9iLUmGLX11CqzqNCO2XFGe/1eXB/c+SWrF/g==} - peerDependencies: - '@volar/language-service': ~2.1.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true + volar-service-css@0.0.34(@volar/language-service@2.1.6): dependencies: - '@volar/language-service': 2.1.2 - vscode-css-languageservice: 6.2.12 + vscode-css-languageservice: 6.2.14 vscode-languageserver-textdocument: 1.0.11 vscode-uri: 3.0.8 - dev: false + optionalDependencies: + '@volar/language-service': 2.1.6 - /volar-service-emmet@0.0.34(@volar/language-service@2.1.2): - resolution: {integrity: sha512-ubQvMCmHPp8Ic82LMPkgrp9ot+u2p/RDd0RyT0EykRkZpWsagHUF5HWkVheLfiMyx2rFuWx/+7qZPOgypx6h6g==} - peerDependencies: - '@volar/language-service': ~2.1.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true + volar-service-emmet@0.0.34(@volar/language-service@2.1.6): dependencies: - '@volar/language-service': 2.1.2 - '@vscode/emmet-helper': 2.9.2 - vscode-html-languageservice: 5.1.2 - dev: false + '@vscode/emmet-helper': 2.9.3 + vscode-html-languageservice: 5.2.0 + optionalDependencies: + '@volar/language-service': 2.1.6 - /volar-service-html@0.0.34(@volar/language-service@2.1.2): - resolution: {integrity: sha512-kMEneea1tQbiRcyKavqdrSVt8zV06t+0/3pGkjO3gV6sikXTNShIDkdtB4Tq9vE2cQdM50TuS7utVV7iysUxHw==} - peerDependencies: - '@volar/language-service': ~2.1.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true + volar-service-html@0.0.34(@volar/language-service@2.1.6): dependencies: - '@volar/language-service': 2.1.2 - vscode-html-languageservice: 5.1.2 + vscode-html-languageservice: 5.2.0 vscode-languageserver-textdocument: 1.0.11 vscode-uri: 3.0.8 - dev: false + optionalDependencies: + '@volar/language-service': 2.1.6 - /volar-service-prettier@0.0.34(@volar/language-service@2.1.2)(prettier@3.2.5): - resolution: {integrity: sha512-BNfJ8FwfPi1Wm/JkuzNjraOLdtKieGksNT/bDyquygVawv1QUzO2HB1hiMKfZGdcSFG5ZL9R0j7bBfRTfXA2gg==} - peerDependencies: - '@volar/language-service': ~2.1.0 - prettier: ^2.2 || ^3.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true - prettier: - optional: true + volar-service-prettier@0.0.34(@volar/language-service@2.1.6)(prettier@3.2.5): dependencies: - '@volar/language-service': 2.1.2 - prettier: 3.2.5 vscode-uri: 3.0.8 - dev: false + optionalDependencies: + '@volar/language-service': 2.1.6 + prettier: 3.2.5 - /volar-service-typescript-twoslash-queries@0.0.34(@volar/language-service@2.1.2): - resolution: {integrity: sha512-XAY2YtWKUp6ht89gxt3L5Dr46LU45d/VlBkj1KXUwNlinpoWiGN4Nm3B6DRF3VoBThAnQgm4c7WD0S+5yTzh+w==} - peerDependencies: - '@volar/language-service': ~2.1.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true - dependencies: - '@volar/language-service': 2.1.2 - dev: false + volar-service-typescript-twoslash-queries@0.0.34(@volar/language-service@2.1.6): + optionalDependencies: + '@volar/language-service': 2.1.6 - /volar-service-typescript@0.0.34(@volar/language-service@2.1.2): - resolution: {integrity: sha512-NbAry0w8ZXFgGsflvMwmPDCzgJGx3C+eYxFEbldaumkpTAJiywECWiUbPIOfmEHgpOllUKSnhwtLlWFK4YnfQg==} - peerDependencies: - '@volar/language-service': ~2.1.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true + volar-service-typescript@0.0.34(@volar/language-service@2.1.6): dependencies: - '@volar/language-service': 2.1.2 path-browserify: 1.0.1 semver: 7.6.0 typescript-auto-import-cache: 0.3.2 vscode-languageserver-textdocument: 1.0.11 vscode-nls: 5.2.0 - dev: false + optionalDependencies: + '@volar/language-service': 2.1.6 - /vscode-css-languageservice@6.2.12: - resolution: {integrity: sha512-PS9r7HgNjqzRl3v91sXpCyZPc8UDotNo6gntFNtGCKPhGA9Frk7g/VjX1Mbv3F00pn56D+rxrFzR9ep4cawOgA==} + vscode-css-languageservice@6.2.14: dependencies: '@vscode/l10n': 0.0.18 vscode-languageserver-textdocument: 1.0.11 vscode-languageserver-types: 3.17.5 vscode-uri: 3.0.8 - dev: false - /vscode-html-languageservice@5.1.2: - resolution: {integrity: sha512-wkWfEx/IIR3s2P5yD4aTGHiOb8IAzFxgkSt1uSC3itJ4oDAm23yG7o0L29JljUdnXDDgLafPAvhv8A2I/8riHw==} + vscode-html-languageservice@5.2.0: dependencies: '@vscode/l10n': 0.0.18 vscode-languageserver-textdocument: 1.0.11 vscode-languageserver-types: 3.17.5 vscode-uri: 3.0.8 - dev: false - /vscode-jsonrpc@8.2.0: - resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} - engines: {node: '>=14.0.0'} - dev: false + vscode-jsonrpc@8.2.0: {} - /vscode-languageserver-protocol@3.17.5: - resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + vscode-languageserver-protocol@3.17.5: dependencies: vscode-jsonrpc: 8.2.0 vscode-languageserver-types: 3.17.5 - dev: false - /vscode-languageserver-textdocument@1.0.11: - resolution: {integrity: sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==} - dev: false + vscode-languageserver-textdocument@1.0.11: {} - /vscode-languageserver-types@3.17.5: - resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} - dev: false + vscode-languageserver-types@3.17.5: {} - /vscode-languageserver@9.0.1: - resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} - hasBin: true + vscode-languageserver@9.0.1: dependencies: vscode-languageserver-protocol: 3.17.5 - dev: false - /vscode-nls@5.2.0: - resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==} - dev: false + vscode-nls@5.2.0: {} - /vscode-uri@2.1.2: - resolution: {integrity: sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==} - dev: false + vscode-uri@2.1.2: {} - /vscode-uri@3.0.8: - resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} - dev: false + vscode-uri@3.0.8: {} - /walk-back@5.1.0: - resolution: {integrity: sha512-Uhxps5yZcVNbLEAnb+xaEEMdgTXl9qAQDzKYejG2AZ7qPwRQ81lozY9ECDbjLPNWm7YsO1IK5rsP1KoQzXAcGA==} - engines: {node: '>=12.17'} - dev: false + walk-back@5.1.0: {} - /web-namespaces@2.0.1: - resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} - dev: true + web-namespaces@2.0.1: {} - /web-streams-polyfill@3.3.3: - resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} - engines: {node: '>= 8'} - dev: true + web-streams-polyfill@3.3.3: {} - /webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - dev: true + webidl-conversions@3.0.1: {} - /whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 - dev: true - /which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 is-boolean-object: 1.1.2 is-number-object: 1.0.7 is-string: 1.0.7 is-symbol: 1.0.4 - dev: true - /which-builtin-type@1.1.3: - resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==} - engines: {node: '>= 0.4'} + which-builtin-type@1.1.3: dependencies: function.prototype.name: 1.1.6 has-tostringtag: 1.0.2 @@ -8207,136 +9490,82 @@ packages: which-boxed-primitive: 1.0.2 which-collection: 1.0.2 which-typed-array: 1.1.15 - dev: true - /which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} + which-collection@1.0.2: dependencies: is-map: 2.0.3 is-set: 2.0.3 is-weakmap: 2.0.2 is-weakset: 2.0.3 - dev: true - /which-pm-runs@1.1.0: - resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} - engines: {node: '>=4'} - dev: true + which-pm-runs@1.1.0: {} - /which-pm@2.0.0: - resolution: {integrity: sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==} - engines: {node: '>=8.15'} + which-pm@2.0.0: dependencies: load-yaml-file: 0.2.0 path-exists: 4.0.0 - dev: true - /which-pm@2.1.1: - resolution: {integrity: sha512-xzzxNw2wMaoCWXiGE8IJ9wuPMU+EYhFksjHxrRT8kMT5SnocBPRg69YAMtyV4D12fP582RA+k3P8H9J5EMdIxQ==} - engines: {node: '>=8.15'} + which-pm@2.1.1: dependencies: load-yaml-file: 0.2.0 path-exists: 4.0.0 - dev: true - /which-typed-array@1.1.15: - resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} - engines: {node: '>= 0.4'} + which-typed-array@1.1.15: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.7 for-each: 0.3.3 gopd: 1.0.1 has-tostringtag: 1.0.2 - dev: true - /which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true + which@2.0.2: dependencies: isexe: 2.0.0 - dev: true - /why-is-node-running@2.2.2: - resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} - engines: {node: '>=8'} - hasBin: true + why-is-node-running@2.2.2: dependencies: siginfo: 2.0.0 stackback: 0.0.2 - dev: true - /widest-line@4.0.1: - resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} - engines: {node: '>=12'} + widest-line@4.0.1: dependencies: string-width: 5.1.2 - dev: true - /wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: false - /wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} + wrap-ansi@8.1.0: dependencies: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - dev: true - /wrap-ansi@9.0.0: - resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} - engines: {node: '>=18'} + wrap-ansi@9.0.0: dependencies: ansi-styles: 6.2.1 string-width: 7.1.0 strip-ansi: 7.1.0 - dev: true - /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + wrappy@1.0.2: {} - /xmlcreate@2.0.4: - resolution: {integrity: sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==} - dev: false + xmlcreate@2.0.4: {} - /y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - dev: false + y18n@5.0.8: {} - /yallist@2.1.2: - resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} - dev: true + yallist@2.1.2: {} - /yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - dev: true + yallist@3.1.1: {} - /yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@4.0.0: {} - /yaml@2.3.4: - resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} - engines: {node: '>= 14'} - dev: true + yaml@2.3.4: {} - /yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} + yargs-parser@21.1.1: {} - /yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} + yargs@17.7.2: dependencies: cliui: 8.0.1 escalade: 3.1.2 @@ -8345,29 +9574,15 @@ packages: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 - dev: false - /yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - dev: true + yocto-queue@0.1.0: {} - /yocto-queue@1.0.0: - resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} - engines: {node: '>=12.20'} - dev: true + yocto-queue@1.0.0: {} - /zod-to-json-schema@3.22.4(zod@3.22.4): - resolution: {integrity: sha512-2Ed5dJ+n/O3cU383xSY28cuVi0BCQhF8nYqWU5paEpl7fVdqdAmiLdqLyfblbNdfOFwFfi/mqU4O1pwc60iBhQ==} - peerDependencies: - zod: ^3.22.4 + zod-to-json-schema@3.22.4(zod@3.22.4): dependencies: zod: 3.22.4 - dev: true - /zod@3.22.4: - resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} - dev: true + zod@3.22.4: {} - /zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + zwitch@2.0.4: {} diff --git a/frontend/__tests__/client/BoardClient.test.ts b/frontend/__tests__/client/BoardClient.test.ts index 853589ac13..889c4586ef 100644 --- a/frontend/__tests__/client/BoardClient.test.ts +++ b/frontend/__tests__/client/BoardClient.test.ts @@ -6,11 +6,17 @@ import { AXIOS_ERROR_MESSAGE, } from '../fixtures'; import { boardClient } from '@src/clients/board/BoardClient'; +import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; import { HttpStatusCode } from 'axios'; -import { rest } from 'msw'; -const server = setupServer(rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => res(ctx.status(HttpStatusCode.Ok)))); +const server = setupServer( + http.post(MOCK_BOARD_URL_FOR_JIRA, () => { + return new HttpResponse('', { + status: HttpStatusCode.Ok, + }); + }), +); describe('verify board request', () => { beforeAll(() => server.listen()); @@ -29,7 +35,13 @@ describe('verify board request', () => { }); it('should isNoDoneCard is true when board verify response status 204', async () => { - server.use(rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => res(ctx.status(HttpStatusCode.NoContent)))); + server.use( + http.post(MOCK_BOARD_URL_FOR_JIRA, () => { + return new HttpResponse(null, { + status: HttpStatusCode.NoContent, + }); + }), + ); const result = await boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS); @@ -39,9 +51,16 @@ describe('verify board request', () => { it('should throw error when board verify response status 400', async () => { server.use( - rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => - res(ctx.status(HttpStatusCode.BadRequest), ctx.json({ hintInfo: VERIFY_ERROR_MESSAGE.BAD_REQUEST })), - ), + http.post(MOCK_BOARD_URL_FOR_JIRA, () => { + return new HttpResponse( + JSON.stringify({ + hintInfo: VERIFY_ERROR_MESSAGE.BAD_REQUEST, + }), + { + status: HttpStatusCode.BadRequest, + }, + ); + }), ); boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS).catch((e) => { @@ -52,9 +71,16 @@ describe('verify board request', () => { it('should throw error when board verify response status 401', async () => { server.use( - rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => - res(ctx.status(HttpStatusCode.Unauthorized), ctx.json({ hintInfo: VERIFY_ERROR_MESSAGE.UNAUTHORIZED })), - ), + http.post(MOCK_BOARD_URL_FOR_JIRA, () => { + return new HttpResponse( + JSON.stringify({ + hintInfo: VERIFY_ERROR_MESSAGE.UNAUTHORIZED, + }), + { + status: HttpStatusCode.Unauthorized, + }, + ); + }), ); await expect(async () => { @@ -64,14 +90,16 @@ describe('verify board request', () => { it('should throw error when board verify response status 500', async () => { server.use( - rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => - res( - ctx.status(HttpStatusCode.InternalServerError), - ctx.json({ + http.post(MOCK_BOARD_URL_FOR_JIRA, () => { + return new HttpResponse( + JSON.stringify({ hintInfo: VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR, }), - ), - ), + { + status: HttpStatusCode.InternalServerError, + }, + ); + }), ); await expect(async () => { @@ -81,12 +109,16 @@ describe('verify board request', () => { it('should throw error when board verify response status 503', async () => { server.use( - rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => - res( - ctx.status(HttpStatusCode.ServiceUnavailable), - ctx.json({ hintInfo: VERIFY_ERROR_MESSAGE.REQUEST_TIMEOUT }), - ), - ), + http.post(MOCK_BOARD_URL_FOR_JIRA, () => { + return new HttpResponse( + JSON.stringify({ + hintInfo: VERIFY_ERROR_MESSAGE.REQUEST_TIMEOUT, + }), + { + status: HttpStatusCode.ServiceUnavailable, + }, + ); + }), ); await expect(async () => { @@ -95,7 +127,13 @@ describe('verify board request', () => { }); it('should throw error when board verify response status 300', async () => { - server.use(rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => res(ctx.status(HttpStatusCode.MultipleChoices)))); + server.use( + http.post(MOCK_BOARD_URL_FOR_JIRA, () => { + return new HttpResponse('', { + status: HttpStatusCode.MultipleChoices, + }); + }), + ); await expect(async () => { await boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS); @@ -103,7 +141,11 @@ describe('verify board request', () => { }); it('should throw `Network Error` when board verify encountered netwrok error', async () => { - server.use(rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res) => res.networkError('Network Error'))); + server.use( + http.post(MOCK_BOARD_URL_FOR_JIRA, () => { + return HttpResponse.error(); + }), + ); await expect(async () => { await boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS); diff --git a/frontend/__tests__/client/CSVClient.test.ts b/frontend/__tests__/client/CSVClient.test.ts index 1bda549e45..cd1c21f25f 100644 --- a/frontend/__tests__/client/CSVClient.test.ts +++ b/frontend/__tests__/client/CSVClient.test.ts @@ -1,17 +1,23 @@ import { MOCK_EXPORT_CSV_REQUEST_PARAMS, MOCK_EXPORT_CSV_URL, VERIFY_ERROR_MESSAGE } from '../fixtures'; import { csvClient } from '@src/clients/report/CSVClient'; +import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; import { HttpStatusCode } from 'axios'; -import { rest } from 'msw'; -const server = setupServer(rest.get(MOCK_EXPORT_CSV_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Ok)))); +const server = setupServer( + http.get(MOCK_EXPORT_CSV_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.NoContent, + }); + }), +); describe('verify export csv', () => { beforeAll(() => server.listen()); afterAll(() => server.close()); it('should download the pipeline CSV file when export csv request status 200', async () => { - const mockBlob = new Blob(['CSV data'], { type: 'text/csv' }); + const mockBlob = new Blob([''], { type: 'application/octet-stream' }); const mockResponse = { data: mockBlob }; const mockGet = jest.fn().mockResolvedValue(mockResponse); const mockCreateObjectURL = jest.fn().mockImplementation((blob) => { @@ -31,9 +37,12 @@ describe('verify export csv', () => { it('should throw error when export csv request status 500', async () => { server.use( - rest.get(MOCK_EXPORT_CSV_URL, (req, res, ctx) => - res(ctx.status(HttpStatusCode.InternalServerError, VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR)), - ), + http.get(MOCK_EXPORT_CSV_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.InternalServerError, + statusText: VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR, + }); + }), ); await expect(async () => { diff --git a/frontend/__tests__/client/HeaderClient.test.ts b/frontend/__tests__/client/HeaderClient.test.ts index 3815ef6082..1cde59b5b2 100644 --- a/frontend/__tests__/client/HeaderClient.test.ts +++ b/frontend/__tests__/client/HeaderClient.test.ts @@ -1,10 +1,16 @@ import { MOCK_VERSION_URL, VERIFY_ERROR_MESSAGE, VERSION_RESPONSE } from '../fixtures'; import { headerClient } from '@src/clients/header/HeaderClient'; +import { HttpResponse, http } from 'msw'; import { setupServer } from 'msw/node'; import { HttpStatusCode } from 'axios'; -import { rest } from 'msw'; -const server = setupServer(rest.get(MOCK_VERSION_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Ok)))); +const server = setupServer( + http.get(MOCK_VERSION_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.Ok, + }); + }), +); describe('header client', () => { beforeAll(() => server.listen()); @@ -14,9 +20,11 @@ describe('header client', () => { it('should get response when get header status 200', async () => { const excepted = '1.11'; server.use( - rest.get(MOCK_VERSION_URL, (req, res, ctx) => - res(ctx.status(HttpStatusCode.Accepted), ctx.json(VERSION_RESPONSE)), - ), + http.get(MOCK_VERSION_URL, () => { + return new HttpResponse(JSON.stringify(VERSION_RESPONSE), { + status: HttpStatusCode.Accepted, + }); + }), ); await expect(headerClient.getVersion()).resolves.toEqual(excepted); @@ -24,14 +32,16 @@ describe('header client', () => { it('should throw error when get version response status 500', () => { server.use( - rest.get(MOCK_VERSION_URL, (req, res, ctx) => - res( - ctx.status(HttpStatusCode.InternalServerError), - ctx.json({ + http.get(MOCK_VERSION_URL, () => { + return new HttpResponse( + JSON.stringify({ hintInfo: VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR, }), - ), - ), + { + status: HttpStatusCode.InternalServerError, + }, + ); + }), ); expect(async () => { diff --git a/frontend/__tests__/client/MetricsClient.test.ts b/frontend/__tests__/client/MetricsClient.test.ts index 5f2e476333..7f32e8160b 100644 --- a/frontend/__tests__/client/MetricsClient.test.ts +++ b/frontend/__tests__/client/MetricsClient.test.ts @@ -1,8 +1,8 @@ import { BASE_URL, MOCK_GET_STEPS_PARAMS, VERIFY_ERROR_MESSAGE } from '../fixtures'; import { metricsClient } from '@src/clients/MetricsClient'; +import { HttpResponse, http } from 'msw'; import { setupServer } from 'msw/node'; import { HttpStatusCode } from 'axios'; -import { rest } from 'msw'; describe('get steps from metrics response', () => { const { params, buildId, organizationId, pipelineType, token } = MOCK_GET_STEPS_PARAMS; @@ -13,49 +13,56 @@ describe('get steps from metrics response', () => { it('should return steps when getSteps response status 200', async () => { server.use( - rest.get(getStepsUrl, (req, res, ctx) => { - return res(ctx.status(HttpStatusCode.Ok), ctx.json({ steps: ['step1'] })); + http.get(getStepsUrl, () => { + return new HttpResponse(JSON.stringify({ steps: ['step1'] }), { + status: HttpStatusCode.Ok, + }); }), ); - const result = await metricsClient.getSteps(params, buildId, organizationId, pipelineType, token); + const result = await metricsClient.getSteps(params[0], buildId, organizationId, pipelineType, token); expect(result).toEqual({ response: ['step1'], haveStep: true }); }); it('should throw error when getSteps response status 500', async () => { server.use( - rest.get(getStepsUrl, (req, res, ctx) => - res( - ctx.status(HttpStatusCode.InternalServerError), - ctx.json({ - hintInfo: VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR, - }), - ), - ), + http.get(getStepsUrl, () => { + return new HttpResponse(JSON.stringify({ hintInfo: VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR }), { + status: HttpStatusCode.InternalServerError, + }); + }), ); await expect(async () => { - await metricsClient.getSteps(params, buildId, organizationId, pipelineType, token); + await metricsClient.getSteps(params[0], buildId, organizationId, pipelineType, token); }).rejects.toThrow(VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR); }); it('should throw error when getSteps response status 400', async () => { server.use( - rest.get(getStepsUrl, (req, res, ctx) => - res(ctx.status(HttpStatusCode.BadRequest), ctx.json({ hintInfo: VERIFY_ERROR_MESSAGE.BAD_REQUEST })), - ), + http.get(getStepsUrl, () => { + return new HttpResponse(JSON.stringify({ hintInfo: VERIFY_ERROR_MESSAGE.BAD_REQUEST }), { + status: HttpStatusCode.BadRequest, + }); + }), ); await expect(async () => { - await metricsClient.getSteps(params, buildId, organizationId, pipelineType, token); + await metricsClient.getSteps(params[0], buildId, organizationId, pipelineType, token); }).rejects.toThrow(VERIFY_ERROR_MESSAGE.BAD_REQUEST); }); it('should show isNoStep True when getSteps response status 204', async () => { - server.use(rest.get(getStepsUrl, (req, res, ctx) => res(ctx.status(HttpStatusCode.NoContent)))); + server.use( + http.get(getStepsUrl, () => { + return new HttpResponse(null, { + status: HttpStatusCode.NoContent, + }); + }), + ); - const result = await metricsClient.getSteps(params, buildId, organizationId, pipelineType, token); + const result = await metricsClient.getSteps(params[0], buildId, organizationId, pipelineType, token); expect(result).toEqual({ branches: [], response: [], haveStep: false, pipelineCrews: [] }); }); diff --git a/frontend/__tests__/client/PipelineToolClient.test.ts b/frontend/__tests__/client/PipelineToolClient.test.ts index ed50cd7c95..37185d9103 100644 --- a/frontend/__tests__/client/PipelineToolClient.test.ts +++ b/frontend/__tests__/client/PipelineToolClient.test.ts @@ -5,13 +5,15 @@ import { MOCK_PIPELINE_VERIFY_URL, } from '../fixtures'; import { pipelineToolClient } from '@src/clients/pipeline/PipelineToolClient'; +import { HttpResponse, http } from 'msw'; import { setupServer } from 'msw/node'; import { HttpStatusCode } from 'axios'; -import { rest } from 'msw'; const server = setupServer( - rest.post(MOCK_PIPELINE_VERIFY_URL, (req, res, ctx) => { - return res(ctx.status(HttpStatusCode.NoContent)); + http.post(MOCK_PIPELINE_VERIFY_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.NoContent, + }); }), ); @@ -43,7 +45,13 @@ describe('PipelineToolClient', () => { ]; it.each(errorCases)('should return error code when verify endponint returns error', async ({ code }) => { - server.use(rest.post(MOCK_PIPELINE_VERIFY_URL, (req, res, ctx) => res(ctx.status(code)))); + server.use( + http.post(MOCK_PIPELINE_VERIFY_URL, () => { + return new HttpResponse(null, { + status: code, + }); + }), + ); const result = await pipelineToolClient.verify(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS); @@ -55,9 +63,11 @@ describe('PipelineToolClient', () => { describe('Get pipelineTool info request', () => { it('should return 200 code and corresponding data when pipelineTool get info returns code 200', async () => { server.use( - rest.post(MOCK_PIPELINE_GET_INFO_URL, (req, res, ctx) => - res(ctx.status(HttpStatusCode.Ok), ctx.json(MOCK_BUILD_KITE_GET_INFO_RESPONSE)), - ), + http.post(MOCK_PIPELINE_GET_INFO_URL, () => { + return new HttpResponse(JSON.stringify(MOCK_BUILD_KITE_GET_INFO_RESPONSE), { + status: HttpStatusCode.Ok, + }); + }), ); const result = await pipelineToolClient.getInfo(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS); @@ -79,22 +89,22 @@ describe('PipelineToolClient', () => { }, { code: HttpStatusCode.BadRequest, - errorTitle: 'Invalid input!', + errorTitle: 'Failed to get Pipeline configuration!', errorMessage, }, { code: HttpStatusCode.Unauthorized, - errorTitle: 'Unauthorized request!', + errorTitle: 'Failed to get Pipeline configuration!', errorMessage, }, { code: HttpStatusCode.Forbidden, - errorTitle: 'Forbidden request!', + errorTitle: 'Failed to get Pipeline configuration!', errorMessage, }, { code: HttpStatusCode.NotFound, - errorTitle: 'Not found!', + errorTitle: 'Failed to get Pipeline configuration!', errorMessage, }, ]; @@ -102,7 +112,13 @@ describe('PipelineToolClient', () => { it.each(errorCases)( `should return result with code:$code and title:$errorTitle and unify errorMessage when verify endpoint returns code:$code`, async ({ code, errorTitle, errorMessage }) => { - server.use(rest.post(MOCK_PIPELINE_GET_INFO_URL, (req, res, ctx) => res(ctx.status(code)))); + server.use( + http.post(MOCK_PIPELINE_GET_INFO_URL, () => { + return new HttpResponse(null, { + status: code, + }); + }), + ); const result = await pipelineToolClient.getInfo(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS); @@ -115,8 +131,8 @@ describe('PipelineToolClient', () => { it('should return ERR_NETWORK error as its code when axios client detect network error', async () => { server.use( - rest.post(MOCK_PIPELINE_GET_INFO_URL, (req, res) => { - return res.networkError('mock network error'); + http.post(MOCK_PIPELINE_GET_INFO_URL, () => { + return HttpResponse.error(); }), ); @@ -128,8 +144,10 @@ describe('PipelineToolClient', () => { it('should return "Unknown error" as a last resort when axios error code didn\'t match the predeifned erorr cases', async () => { server.use( - rest.post(MOCK_PIPELINE_GET_INFO_URL, (req, res, ctx) => { - return res(ctx.status(-1), ctx.body('mock error not covered by httpClient')); + http.post(MOCK_PIPELINE_GET_INFO_URL, () => { + return new HttpResponse(JSON.stringify('mock error not covered by httpClient'), { + status: -1, + }); }), ); diff --git a/frontend/__tests__/client/ReportClient.test.ts b/frontend/__tests__/client/ReportClient.test.ts index 41066ff8dc..9eb557e28a 100644 --- a/frontend/__tests__/client/ReportClient.test.ts +++ b/frontend/__tests__/client/ReportClient.test.ts @@ -5,14 +5,22 @@ import { VERIFY_ERROR_MESSAGE, } from '../fixtures'; import { reportClient } from '@src/clients/report/ReportClient'; +import { HttpResponse, http } from 'msw'; import { setupServer } from 'msw/node'; import { HttpStatusCode } from 'axios'; -import { rest } from 'msw'; const MOCK_REPORT_URL = 'http://localhost/api/v1/reports'; const server = setupServer( - rest.post(MOCK_REPORT_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Ok))), - rest.get(MOCK_REPORT_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Ok))), + http.post(MOCK_REPORT_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.Ok, + }); + }), + http.get(MOCK_REPORT_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.Ok, + }); + }), ); describe('report client', () => { @@ -21,13 +29,13 @@ describe('report client', () => { afterAll(() => server.close()); it('should get response when generate report request status 202', async () => { - const excepted = { - response: MOCK_RETRIEVE_REPORT_RESPONSE, - }; + const excepted = MOCK_RETRIEVE_REPORT_RESPONSE; server.use( - rest.post(MOCK_REPORT_URL, (req, res, ctx) => - res(ctx.status(HttpStatusCode.Accepted), ctx.json(MOCK_RETRIEVE_REPORT_RESPONSE)), - ), + http.post(MOCK_REPORT_URL, () => { + return new HttpResponse(JSON.stringify(MOCK_RETRIEVE_REPORT_RESPONSE), { + status: HttpStatusCode.Accepted, + }); + }), ); await expect(reportClient.retrieveByUrl(MOCK_GENERATE_REPORT_REQUEST_PARAMS, '/reports')).resolves.toStrictEqual( @@ -37,14 +45,16 @@ describe('report client', () => { it('should throw error when generate report response status 500', async () => { server.use( - rest.post(MOCK_REPORT_URL, (req, res, ctx) => - res( - ctx.status(HttpStatusCode.InternalServerError), - ctx.json({ + http.post(MOCK_REPORT_URL, () => { + return new HttpResponse( + JSON.stringify({ hintInfo: VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR, }), - ), - ), + { + status: HttpStatusCode.InternalServerError, + }, + ); + }), ); await expect(async () => { @@ -54,14 +64,16 @@ describe('report client', () => { it('should throw error when generate report response status 400', async () => { server.use( - rest.post(MOCK_REPORT_URL, (req, res, ctx) => - res( - ctx.status(HttpStatusCode.BadRequest), - ctx.json({ + http.post(MOCK_REPORT_URL, () => { + return new HttpResponse( + JSON.stringify({ hintInfo: VERIFY_ERROR_MESSAGE.BAD_REQUEST, }), - ), - ), + { + status: HttpStatusCode.BadRequest, + }, + ); + }), ); await expect(async () => { @@ -71,14 +83,16 @@ describe('report client', () => { it('should throw error when calling pollingReport given response status 500', () => { server.use( - rest.get(MOCK_REPORT_URL, (req, res, ctx) => - res( - ctx.status(HttpStatusCode.InternalServerError), - ctx.json({ + http.get(MOCK_REPORT_URL, () => { + return new HttpResponse( + JSON.stringify({ hintInfo: VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR, }), - ), - ), + { + status: HttpStatusCode.InternalServerError, + }, + ); + }), ); expect(async () => { @@ -92,9 +106,11 @@ describe('report client', () => { response: MOCK_REPORT_RESPONSE, }; server.use( - rest.get(MOCK_REPORT_URL, (req, res, ctx) => - res(ctx.status(HttpStatusCode.Created), ctx.json(MOCK_REPORT_RESPONSE)), - ), + http.get(MOCK_REPORT_URL, () => { + return new HttpResponse(JSON.stringify(MOCK_REPORT_RESPONSE), { + status: HttpStatusCode.Created, + }); + }), ); await expect(reportClient.polling(MOCK_REPORT_URL)).resolves.toEqual(excepted); diff --git a/frontend/__tests__/client/SourceControlClient.test.ts b/frontend/__tests__/client/SourceControlClient.test.ts index 8e33ec18e7..5e79e38684 100644 --- a/frontend/__tests__/client/SourceControlClient.test.ts +++ b/frontend/__tests__/client/SourceControlClient.test.ts @@ -1,10 +1,16 @@ import { MOCK_SOURCE_CONTROL_VERIFY_TOKEN_URL, MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS } from '../fixtures'; import { sourceControlClient } from '@src/clients/sourceControl/SourceControlClient'; +import { HttpResponse, http } from 'msw'; import { setupServer } from 'msw/node'; import { HttpStatusCode } from 'axios'; -import { rest } from 'msw'; -const server = setupServer(rest.post(MOCK_SOURCE_CONTROL_VERIFY_TOKEN_URL, (req, res, ctx) => res(ctx.status(204)))); +const server = setupServer( + http.post(MOCK_SOURCE_CONTROL_VERIFY_TOKEN_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.NoContent, + }); + }), +); describe('verify sourceControl request', () => { beforeAll(() => server.listen()); @@ -18,7 +24,11 @@ describe('verify sourceControl request', () => { it('should set error title when sourceControl verify response status is 401', async () => { server.use( - rest.post(MOCK_SOURCE_CONTROL_VERIFY_TOKEN_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Unauthorized))), + http.post(MOCK_SOURCE_CONTROL_VERIFY_TOKEN_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.Unauthorized, + }); + }), ); const result = await sourceControlClient.verifyToken(MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS); @@ -28,9 +38,11 @@ describe('verify sourceControl request', () => { it('should set default error title when sourceControl verify response status 500', async () => { server.use( - rest.post(MOCK_SOURCE_CONTROL_VERIFY_TOKEN_URL, (req, res, ctx) => - res(ctx.status(HttpStatusCode.InternalServerError)), - ), + http.post(MOCK_SOURCE_CONTROL_VERIFY_TOKEN_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.InternalServerError, + }); + }), ); const result = await sourceControlClient.verifyToken(MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS); diff --git a/frontend/__tests__/components/Common/DateRangeViewer/DateRangeViewer.test.tsx b/frontend/__tests__/components/Common/DateRangeViewer/DateRangeViewer.test.tsx index e8401ba659..ae6b02646e 100644 --- a/frontend/__tests__/components/Common/DateRangeViewer/DateRangeViewer.test.tsx +++ b/frontend/__tests__/components/Common/DateRangeViewer/DateRangeViewer.test.tsx @@ -1,12 +1,35 @@ +import { + nextStep, + updateMetricsPageFailedTimeRangeInfos, + updateReportPageFailedTimeRangeInfos, +} from '@src/context/stepper/StepperSlice'; import DateRangeViewer from '@src/components/Common/DateRangeViewer'; -import { TDateRange } from '@src/context/config/configSlice'; +import { DateRangeList } from '@src/context/config/configSlice'; +import { formatDateToTimestampString } from '@src/utils/util'; +import { setupStore } from '@test/utils/setupStoreUtil'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { render } from '@testing-library/react'; +import { Provider } from 'react-redux'; +import React from 'react'; describe('DateRangeViewer', () => { - const setup = (dateRanges: TDateRange) => { - return render(); + let store = setupStore(); + const setup = (dateRanges: DateRangeList) => { + return render( + + + , + ); }; + + beforeEach(() => { + store = setupStore(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + const mockDateRanges = [ { startDate: '2024-03-19T00:00:00.000+08:00', @@ -42,4 +65,104 @@ describe('DateRangeViewer', () => { expect(getByText(/2024\/03\/19/)).toBeInTheDocument(); expect(getByText(/2024\/03\/21/)).toBeInTheDocument(); }); + + describe('DateRangeViewer in metrics page', () => { + beforeEach(() => { + store.dispatch(nextStep()); + }); + it('should show priority high icon given click expand button and there are some error infos', async () => { + const failedTimeRangeList = [ + { + startDate: formatDateToTimestampString('2024-02-01T00:00:00.000+08:00'), + errors: { isBoardInfoError: true }, + }, + { + startDate: formatDateToTimestampString('2024-03-19T00:00:00.000+08:00'), + errors: { isPipelineStepError: true }, + }, + { + startDate: formatDateToTimestampString('2024-04-01T00:00:00.000+08:00'), + errors: { isPipelineInfoError: true }, + }, + ]; + store.dispatch(updateMetricsPageFailedTimeRangeInfos(failedTimeRangeList)); + const { getByLabelText } = setup(mockDateRanges); + expect(screen.getByTestId('PriorityHighIcon')).toBeInTheDocument(); + + await userEvent.click(getByLabelText('expandMore')); + expect(screen.getAllByTestId('PriorityHighIcon')).toHaveLength(4); + }); + + it('should not show priority high icon given click expand button and there is no error info', async () => { + const failedTimeRangeList = [ + { + startDate: formatDateToTimestampString('2024-02-01T00:00:00.000+08:00'), + errors: { isBoardInfoError: false }, + }, + { + startDate: formatDateToTimestampString('2024-03-19T00:00:00.000+08:00'), + errors: { isPipelineStepError: false }, + }, + { + startDate: formatDateToTimestampString('2024-04-01T00:00:00.000+08:00'), + errors: { isPipelineInfoError: false }, + }, + ]; + store.dispatch(updateMetricsPageFailedTimeRangeInfos(failedTimeRangeList)); + const { getByLabelText } = setup(mockDateRanges); + + await userEvent.click(getByLabelText('expandMore')); + + expect(screen.queryByTestId('PriorityHighIcon')).not.toBeInTheDocument(); + }); + }); + + describe('DateRangeViewer in report page', () => { + beforeEach(() => { + store.dispatch(nextStep()); + store.dispatch(nextStep()); + }); + it('should not show priority high icon in report page given click expand button and there is no error info', async () => { + const failedTimeRangeList = [ + { + startDate: formatDateToTimestampString('2024-02-01T00:00:00.000+08:00'), + errors: { isGainPollingUrlError: false }, + }, + { + startDate: formatDateToTimestampString('2024-03-19T00:00:00.000+08:00'), + errors: { isPollingError: false }, + }, + ]; + + store.dispatch(updateReportPageFailedTimeRangeInfos(failedTimeRangeList)); + const { getByLabelText } = setup(mockDateRanges); + + await userEvent.click(getByLabelText('expandMore')); + + expect(screen.queryByTestId('PriorityHighIcon')).not.toBeInTheDocument(); + }); + + it('should show priority high icon in report page given click expand button and there are some error infos', async () => { + const failedTimeRangeList = [ + { + startDate: formatDateToTimestampString('2024-02-01T00:00:00.000+08:00'), + errors: { isGainPollingUrlError: true }, + }, + { + startDate: formatDateToTimestampString('2024-03-19T00:00:00.000+08:00'), + errors: { isPollingError: true }, + }, + { + startDate: formatDateToTimestampString('2024-04-01T00:00:00.000+08:00'), + errors: { isPollingError: false }, + }, + ]; + store.dispatch(updateReportPageFailedTimeRangeInfos(failedTimeRangeList)); + const { getByLabelText } = setup(mockDateRanges); + expect(screen.getByTestId('PriorityHighIcon')).toBeInTheDocument(); + + await userEvent.click(getByLabelText('expandMore')); + expect(screen.getAllByTestId('PriorityHighIcon')).toHaveLength(3); + }); + }); }); diff --git a/frontend/__tests__/components/Common/ReportForThreeColumns.test.tsx b/frontend/__tests__/components/Common/ReportForThreeColumns.test.tsx index 3cb975f8da..752d4f60c7 100644 --- a/frontend/__tests__/components/Common/ReportForThreeColumns.test.tsx +++ b/frontend/__tests__/components/Common/ReportForThreeColumns.test.tsx @@ -1,7 +1,6 @@ import ReportForThreeColumns from '@src/components/Common/ReportForThreeColumns'; +import { LEAD_TIME_FOR_CHANGES, LOADING, VELOCITY } from '../../fixtures'; import { render, screen } from '@testing-library/react'; -import { LOADING, VELOCITY } from '../../fixtures'; -import React from 'react'; describe('Report for three columns', () => { it('should show loading when data is empty', () => { @@ -18,6 +17,20 @@ describe('Report for three columns', () => { { id: 2, name: 'name3', valuesList: [{ name: 'test3', value: '3' }] }, ]; + render( + , + ); + + expect(screen.getByTestId(LEAD_TIME_FOR_CHANGES)).toBeInTheDocument(); + }); + + it('should show table when data name contains emoji', () => { + const mockData = [ + { id: 0, name: 'name1/:rocket: Deploy prod', valuesList: [{ name: 'test1', value: '1' }] }, + { id: 1, name: 'name2/:rocket: Deploy prod', valuesList: [{ name: 'test2', value: '2' }] }, + { id: 2, name: 'name3/:rocket: Deploy prod', valuesList: [{ name: 'test3', value: '3' }] }, + ]; + render(); expect(screen.getByTestId(VELOCITY)).toBeInTheDocument(); diff --git a/frontend/__tests__/components/Common/ReportForTwoColumns.test.tsx b/frontend/__tests__/components/Common/ReportForTwoColumns.test.tsx new file mode 100644 index 0000000000..91cdda2aeb --- /dev/null +++ b/frontend/__tests__/components/Common/ReportForTwoColumns.test.tsx @@ -0,0 +1,29 @@ +import ReportForTwoColumns from '@src/components/Common/ReportForTwoColumns'; +import { REPORT_SUFFIX_UNITS } from '@src/constants/resources'; +import { render, screen } from '@testing-library/react'; +import { CYCLE_TIME, VELOCITY } from '../../fixtures'; + +describe('Report for two columns', () => { + it('should show table when data is not empty', () => { + const mockData = [ + { id: 0, name: 'name1', valueList: [{ value: '1' }] }, + { id: 1, name: 'name2', valueList: [{ value: '2' }] }, + { id: 2, name: 'name3', valueList: [{ value: '3' }] }, + ]; + + render(); + + expect(screen.getByTestId(VELOCITY)).toBeInTheDocument(); + }); + + it('should show table when data with Units is not empty', () => { + const mockData = [ + { id: 0, name: 'name1', valueList: [{ value: 1, units: REPORT_SUFFIX_UNITS.PER_CARD }] }, + { id: 1, name: 'name2', valueList: [{ value: 2, units: REPORT_SUFFIX_UNITS.PER_CARD }] }, + ]; + + render(); + + expect(screen.getByTestId(CYCLE_TIME)).toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/components/HomeGuide/HomeGuide.test.tsx b/frontend/__tests__/components/HomeGuide/HomeGuide.test.tsx index fb7682d4b9..bcd63ba2b6 100644 --- a/frontend/__tests__/components/HomeGuide/HomeGuide.test.tsx +++ b/frontend/__tests__/components/HomeGuide/HomeGuide.test.tsx @@ -87,7 +87,7 @@ describe('HomeGuide', () => { fireEvent.change(input); await waitFor(() => { - expect(mockedUseAppDispatch).toHaveBeenCalledTimes(4); + expect(mockedUseAppDispatch).toHaveBeenCalledTimes(5); expect(navigateMock).toHaveBeenCalledWith(METRICS_PAGE_ROUTE); }); }); diff --git a/frontend/__tests__/constants/fileConfig/fileConfig.test.ts b/frontend/__tests__/constants/fileConfig/fileConfig.test.ts index 93ec4b8d15..e0ee8726fa 100644 --- a/frontend/__tests__/constants/fileConfig/fileConfig.test.ts +++ b/frontend/__tests__/constants/fileConfig/fileConfig.test.ts @@ -5,11 +5,13 @@ import { CHINA_CALENDAR, DEFAULT_REWORK_SETTINGS, } from '../../fixtures'; +import { SortType } from '@src/containers/ConfigStep/DateRangePicker/types'; import { convertToNewFileConfig } from '@src/constants/fileConfig'; describe('#fileConfig', () => { const BASIC_NEW_CONFIG = { projectName: 'ConfigFileForImporting', + sortType: SortType.DEFAULT, dateRange: [ { startDate: '2023-03-16T00:00:00.000+08:00', diff --git a/frontend/__tests__/containers/ConfigStep/MetricsTypeCheckbox.test.tsx b/frontend/__tests__/containers/ConfigStep/BasicInfo.test.tsx similarity index 84% rename from frontend/__tests__/containers/ConfigStep/MetricsTypeCheckbox.test.tsx rename to frontend/__tests__/containers/ConfigStep/BasicInfo.test.tsx index 2a46a935b4..f505be840b 100644 --- a/frontend/__tests__/containers/ConfigStep/MetricsTypeCheckbox.test.tsx +++ b/frontend/__tests__/containers/ConfigStep/BasicInfo.test.tsx @@ -2,7 +2,6 @@ import { ALL, DEV_CHANGE_FAILURE_RATE, CLASSIFICATION, - CONFIG_TITLE, CYCLE_TIME, DEPLOYMENT_FREQUENCY, LEAD_TIME_FOR_CHANGES, @@ -12,11 +11,13 @@ import { REWORK_TIMES, VELOCITY, } from '../../fixtures'; -import { MetricsTypeCheckbox } from '@src/containers/ConfigStep/MetricsTypeCheckbox'; +import { basicInfoDefaultValues } from '@src/containers/ConfigStep/Form/useDefaultValues'; +import { basicInfoSchema } from '@src/containers/ConfigStep/Form/schema'; import { render, waitFor, within, screen } from '@testing-library/react'; import { SELECTED_VALUE_SEPARATOR } from '@src/constants/commons'; import BasicInfo from '@src/containers/ConfigStep/BasicInfo'; import { setupStore } from '../../utils/setupStoreUtil'; +import { FormProvider } from '@test/utils/FormProvider'; import userEvent from '@testing-library/user-event'; import { Provider } from 'react-redux'; @@ -27,8 +28,9 @@ describe('MetricsTypeCheckbox', () => { store = setupStore(); return render( - - + + + , ); }; @@ -146,23 +148,4 @@ describe('MetricsTypeCheckbox', () => { expect(getByText(/Metrics is required/i)).toBeInTheDocument(); }); - - it('should show board component when click MetricsTypeCheckbox selection velocity ', async () => { - setup(); - await userEvent.click(screen.getByRole('combobox', { name: REQUIRED_DATA })); - const listBox = within(screen.getByRole('listbox')); - await userEvent.click(listBox.getByRole('option', { name: VELOCITY })); - expect(screen.getAllByText(CONFIG_TITLE.BOARD)[0]).toBeInTheDocument(); - }); - - it('should hidden board component when MetricsTypeCheckbox select is null given MetricsTypeCheckbox select is velocity ', async () => { - setup(); - - await userEvent.click(screen.getByRole('combobox', { name: REQUIRED_DATA })); - const requireDateSelection = within(screen.getByRole('listbox')); - await userEvent.click(requireDateSelection.getByRole('option', { name: VELOCITY })); - await userEvent.click(requireDateSelection.getByRole('option', { name: VELOCITY })); - - expect(screen.queryByText(CONFIG_TITLE.BOARD)).not.toBeInTheDocument(); - }); }); diff --git a/frontend/__tests__/containers/ConfigStep/Board.test.tsx b/frontend/__tests__/containers/ConfigStep/Board.test.tsx index 85f092ed0d..cfed37773b 100644 --- a/frontend/__tests__/containers/ConfigStep/Board.test.tsx +++ b/frontend/__tests__/containers/ConfigStep/Board.test.tsx @@ -10,17 +10,20 @@ import { FAKE_TOKEN, REVERIFY, } from '../../fixtures'; +import { boardConfigDefaultValues } from '@src/containers/ConfigStep/Form/useDefaultValues'; +import { boardConfigSchema } from '@src/containers/ConfigStep/Form/schema'; import { render, screen, waitFor, within } from '@testing-library/react'; import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; import { boardClient } from '@src/clients/board/BoardClient'; import { Board } from '@src/containers/ConfigStep/Board'; import { setupStore } from '../../utils/setupStoreUtil'; +import { FormProvider } from '@test/utils/FormProvider'; import { TimeoutError } from '@src/errors/TimeoutError'; import userEvent from '@testing-library/user-event'; +import { HttpResponse, http, delay } from 'msw'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; import { HttpStatusCode } from 'axios'; -import { rest } from 'msw'; export const fillBoardFieldsInformation = async () => { await userEvent.type(screen.getByLabelText(/board id/i), '1'); @@ -33,16 +36,16 @@ let store = null; const server = setupServer(); -const mockVerifySuccess = (delay = 0) => { +const mockVerifySuccess = (delayValue = 0) => { server.use( - rest.post(MOCK_BOARD_URL_FOR_JIRA, (_, res, ctx) => - res( - ctx.json({ + http.post(MOCK_BOARD_URL_FOR_JIRA, async () => { + await delay(delayValue); + return new HttpResponse( + JSON.stringify({ projectKey: 'FAKE', }), - ctx.delay(delay), - ), - ), + ); + }), ); }; @@ -59,7 +62,9 @@ describe('Board', () => { store = setupStore(); return render( - + + + , ); }; @@ -240,7 +245,13 @@ describe('Board', () => { }); it('should show error message when board verify response status is 401', async () => { - server.use(rest.post(MOCK_BOARD_URL_FOR_JIRA, (_, res, ctx) => res(ctx.status(HttpStatusCode.Unauthorized)))); + server.use( + http.post(MOCK_BOARD_URL_FOR_JIRA, () => { + return new HttpResponse(null, { + status: HttpStatusCode.Unauthorized, + }); + }), + ); setup(); await fillBoardFieldsInformation(); @@ -252,4 +263,39 @@ describe('Board', () => { ).toBeInTheDocument(); }); }); + + it('should close alert modal when user manually close the alert', async () => { + setup(); + await fillBoardFieldsInformation(); + const timeoutError = new TimeoutError('', AXIOS_REQUEST_ERROR_CODE.TIMEOUT); + boardClient.getVerifyBoard = jest.fn().mockImplementation(() => Promise.reject(timeoutError)); + + await userEvent.click(screen.getByText(VERIFY)); + + expect(screen.getByTestId('timeoutAlert')).toBeInTheDocument(); + + await userEvent.click(screen.getByLabelText('Close')); + + expect(screen.queryByLabelText('timeoutAlert')).not.toBeInTheDocument(); + }); + + it('should allow user to re-submit when user interact again with form given form is already submit successfully', async () => { + setup(); + mockVerifySuccess(); + await fillBoardFieldsInformation(); + + expect(screen.getByRole('button', { name: /verify/i })).toBeEnabled(); + + await userEvent.click(screen.getByText(/verify/i)); + + expect(await screen.findByRole('button', { name: /reset/i })).toBeInTheDocument(); + expect(await screen.findByRole('button', { name: /verified/i })).toBeDisabled(); + + const emailInput = (await screen.findByRole('textbox', { name: 'Email' })) as HTMLInputElement; + await userEvent.clear(emailInput); + await userEvent.type(emailInput, 'other@qq.com'); + const verifyButton = await screen.findByRole('button', { name: /verify/i }); + + expect(verifyButton).toBeEnabled(); + }); }); diff --git a/frontend/__tests__/containers/ConfigStep/ConfigStep.test.tsx b/frontend/__tests__/containers/ConfigStep/ConfigStep.test.tsx index 012b813ac9..3b25695ade 100644 --- a/frontend/__tests__/containers/ConfigStep/ConfigStep.test.tsx +++ b/frontend/__tests__/containers/ConfigStep/ConfigStep.test.tsx @@ -15,42 +15,110 @@ import { VELOCITY, VERIFIED, VERIFY, + ALL, + FAKE_TOKEN, + PIPELINE_TOOL_TOKEN_INPUT_LABEL, } from '../../fixtures'; -import { fillBoardFieldsInformation } from '@test/containers/ConfigStep/Board.test'; +import { + basicInfoSchema, + boardConfigSchema, + pipelineToolSchema, + sourceControlSchema, + IBasicInfoData, + IBoardConfigData, + IPipelineToolData, + ISourceControlData, +} from '@src/containers/ConfigStep/Form/schema'; +import { + basicInfoDefaultValues, + boardConfigDefaultValues, + pipelineToolDefaultValues, + sourceControlDefaultValues, +} from '@src/containers/ConfigStep/Form/useDefaultValues'; import { act, render, screen, waitFor, within } from '@testing-library/react'; import { setupStore } from '../../utils/setupStoreUtil'; +import { yupResolver } from '@hookform/resolvers/yup'; import userEvent from '@testing-library/user-event'; import ConfigStep from '@src/containers/ConfigStep'; import { closeMuiModal } from '@test/testUtils'; +import { useForm } from 'react-hook-form'; +import { HttpResponse, http } from 'msw'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; -import { rest } from 'msw'; +import { HttpStatusCode } from 'axios'; import dayjs from 'dayjs'; const server = setupServer( - rest.post(MOCK_PIPELINE_VERIFY_URL, (_, res, ctx) => res(ctx.status(204))), - rest.post(MOCK_BOARD_URL_FOR_JIRA, (_, res, ctx) => - res( - ctx.status(200), - ctx.json({ + http.post(MOCK_PIPELINE_VERIFY_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.NoContent, + }); + }), + http.post(MOCK_BOARD_URL_FOR_JIRA, () => { + return new HttpResponse( + JSON.stringify({ projectKey: 'FAKE', }), - ), - ), + { + status: HttpStatusCode.Ok, + }, + ); + }), ); +export const fillBoardFieldsInformation = async () => { + await userEvent.type(screen.getByLabelText(/board id/i), '1'); + await userEvent.type(screen.getByLabelText(/email/i), 'fake@qq.com'); + await userEvent.type(screen.getByLabelText(/site/i), 'fake'); + await userEvent.type(screen.getByLabelText(/token/i), FAKE_TOKEN); +}; + let store = null; jest.mock('@src/context/config/configSlice', () => ({ ...jest.requireActual('@src/context/config/configSlice'), selectWarningMessage: jest.fn().mockReturnValue('Test warning Message'), })); +const ConfigStepWithFormInstances = () => { + const basicInfoMethods = useForm({ + defaultValues: basicInfoDefaultValues, + resolver: yupResolver(basicInfoSchema), + mode: 'onChange', + }); + + const boardConfigMethods = useForm({ + defaultValues: boardConfigDefaultValues, + resolver: yupResolver(boardConfigSchema), + mode: 'onChange', + }); + + const pipelineToolMethods = useForm({ + defaultValues: pipelineToolDefaultValues, + resolver: yupResolver(pipelineToolSchema), + mode: 'onChange', + }); + + const sourceControlMethods = useForm({ + defaultValues: sourceControlDefaultValues, + resolver: yupResolver(sourceControlSchema), + mode: 'onChange', + }); + return ( + + ); +}; + describe('ConfigStep', () => { const setup = () => { store = setupStore(); return render( - + , ); }; @@ -235,7 +303,9 @@ describe('ConfigStep', () => { const requireDateSelection = within(screen.getByRole('listbox')); await userEvent.click(requireDateSelection.getByRole('option', { name: DEPLOYMENT_FREQUENCY })); await closeMuiModal(userEvent); - const tokenNode = within(screen.getByTestId('pipelineToolTextField')).getByLabelText('input Token'); + const tokenNode = within(screen.getByTestId('pipelineToolTextField')).getByLabelText( + PIPELINE_TOOL_TOKEN_INPUT_LABEL, + ); await userEvent.type(tokenNode, FAKE_PIPELINE_TOKEN); const submitButton = screen.getByText(VERIFY); await userEvent.click(submitButton); @@ -248,4 +318,18 @@ describe('ConfigStep', () => { expect(screen.queryByText(VERIFIED)).toBeVisible(); expect(screen.queryByText(RESET)).toBeVisible(); }); + + it('should show all forms given all metrics selected', async () => { + setup(); + + const requiredMetricsField = screen.getByRole('combobox', { name: REQUIRED_DATA }); + await userEvent.click(requiredMetricsField); + const requireDateSelection = within(screen.getByRole('listbox')); + await userEvent.click(requireDateSelection.getByRole('option', { name: ALL })); + await closeMuiModal(userEvent); + + expect(screen.getByLabelText('Board Config')).toBeInTheDocument(); + expect(screen.getByLabelText('Pipeline Tool Config')).toBeInTheDocument(); + expect(screen.getByLabelText('Source Control Config')).toBeInTheDocument(); + }); }); diff --git a/frontend/__tests__/containers/ConfigStep/DateRangePicker.test.tsx b/frontend/__tests__/containers/ConfigStep/DateRangePicker.test.tsx index 9e961d2c1e..27e0375673 100644 --- a/frontend/__tests__/containers/ConfigStep/DateRangePicker.test.tsx +++ b/frontend/__tests__/containers/ConfigStep/DateRangePicker.test.tsx @@ -1,19 +1,23 @@ -import { - initDeploymentFrequencySettings, - updateShouldGetBoardConfig, - updateShouldGetPipelineConfig, -} from '@src/context/Metrics/metricsSlice'; +import { updateShouldGetBoardConfig, updateShouldGetPipelineConfig } from '@src/context/Metrics/metricsSlice'; +import { SortedDateRangeType, sortFn, SortType } from '@src/containers/ConfigStep/DateRangePicker/types'; +import { basicInfoDefaultValues } from '@src/containers/ConfigStep/Form/useDefaultValues'; import { DateRangePickerSection } from '@src/containers/ConfigStep/DateRangePicker'; +import { basicInfoSchema } from '@src/containers/ConfigStep/Form/schema'; import { ERROR_DATE, TIME_RANGE_ERROR_MESSAGE } from '../../fixtures'; import { render, screen, within } from '@testing-library/react'; import { setupStore } from '../../utils/setupStoreUtil'; +import { FormProvider } from '@test/utils/FormProvider'; import userEvent from '@testing-library/user-event'; +import { ThemeProvider } from '@mui/material'; import { Provider } from 'react-redux'; +import sortBy from 'lodash/sortBy'; +import { theme } from '@src/theme'; +import get from 'lodash/get'; import React from 'react'; import dayjs from 'dayjs'; -const START_DATE_LABEL = 'From *'; -const END_DATE_LABEL = 'To *'; +const START_DATE_LABEL = 'From'; +const END_DATE_LABEL = 'To'; const TODAY = dayjs('2024-03-20'); const INPUT_DATE_VALUE = TODAY.format('MM/DD/YYYY'); let store = setupStore(); @@ -30,12 +34,19 @@ const setup = () => { store = setupStore(); return render( - + + + + + , ); }; describe('DateRangePickerSection', () => { + beforeEach(() => { + setup(); + }); describe('Single range behaviors', () => { const expectDate = (inputDate: HTMLInputElement) => { expect(inputDate.value).toEqual(expect.stringContaining(TODAY.date().toString())); @@ -44,15 +55,11 @@ describe('DateRangePickerSection', () => { }; it('should render DateRangePicker', () => { - setup(); - expect(screen.queryAllByText(START_DATE_LABEL)).toHaveLength(1); expect(screen.queryAllByText(END_DATE_LABEL)).toHaveLength(1); }); it('should show right start date when input a valid date given init start date is null ', async () => { - setup(); - const startDateInput = screen.getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement; await userEvent.type(startDateInput, INPUT_DATE_VALUE); @@ -60,8 +67,6 @@ describe('DateRangePickerSection', () => { }); it('should show right end date when input a valid date given init end date is null ', async () => { - setup(); - const endDateInput = screen.getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; await userEvent.type(endDateInput, INPUT_DATE_VALUE); @@ -69,8 +74,6 @@ describe('DateRangePickerSection', () => { }); it('should Auto-fill endDate which is after startDate 13 days when fill right startDate ', async () => { - setup(); - const endDate = TODAY.add(13, 'day'); const startDateInput = screen.getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement; const endDateInput = screen.getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; @@ -82,8 +85,6 @@ describe('DateRangePickerSection', () => { }); it('should Auto-clear endDate when its corresponding startDate is cleared ', async () => { - setup(); - const addButton = screen.getByLabelText('Button for adding date range'); await userEvent.click(addButton); const rangeDate1 = ['03/01/2024', '03/10/2024']; @@ -98,8 +99,6 @@ describe('DateRangePickerSection', () => { }); it('should not auto change startDate when its corresponding endDate changes ', async () => { - setup(); - const startDateInput = screen.getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement; const endDateInput = screen.getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; const startDate = dayjs('2024-03-20').format('MM/DD/YYYY'); @@ -113,8 +112,6 @@ describe('DateRangePickerSection', () => { }); it('should not Auto-fill endDate which is after startDate 14 days when fill wrong format startDate ', async () => { - setup(); - const startDateInput = screen.getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement; const endDateInput = screen.getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; await userEvent.type(startDateInput, ERROR_DATE); @@ -124,32 +121,24 @@ describe('DateRangePickerSection', () => { }); it('should dispatch update configuration when change startDate', async () => { - setup(); - const startDateInput = screen.getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement; await userEvent.type(startDateInput, INPUT_DATE_VALUE); expect(updateShouldGetBoardConfig).toHaveBeenCalledWith(true); expect(updateShouldGetPipelineConfig).toHaveBeenCalledWith(true); - expect(initDeploymentFrequencySettings).toHaveBeenCalled(); }); it('should dispatch update configuration when change endDate', async () => { - setup(); - const endDateInput = screen.getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; await userEvent.type(endDateInput, INPUT_DATE_VALUE); expect(updateShouldGetBoardConfig).toHaveBeenCalledWith(true); expect(updateShouldGetPipelineConfig).toHaveBeenCalledWith(true); - expect(initDeploymentFrequencySettings).toHaveBeenCalled(); }); }); describe('Multiple range amount behaviors', () => { it('should not show remove button given there is only one range by default', () => { - setup(); - const removeButton = screen.queryByRole('button', { name: 'Remove' }); const ranges = screen.getAllByLabelText('Range picker row'); @@ -158,8 +147,6 @@ describe('DateRangePickerSection', () => { }); it('should allow user to add up to 6 ranges', async () => { - setup(); - const addButton = screen.getByLabelText('Button for adding date range'); const defaultRanges = screen.getAllByLabelText('Range picker row'); @@ -173,8 +160,6 @@ describe('DateRangePickerSection', () => { }); it('should show remove button when ranges are more than 1 and user is able to remove the range itself by clicking the remove button within that row', async () => { - setup(); - const addButton = screen.getByLabelText('Button for adding date range'); await userEvent.click(addButton); const ranges = screen.getAllByLabelText('Range picker row'); @@ -194,8 +179,6 @@ describe('DateRangePickerSection', () => { }); it('should dispatch update configuration when remove the range', async () => { - setup(); - const addButton = screen.getByLabelText('Button for adding date range'); await userEvent.click(addButton); const ranges = screen.getAllByLabelText('Range picker row'); @@ -210,13 +193,11 @@ describe('DateRangePickerSection', () => { expect(updateShouldGetBoardConfig).toHaveBeenCalledWith(true); expect(updateShouldGetPipelineConfig).toHaveBeenCalledWith(true); - expect(initDeploymentFrequencySettings).toHaveBeenCalled(); }); }); describe('Multiple ranges date interactions', () => { it('should auto fill end date when change star date by cloeset earliest date of other ranges', async () => { - setup(); const rangeDate1 = ['03/12/2024', '03/25/2024']; const rangeDate2 = ['03/08/2024']; @@ -237,7 +218,6 @@ describe('DateRangePickerSection', () => { }); it('should display error message for start-date and end-date respectively when time ranges conflict', async () => { - setup(); const rangeDate1 = ['03/12/2024', '03/25/2024']; const rangeDate2 = ['03/08/2024', '03/26/2024']; @@ -257,5 +237,159 @@ describe('DateRangePickerSection', () => { expect(screen.getByText(TIME_RANGE_ERROR_MESSAGE.START_DATE_INVALID_TEXT)).toBeVisible(); expect(screen.getByText(TIME_RANGE_ERROR_MESSAGE.END_DATE_INVALID_TEXT)).toBeVisible(); }); + + it('should provide unified error message when given all invalid time input', async () => { + const correctRange = ['03/15/2024', '03/25/2024']; + const rangeOfTooEarly = ['03/15/1899', '03/25/1898']; + const rangeOfInvalidFormat = ['XXxYY/2024', 'ZZ/11/2024']; + const startDateRequiredErrorMessage = 'Start date is required'; + const endDateRequiredErrorMessage = 'End date is required'; + const unifiedStartDateErrorMessage = 'Start date is invalid'; + const unifiedEndDateErrorMessage = 'End date is invalid'; + + const ranges = screen.getAllByLabelText('Range picker row'); + const startDateInput = within(ranges[0]).getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement; + const endDateInput = within(ranges[0]).getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; + await userEvent.type(startDateInput, rangeOfTooEarly[0]); + await userEvent.type(endDateInput, rangeOfTooEarly[1]); + + expect(await screen.findByText(unifiedStartDateErrorMessage)).toBeVisible(); + expect(await screen.findByText(unifiedEndDateErrorMessage)).toBeVisible(); + + await userEvent.clear(startDateInput); + await userEvent.clear(endDateInput); + await userEvent.keyboard('{Tab}'); + + expect(await screen.findByText(startDateRequiredErrorMessage)).toBeVisible(); + expect(await screen.findByText(endDateRequiredErrorMessage)).toBeVisible(); + + await userEvent.type(startDateInput, correctRange[0]); + await userEvent.type(endDateInput, correctRange[1]); + + expect(screen.queryByText(startDateRequiredErrorMessage)).toBeNull(); + expect(screen.queryByText(endDateRequiredErrorMessage)).toBeNull(); + expect(screen.queryByText(unifiedStartDateErrorMessage)).toBeNull(); + expect(screen.queryByText(unifiedEndDateErrorMessage)).toBeNull(); + + await userEvent.type(startDateInput, rangeOfInvalidFormat[0]); + await userEvent.type(endDateInput, rangeOfInvalidFormat[1]); + + expect(screen.queryByText(startDateRequiredErrorMessage)).toBeNull(); + expect(screen.queryByText(endDateRequiredErrorMessage)).toBeNull(); + expect(screen.queryByText(unifiedStartDateErrorMessage)).toBeVisible(); + expect(screen.queryByText(unifiedEndDateErrorMessage)).toBeVisible(); + }); + }); + + describe('Sort date range behaviors', () => { + it('should not show sort button given only one date range', async () => { + const rangeDate1 = ['03/15/2024', '03/25/2024']; + const ranges = screen.getAllByLabelText('Range picker row'); + const startDate1Input = within(ranges[0]).getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement; + const endDate1Input = within(ranges[0]).getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; + await userEvent.type(startDate1Input, rangeDate1[0]); + await userEvent.type(endDate1Input, rangeDate1[1]); + const sortButtonContainer = screen.queryByLabelText('Sorting date range'); + expect(sortButtonContainer).toBeNull(); + }); + + it('should show sort button given more than one time range', async () => { + const rangeDate1 = ['03/15/2024', '03/25/2024']; + const rangeDate2 = ['03/08/2024', '03/11/2024']; + + const addButton = screen.getByLabelText('Button for adding date range'); + await userEvent.click(addButton); + const ranges = screen.getAllByLabelText('Range picker row'); + const startDate1Input = within(ranges[0]).getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement; + const endDate1Input = within(ranges[0]).getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; + const startDate2Input = within(ranges[1]).getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement; + const endDate12nput = within(ranges[1]).getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; + await userEvent.type(startDate1Input, rangeDate1[0]); + await userEvent.type(endDate1Input, rangeDate1[1]); + await userEvent.type(startDate2Input, rangeDate2[0]); + await userEvent.type(endDate12nput, rangeDate2[1]); + const sortButton = screen.getByLabelText('Sorting date range'); + expect(sortButton).toBeInTheDocument(); + }); + + it('should disabled sort button given exist errors in date range', async () => { + const rangeDate1 = ['03/12/2024', '03/25/2024']; + const rangeDate2 = ['03/08/2024', '03/26/2024']; + + const addButton = screen.getByLabelText('Button for adding date range'); + await userEvent.click(addButton); + await userEvent.click(addButton); + const ranges = screen.getAllByLabelText('Range picker row'); + const startDate1Input = within(ranges[0]).getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement; + const endDate1Input = within(ranges[0]).getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; + const startDate2Input = within(ranges[1]).getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement; + const endDate12nput = within(ranges[1]).getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; + await userEvent.type(startDate1Input, rangeDate1[0]); + await userEvent.type(endDate1Input, rangeDate1[1]); + await userEvent.type(startDate2Input, rangeDate2[0]); + await userEvent.type(endDate12nput, rangeDate2[1]); + const sortButtonContainer = screen.queryByLabelText('sort button'); + expect(sortButtonContainer).toBeDisabled(); + }); + + it('should update sort status when handleSortTypeChange is called', async () => { + const rangeDate1 = ['03/15/2024', '03/25/2024']; + const rangeDate2 = ['03/08/2024', '03/11/2024']; + + const addButton = screen.getByLabelText('Button for adding date range'); + await userEvent.click(addButton); + const ranges = screen.getAllByLabelText('Range picker row'); + const startDate1Input = within(ranges[0]).getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement; + const endDate1Input = within(ranges[0]).getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; + const startDate2Input = within(ranges[1]).getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement; + const endDate12nput = within(ranges[1]).getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; + await userEvent.type(startDate1Input, rangeDate1[0]); + await userEvent.type(endDate1Input, rangeDate1[1]); + await userEvent.type(startDate2Input, rangeDate2[0]); + await userEvent.type(endDate12nput, rangeDate2[1]); + const sortButton = screen.getByLabelText('sort button'); + await userEvent.click(sortButton); + expect(screen.getByRole('button', { name: 'Descending' })).toBeInTheDocument(); + await userEvent.click(sortButton); + expect(screen.getByRole('button', { name: 'Ascending' })).toBeInTheDocument(); + }); + const dateRange1: SortedDateRangeType = { + startDate: '2024-03-10', + endDate: '2024-03-15', + sortIndex: 1, + startDateError: null, + endDateError: null, + }; + + const dateRange2: SortedDateRangeType = { + startDate: '2024-03-05', + endDate: '2024-03-08', + sortIndex: 2, + startDateError: null, + endDateError: null, + }; + + const dateRange3: SortedDateRangeType = { + startDate: '2024-03-20', + endDate: '2024-03-25', + sortIndex: 3, + startDateError: null, + endDateError: null, + }; + + it('should correctly sort by default sortIndex', () => { + const sorted = [dateRange1, dateRange2, dateRange3].sort(sortFn[SortType.DEFAULT]); + expect(sorted).toEqual([dateRange1, dateRange2, dateRange3]); + }); + + it('should correctly sort by startDate in descending order', () => { + const sorted = sortBy([dateRange1, dateRange2, dateRange3], get(sortFn, SortType.DESCENDING)); + expect(sorted).toEqual([dateRange3, dateRange1, dateRange2]); + }); + + it('should correctly sort by startDate in ascending order', () => { + const sorted = sortBy([dateRange1, dateRange2, dateRange3], get(sortFn, SortType.ASCENDING)); + expect(sorted).toEqual([dateRange2, dateRange1, dateRange3]); + }); }); }); diff --git a/frontend/__tests__/containers/ConfigStep/PipelineTool.test.tsx b/frontend/__tests__/containers/ConfigStep/PipelineTool.test.tsx index af1c886bf4..cb86e8fa89 100644 --- a/frontend/__tests__/containers/ConfigStep/PipelineTool.test.tsx +++ b/frontend/__tests__/containers/ConfigStep/PipelineTool.test.tsx @@ -10,21 +10,27 @@ import { MOCK_PIPELINE_VERIFY_URL, FAKE_PIPELINE_TOKEN, REVERIFY, + PIPELINE_TOOL_TOKEN_INPUT_LABEL, + TIMEOUT_ALERT_TEST_ID, } from '../../fixtures'; +import { pipelineToolDefaultValues } from '@src/containers/ConfigStep/Form/useDefaultValues'; import { pipelineToolClient } from '@src/clients/pipeline/PipelineToolClient'; +import { pipelineToolSchema } from '@src/containers/ConfigStep/Form/schema'; import { render, screen, waitFor, within } from '@testing-library/react'; import { PipelineTool } from '@src/containers/ConfigStep/PipelineTool'; import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; import { setupStore } from '../../utils/setupStoreUtil'; +import { FormProvider } from '@test/utils/FormProvider'; +import { TimeoutError } from '@src/errors/TimeoutError'; import userEvent from '@testing-library/user-event'; +import { HttpResponse, delay, http } from 'msw'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; import { HttpStatusCode } from 'axios'; -import { rest } from 'msw'; export const fillPipelineToolFieldsInformation = async () => { const tokenInput = within(screen.getByTestId('pipelineToolTextField')).getByLabelText( - 'input Token', + PIPELINE_TOOL_TOKEN_INPUT_LABEL, ) as HTMLInputElement; await userEvent.type(tokenInput, FAKE_PIPELINE_TOKEN); @@ -33,26 +39,35 @@ export const fillPipelineToolFieldsInformation = async () => { let store = null; -const server = setupServer(rest.post(MOCK_PIPELINE_VERIFY_URL, (req, res, ctx) => res(ctx.status(204)))); +const server = setupServer( + http.post(MOCK_PIPELINE_VERIFY_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.NoContent, + }); + }), +); const originalVerify = pipelineToolClient.verify; describe('PipelineTool', () => { beforeAll(() => server.listen()); afterAll(() => server.close()); + afterEach(() => { + store = null; + pipelineToolClient.verify = originalVerify; + }); + store = setupStore(); const setup = () => { store = setupStore(); return render( - + + + , ); }; - afterEach(() => { - store = null; - pipelineToolClient.verify = originalVerify; - }); it('should show pipelineTool title and fields when render pipelineTool component ', () => { setup(); @@ -74,7 +89,7 @@ describe('PipelineTool', () => { it('should clear all fields information when click reset button', async () => { setup(); const tokenInput = within(screen.getByTestId('pipelineToolTextField')).getByLabelText( - 'input Token', + PIPELINE_TOOL_TOKEN_INPUT_LABEL, ) as HTMLInputElement; await fillPipelineToolFieldsInformation(); @@ -95,11 +110,11 @@ describe('PipelineTool', () => { await userEvent.click(screen.getByText(VERIFY)); - expect(screen.getByTestId('timeoutAlert')).toBeInTheDocument(); + expect(screen.getByTestId(TIMEOUT_ALERT_TEST_ID)).toBeInTheDocument(); await userEvent.click(screen.getByRole('button', { name: RESET })); - expect(screen.queryByTestId('timeoutAlert')).not.toBeInTheDocument(); + expect(screen.queryByTestId(TIMEOUT_ALERT_TEST_ID)).not.toBeInTheDocument(); }); it('should hidden timeout alert when the error type of api call becomes other', async () => { @@ -109,13 +124,13 @@ describe('PipelineTool', () => { await userEvent.click(screen.getByText(VERIFY)); - expect(screen.getByTestId('timeoutAlert')).toBeInTheDocument(); + expect(screen.getByTestId(TIMEOUT_ALERT_TEST_ID)).toBeInTheDocument(); pipelineToolClient.verify = jest.fn().mockResolvedValue({ code: HttpStatusCode.Unauthorized }); await userEvent.click(screen.getByText(REVERIFY)); - expect(screen.queryByTestId('timeoutAlert')).not.toBeInTheDocument(); + expect(screen.queryByTestId(TIMEOUT_ALERT_TEST_ID)).not.toBeInTheDocument(); }); it('should show detail options when click pipelineTool fields', async () => { @@ -144,7 +159,7 @@ describe('PipelineTool', () => { await fillPipelineToolFieldsInformation(); const mockInfo = 'mockToken'; const tokenInput = within(screen.getByTestId('pipelineToolTextField')).getByLabelText( - 'input Token', + PIPELINE_TOOL_TOKEN_INPUT_LABEL, ) as HTMLInputElement; await userEvent.type(tokenInput, mockInfo); await userEvent.clear(tokenInput); @@ -162,7 +177,7 @@ describe('PipelineTool', () => { it('should show error message when focus on field given an empty value', async () => { setup(); - await userEvent.click(screen.getByLabelText('input Token')); + await userEvent.click(screen.getByLabelText(PIPELINE_TOOL_TOKEN_INPUT_LABEL)); expect(screen.getByText(TOKEN_ERROR_MESSAGE[1])).toBeInTheDocument(); expect(screen.getByText(TOKEN_ERROR_MESSAGE[1])).toHaveStyle(ERROR_MESSAGE_COLOR); @@ -172,7 +187,7 @@ describe('PipelineTool', () => { setup(); const mockInfo = 'mockToken'; const tokenInput = within(screen.getByTestId('pipelineToolTextField')).getByLabelText( - 'input Token', + PIPELINE_TOOL_TOKEN_INPUT_LABEL, ) as HTMLInputElement; await userEvent.type(tokenInput, mockInfo); @@ -204,7 +219,12 @@ describe('PipelineTool', () => { it('should check loading animation when click verify button', async () => { server.use( - rest.post(MOCK_PIPELINE_VERIFY_URL, (_, res, ctx) => res(ctx.delay(300), ctx.status(HttpStatusCode.Ok))), + http.post(MOCK_PIPELINE_VERIFY_URL, async () => { + await delay(300); + return new HttpResponse(null, { + status: HttpStatusCode.Ok, + }); + }), ); const { container } = setup(); await fillPipelineToolFieldsInformation(); @@ -214,7 +234,13 @@ describe('PipelineTool', () => { }); it('should check error text appear when pipelineTool verify response status is 401', async () => { - server.use(rest.post(MOCK_PIPELINE_VERIFY_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Unauthorized)))); + server.use( + http.post(MOCK_PIPELINE_VERIFY_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.Unauthorized, + }); + }), + ); const { getByText } = setup(); await fillPipelineToolFieldsInformation(); @@ -224,4 +250,46 @@ describe('PipelineTool', () => { expect(getByText('Token is incorrect!')).toBeInTheDocument(); }); }); + + it('should close alert modal when user manually close the alert', async () => { + setup(); + await fillPipelineToolFieldsInformation(); + const timeoutError = new TimeoutError('', AXIOS_REQUEST_ERROR_CODE.TIMEOUT); + pipelineToolClient.verify = jest.fn().mockImplementation(() => Promise.resolve(timeoutError)); + + await userEvent.click(screen.getByText(VERIFY)); + + expect(await screen.getByTestId(TIMEOUT_ALERT_TEST_ID)).toBeInTheDocument(); + + await userEvent.click(screen.getByLabelText('Close')); + + expect(screen.queryByTestId(TIMEOUT_ALERT_TEST_ID)).not.toBeInTheDocument(); + }); + + it('should allow user to re-submit when user interact again with form given form is already submit successfully', async () => { + server.use( + http.post(MOCK_PIPELINE_VERIFY_URL, async () => { + await delay(100); + return new HttpResponse(null, { + status: HttpStatusCode.NoContent, + }); + }), + ); + setup(); + await fillPipelineToolFieldsInformation(); + + expect(screen.getByRole('button', { name: /verify/i })).toBeEnabled(); + + await userEvent.click(screen.getByText(/verify/i)); + + expect(await screen.findByRole('button', { name: /reset/i })).toBeInTheDocument(); + expect(await screen.findByRole('button', { name: /verified/i })).toBeDisabled(); + + const tokenInput = (await screen.findByLabelText('Token *')) as HTMLInputElement; + await userEvent.clear(tokenInput); + await userEvent.type(tokenInput, FAKE_PIPELINE_TOKEN); + const verifyButton = await screen.findByRole('button', { name: /verify/i }); + + expect(verifyButton).toBeEnabled(); + }); }); diff --git a/frontend/__tests__/containers/ConfigStep/SortingDateRange.test.tsx b/frontend/__tests__/containers/ConfigStep/SortingDateRange.test.tsx new file mode 100644 index 0000000000..0aa7047f9f --- /dev/null +++ b/frontend/__tests__/containers/ConfigStep/SortingDateRange.test.tsx @@ -0,0 +1,62 @@ +import { AscendingIcon, DescendingIcon } from '@src/containers/ConfigStep/DateRangePicker/style'; +import { SortingDateRange } from '@src/containers/ConfigStep/DateRangePicker/SortingDateRange'; +import { setupStore } from '@test/utils/setupStoreUtil'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { ThemeProvider } from '@mui/material'; +import { Provider } from 'react-redux'; +import { theme } from '@src/theme'; +import React from 'react'; + +let store = setupStore(); +const setup = () => { + store = setupStore(); + return render( + + + + + , + ); +}; + +describe('SortDateRange button behaviors', () => { + it('should show sort time rang button', () => { + setup(); + const sortButtonContainer = screen.getByLabelText('Sorting date range'); + expect(sortButtonContainer).toBeInTheDocument(); + + const sortTextButton = screen.getByText('Default sort'); + expect(sortTextButton).toBeInTheDocument(); + + const sortButton = screen.getByLabelText('sort button'); + expect(sortButton).toBeInTheDocument(); + }); + + it('should change sort order iterately given SortButton is clicked', async () => { + setup(); + const sortButton = screen.getByLabelText('sort button'); + await userEvent.click(sortButton); + const arrowDropDown = screen.getByRole('button', { name: 'Descending' }); + expect(arrowDropDown).toBeInTheDocument(); + await userEvent.click(sortButton); + const arrowDropUp = screen.getByRole('button', { name: 'Ascending' }); + expect(arrowDropUp).toBeInTheDocument(); + }); + + it('should render AscendingIcon with correct styles', () => { + render(); + + const ascendingIcon = screen.getByTestId('ArrowDropUpIcon'); + expect(ascendingIcon).toBeInTheDocument(); + expect(ascendingIcon).toHaveStyle(`color: ${theme.main.button.disabled.color}`); + }); + + it('should render DescendingIcon with correct styles', () => { + render(); + + const descendingIcon = screen.getByTestId('ArrowDropDownIcon'); + expect(descendingIcon).toBeInTheDocument(); + expect(descendingIcon).toHaveStyle(`color: ${theme.main.button.disabled.color}`); + }); +}); diff --git a/frontend/__tests__/containers/ConfigStep/SourceControl.test.tsx b/frontend/__tests__/containers/ConfigStep/SourceControl.test.tsx index 23d32ca6ca..c06de088a8 100644 --- a/frontend/__tests__/containers/ConfigStep/SourceControl.test.tsx +++ b/frontend/__tests__/containers/ConfigStep/SourceControl.test.tsx @@ -10,33 +10,43 @@ import { VERIFIED, VERIFY, } from '../../fixtures'; -import { initDeploymentFrequencySettings, updateShouldGetPipelineConfig } from '@src/context/Metrics/metricsSlice'; +import { sourceControlDefaultValues } from '@src/containers/ConfigStep/Form/useDefaultValues'; import { AXIOS_REQUEST_ERROR_CODE, SOURCE_CONTROL_TYPES } from '@src/constants/resources'; import { sourceControlClient } from '@src/clients/sourceControl/SourceControlClient'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { updateShouldGetPipelineConfig } from '@src/context/Metrics/metricsSlice'; +import { sourceControlSchema } from '@src/containers/ConfigStep/Form/schema'; import { SourceControl } from '@src/containers/ConfigStep/SourceControl'; +import { render, screen, act, waitFor } from '@testing-library/react'; import { setupStore } from '../../utils/setupStoreUtil'; +import { FormProvider } from '@test/utils/FormProvider'; import userEvent from '@testing-library/user-event'; +import { HttpResponse, delay, http } from 'msw'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; import { HttpStatusCode } from 'axios'; -import { rest } from 'msw'; import React from 'react'; -export const fillSourceControlFieldsInformation = () => { - const mockInfo = 'AAAAA_XXXXXX' - .replace('AAAAA', 'ghpghoghughsghr') - .replace('XXXXXX', '1A2b1A2b1A2b1A2b1A2b1A2b1A2b1A2b1A2b'); +const mockValidFormtToken = 'AAAAA_XXXXXX' + .replace('AAAAA', 'ghpghoghughsghr') + .replace('XXXXXX', '1A2b1A2b1A2b1A2b1A2b1A2b1A2b1A2b1A2b'); + +export const fillSourceControlFieldsInformation = async () => { const tokenInput = screen.getByTestId('sourceControlTextField').querySelector('input') as HTMLInputElement; - fireEvent.change(tokenInput, { target: { value: mockInfo } }); + await userEvent.type(tokenInput, mockValidFormtToken); - expect(tokenInput.value).toEqual(mockInfo); + expect(tokenInput.value).toEqual(mockValidFormtToken); }; let store = null; -const server = setupServer(rest.post(MOCK_SOURCE_CONTROL_VERIFY_TOKEN_URL, (req, res, ctx) => res(ctx.status(204)))); +const server = setupServer( + http.post(MOCK_SOURCE_CONTROL_VERIFY_TOKEN_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.NoContent, + }); + }), +); const originalVerifyToken = sourceControlClient.verifyToken; @@ -49,19 +59,22 @@ jest.mock('@src/context/Metrics/metricsSlice', () => ({ describe('SourceControl', () => { beforeAll(() => server.listen()); afterAll(() => server.close()); + afterEach(() => { + store = null; + sourceControlClient.verifyToken = originalVerifyToken; + }); + store = setupStore(); const setup = () => { store = setupStore(); return render( - + + + , ); }; - afterEach(() => { - store = null; - sourceControlClient.verifyToken = originalVerifyToken; - }); it('should show sourceControl title and fields when render sourceControl component', () => { setup(); @@ -83,9 +96,9 @@ describe('SourceControl', () => { setup(); const tokenInput = screen.getByTestId('sourceControlTextField').querySelector('input') as HTMLInputElement; - fillSourceControlFieldsInformation(); + await fillSourceControlFieldsInformation(); - await userEvent.click(screen.getByText(VERIFY)); + await userEvent.click(screen.getByRole('button', { name: VERIFY })); await waitFor(async () => { expect(screen.getByRole('button', { name: RESET })).toBeTruthy(); @@ -133,22 +146,24 @@ describe('SourceControl', () => { expect(queryByTestId('timeoutAlert')).not.toBeInTheDocument(); }); - it('should enable verify button when all fields checked correctly given disable verify button', () => { + it('should enable verify button when all fields checked correctly given disable verify button', async () => { setup(); const verifyButton = screen.getByRole('button', { name: VERIFY }); expect(verifyButton).toBeDisabled(); - fillSourceControlFieldsInformation(); + await fillSourceControlFieldsInformation(); - expect(verifyButton).toBeEnabled(); + await waitFor(() => { + expect(screen.getByRole('button', { name: VERIFY })).toBeEnabled(); + }); }); it('should show reset button and verified button when verify successfully', async () => { setup(); - fillSourceControlFieldsInformation(); + await fillSourceControlFieldsInformation(); - fireEvent.click(screen.getByText(VERIFY)); + await userEvent.click(screen.getByText(VERIFY)); await waitFor(() => { expect(screen.getByText(RESET)).toBeTruthy(); @@ -161,26 +176,24 @@ describe('SourceControl', () => { it('should reload pipeline config when reset fields', async () => { setup(); - fillSourceControlFieldsInformation(); + await fillSourceControlFieldsInformation(); await userEvent.click(screen.getByText(VERIFY)); await userEvent.click(screen.getByRole('button', { name: RESET })); - fillSourceControlFieldsInformation(); + await fillSourceControlFieldsInformation(); expect(updateShouldGetPipelineConfig).toHaveBeenCalledWith(true); - expect(initDeploymentFrequencySettings).toHaveBeenCalled(); }); - it('should show error message and error style when token is empty', () => { + it('should show error message and error style when token is empty', async () => { setup(); - fillSourceControlFieldsInformation(); - const tokenInput = screen.getByTestId('sourceControlTextField').querySelector('input') as HTMLInputElement; - - fireEvent.change(tokenInput, { target: { value: '' } }); + act(() => { + tokenInput.focus(); + }); expect(screen.getByText(TOKEN_ERROR_MESSAGE[1])).toBeInTheDocument(); expect(screen.getByText(TOKEN_ERROR_MESSAGE[1])).toHaveStyle(ERROR_MESSAGE_COLOR); @@ -192,21 +205,24 @@ describe('SourceControl', () => { expect(screen.queryByText(TOKEN_ERROR_MESSAGE[1])).not.toBeInTheDocument(); }); - it('should show error message when focus on field given an empty value', () => { + it('should show error message when focus on field given an empty value', async () => { setup(); - fireEvent.focus(screen.getByLabelText('input Token')); + const tokenInput = screen.getByTestId('sourceControlTextField').querySelector('input') as HTMLInputElement; + act(() => { + tokenInput.focus(); + }); expect(screen.getByText(TOKEN_ERROR_MESSAGE[1])).toBeInTheDocument(); expect(screen.getByText(TOKEN_ERROR_MESSAGE[1])).toHaveStyle(ERROR_MESSAGE_COLOR); }); - it('should show error message and error style when token is invalid', () => { + it('should show error message and error style when token is invalid', async () => { setup(); const mockInfo = 'mockToken'; const tokenInput = screen.getByTestId('sourceControlTextField').querySelector('input') as HTMLInputElement; - fireEvent.change(tokenInput, { target: { value: mockInfo } }); + await userEvent.type(tokenInput, mockInfo); expect(tokenInput.value).toEqual(mockInfo); expect(screen.getByText(TOKEN_ERROR_MESSAGE[0])).toBeInTheDocument(); @@ -215,16 +231,60 @@ describe('SourceControl', () => { it('should show error notification when sourceControl verify response status is 401', async () => { server.use( - rest.post(MOCK_SOURCE_CONTROL_VERIFY_TOKEN_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Unauthorized))), + http.post(MOCK_SOURCE_CONTROL_VERIFY_TOKEN_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.Unauthorized, + }); + }), ); setup(); - fillSourceControlFieldsInformation(); + await fillSourceControlFieldsInformation(); + await userEvent.click(screen.getByRole('button', { name: VERIFY })); - fireEvent.click(screen.getByRole('button', { name: VERIFY })); + expect(screen.getByText(MOCK_SOURCE_CONTROL_VERIFY_ERROR_CASE_TEXT)).toBeInTheDocument(); + }); - await waitFor(() => { - expect(screen.getByText(MOCK_SOURCE_CONTROL_VERIFY_ERROR_CASE_TEXT)).toBeInTheDocument(); + it('should close alert modal when user manually close the alert', async () => { + setup(); + await fillSourceControlFieldsInformation(); + sourceControlClient.verifyToken = jest.fn().mockResolvedValue({ + code: AXIOS_REQUEST_ERROR_CODE.TIMEOUT, }); + + await userEvent.click(screen.getByText(VERIFY)); + + expect(await screen.getByTestId('timeoutAlert')).toBeInTheDocument(); + + await userEvent.click(screen.getByLabelText('Close')); + + expect(screen.queryByLabelText('timeoutAlert')).not.toBeInTheDocument(); + }); + + it('should allow user to re-submit when user interact again with form given form is already submit successfully', async () => { + server.use( + http.post(MOCK_SOURCE_CONTROL_VERIFY_TOKEN_URL, async () => { + await delay(100); + return new HttpResponse(null, { + status: HttpStatusCode.NoContent, + }); + }), + ); + setup(); + await fillSourceControlFieldsInformation(); + + expect(screen.getByRole('button', { name: /verify/i })).toBeEnabled(); + + await userEvent.click(screen.getByText(/verify/i)); + + expect(await screen.findByRole('button', { name: /reset/i })).toBeInTheDocument(); + expect(await screen.findByRole('button', { name: /verified/i })).toBeDisabled(); + + const tokenInput = (await screen.findByLabelText('Token *')) as HTMLInputElement; + await userEvent.clear(tokenInput); + await userEvent.type(tokenInput, mockValidFormtToken); + const verifyButton = await screen.findByRole('button', { name: /verify/i }); + + expect(verifyButton).toBeEnabled(); }); }); diff --git a/frontend/__tests__/containers/ConfigStep/TimeoutAlet.test.tsx b/frontend/__tests__/containers/ConfigStep/TimeoutAlet.test.tsx index 986fdd3461..bb112b6730 100644 --- a/frontend/__tests__/containers/ConfigStep/TimeoutAlet.test.tsx +++ b/frontend/__tests__/containers/ConfigStep/TimeoutAlet.test.tsx @@ -4,41 +4,20 @@ import userEvent from '@testing-library/user-event'; import React from 'react'; describe('TimeoutAlert', () => { - const setIsShowAlert = jest.fn(); - const setup = ( - setIsShowAlert: (value: boolean) => void, - isShowAlert: boolean, - isVerifyTimeOut: boolean, - moduleType: string, - ) => { - return render( - , - ); + const onCloseSpy = jest.fn(); + const setup = (onClose: () => void, showAlert: boolean, moduleType: string) => { + return render(); }; it('should render board message given moduleType is board', () => { - setup(setIsShowAlert, true, true, 'Board'); + setup(onCloseSpy, true, 'Board'); const message = screen.getByText('Board'); expect(message).toBeInTheDocument(); }); - it('should not render the alert given isVerifyTimeOut or isShowAlert is false', () => { - setup(setIsShowAlert, false, true, 'Board'); - expect(screen.queryByText('Board')).not.toBeInTheDocument(); - - setup(setIsShowAlert, true, false, 'Board'); - - expect(screen.queryByText('Board')).not.toBeInTheDocument(); - }); - - it('should call setIsShowAlert with false when click the close icon given init value', async () => { - setup(setIsShowAlert, true, true, 'any'); + it('should call onCloseSpy when click the close icon given init value', async () => { + setup(onCloseSpy, true, 'any'); const closeIcon = screen.getByTestId('CloseIcon'); act(() => { @@ -46,8 +25,7 @@ describe('TimeoutAlert', () => { }); await waitFor(() => { - expect(setIsShowAlert).toHaveBeenCalledTimes(1); - expect(setIsShowAlert).toHaveBeenCalledWith(false); + expect(onCloseSpy).toHaveBeenCalledTimes(1); }); }); }); diff --git a/frontend/__tests__/containers/MetricsStep/Crews.test.tsx b/frontend/__tests__/containers/MetricsStep/Crews.test.tsx index e7ca40166f..38693e6601 100644 --- a/frontend/__tests__/containers/MetricsStep/Crews.test.tsx +++ b/frontend/__tests__/containers/MetricsStep/Crews.test.tsx @@ -13,7 +13,7 @@ const assigneeFilterValues = ['lastAssignee', 'historicalAssignee']; jest.mock('@src/context/Metrics/metricsSlice', () => ({ ...jest.requireActual('@src/context/Metrics/metricsSlice'), - selectMetricsContent: jest.fn().mockReturnValue({ users: ['crew A', 'crew B'] }), + selectMetricsContent: jest.fn().mockReturnValue({ users: ['crew A', 'crew B'], pipelineCrews: ['A', 'B'] }), })); const mockedUseAppDispatch = jest.fn(); @@ -23,10 +23,10 @@ jest.mock('@src/hooks/useAppDispatch', () => ({ let store = setupStore(); -const setup = () => { +const setup = (type?: string) => { return render( - + , ); }; @@ -46,13 +46,18 @@ describe('Crew', () => { expect(screen.getByText(mockTitle)).toBeInTheDocument(); }); - it('should selected all options by default when initializing', () => { + it('should selected all options by default when initializing given type is board', () => { setup(); expect(screen.getByRole('button', { name: 'crew A' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'crew B' })).toBeInTheDocument(); }); + it('should selected all options by default when initializing given type is other', () => { + setup('other'); + expect(screen.getByRole('button', { name: 'A' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'B' })).toBeInTheDocument(); + }); it('should show detail options when click Included crews button', async () => { setup(); diff --git a/frontend/__tests__/containers/MetricsStep/CycleTime.test.tsx b/frontend/__tests__/containers/MetricsStep/CycleTime.test.tsx index 52f70410c1..363e4cb1a0 100644 --- a/frontend/__tests__/containers/MetricsStep/CycleTime.test.tsx +++ b/frontend/__tests__/containers/MetricsStep/CycleTime.test.tsx @@ -6,7 +6,7 @@ import { updateTreatFlagCardAsBlock, } from '@src/context/Metrics/metricsSlice'; import { BOARD_MAPPING, ERROR_MESSAGE_TIME_DURATION, LIST_OPEN, NO_RESULT_DASH } from '../../fixtures'; -import { CYCLE_TIME_SETTINGS_TYPES, METRICS_CONSTANTS } from '@src/constants/resources'; +import { CYCLE_TIME_SETTINGS_TYPES, MESSAGE, METRICS_CONSTANTS } from '@src/constants/resources'; import { act, render, screen, waitFor, within } from '@testing-library/react'; import { CycleTime } from '@src/containers/MetricsStep/CycleTime'; import { setupStore } from '../../utils/setupStoreUtil'; @@ -320,7 +320,7 @@ describe('CycleTime', () => { setup(); await userEvent.click(screen.getByRole('radio', { name: cycleTimeTypeLabels[1] })); - expect(mockedUseAppDispatch).toHaveBeenCalledTimes(3); + expect(mockedUseAppDispatch).toHaveBeenCalledTimes(4); expect(mockedUseAppDispatch).toHaveBeenCalledWith(setCycleTimeSettingsType(CYCLE_TIME_SETTINGS_TYPES.BY_STATUS)); expect(mockedUseAppDispatch).toHaveBeenCalledWith( updateCycleTimeSettings( @@ -331,6 +331,7 @@ describe('CycleTime', () => { ), ); expect(mockedUseAppDispatch).toHaveBeenCalledWith(saveDoneColumn([])); + expect(mockedUseAppDispatch).toHaveBeenCalledWith(updateTreatFlagCardAsBlock(true)); }); describe('cycle time by status', () => { @@ -406,4 +407,23 @@ describe('CycleTime', () => { expect(mockedUseAppDispatch).not.toHaveBeenCalledWith(saveDoneColumn([])); }); }); + + [CYCLE_TIME_SETTINGS_TYPES.BY_STATUS, CYCLE_TIME_SETTINGS_TYPES.BY_COLUMN].forEach((cycleTimeSettingsType) => { + it('should show warning message given both mapping block column and add flag as block', () => { + (selectMetricsContent as jest.Mock).mockReturnValue({ + cycleTimeSettingsType, + cycleTimeSettings: [ + ...cycleTimeSettings, + { + column: 'Blocked', + status: 'BLOCKED', + value: 'Block', + }, + ], + }); + setup(); + + expect(screen.getByText(MESSAGE.FLAG_CARD_DROPPED_WARNING)).toBeVisible(); + }); + }); }); diff --git a/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/BranchSelection.test.tsx b/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/BranchSelection.test.tsx index 7ae9786bf7..47690d1e41 100644 --- a/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/BranchSelection.test.tsx +++ b/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/BranchSelection.test.tsx @@ -4,12 +4,19 @@ import { updatePipelineToolVerifyResponse, updateSourceControl } from '@src/cont import { render, screen, waitFor } from '@testing-library/react'; import { setupStore } from '@test/utils/setupStoreUtil'; import userEvent from '@testing-library/user-event'; +import { HttpResponse, http } from 'msw'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; -import { rest } from 'msw'; +import { HttpStatusCode } from 'axios'; import React from 'react'; -const server = setupServer(rest.post(MOCK_SOURCE_CONTROL_VERIFY_BRANCH_URL, (req, res, ctx) => res(ctx.status(204)))); +const server = setupServer( + http.post(MOCK_SOURCE_CONTROL_VERIFY_BRANCH_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.NoContent, + }); + }), +); export const MOCK_SOURCE_CONTROL_BRANCHES_SELECTED = ['OPT-1', 'OPT-2', 'OPT-3']; @@ -48,7 +55,7 @@ describe('BranchSelection', () => { return render( - + , ); }; @@ -85,7 +92,13 @@ describe('BranchSelection', () => { }); it('should show error text when API return 400 error', async () => { - server.use(rest.post(MOCK_SOURCE_CONTROL_VERIFY_BRANCH_URL, (req, res, ctx) => res(ctx.status(400)))); + server.use( + http.post(MOCK_SOURCE_CONTROL_VERIFY_BRANCH_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.BadRequest, + }); + }), + ); setup(); await waitFor(() => { @@ -94,10 +107,20 @@ describe('BranchSelection', () => { }); it('should show cancel button when retry successfully', async () => { - server.use(rest.post(MOCK_SOURCE_CONTROL_VERIFY_BRANCH_URL, (req, res) => res.networkError('error'))); + server.use( + http.post(MOCK_SOURCE_CONTROL_VERIFY_BRANCH_URL, () => { + return HttpResponse.error(); + }), + ); setup(); const retryButtons = await screen.findAllByTestId('ReplayIcon'); - server.use(rest.post(MOCK_SOURCE_CONTROL_VERIFY_BRANCH_URL, (req, res, ctx) => res(ctx.status(204)))); + server.use( + http.post(MOCK_SOURCE_CONTROL_VERIFY_BRANCH_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.NoContent, + }); + }), + ); await userEvent.click(retryButtons[0]); diff --git a/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/DeploymentFrequencySettings.test.tsx b/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/DeploymentFrequencySettings.test.tsx index ba5313cce1..bed2e4a8c1 100644 --- a/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/DeploymentFrequencySettings.test.tsx +++ b/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/DeploymentFrequencySettings.test.tsx @@ -7,10 +7,28 @@ import { DEPLOYMENT_FREQUENCY_SETTINGS, LIST_OPEN, LOADING, ORGANIZATION, REMOVE import { DeploymentFrequencySettings } from '@src/containers/MetricsStep/DeploymentFrequencySettings'; import { IUseVerifyPipeLineToolStateInterface } from '@src/hooks/useGetPipelineToolInfoEffect'; import { TokenAccessAlert } from '@src/containers/MetricsStep/TokenAccessAlert'; -import { act, render, screen, within } from '@testing-library/react'; +import { act, render, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Provider } from 'react-redux'; -import { store } from '@src/store'; + +import { setupStore } from '@test/utils/setupStoreUtil'; + +let mockSelectShouldGetPipelineConfig = true; +let mockSelectPipelineNames: string[] = []; +const mockSelectStepsParams = { + organizationId: 0, + pipelineType: '', + token: '', + params: [ + { + pipelineName: mockSelectPipelineNames, + repository: '', + orgName: '', + startTime: '2024-02-01T00:00:00.000+08:00', + endTime: '2024-02-15T23:59:59.999+08:00', + }, + ], +}; jest.mock('@src/hooks', () => ({ ...jest.requireActual('@src/hooks'), @@ -23,24 +41,24 @@ jest.mock('@src/context/Metrics/metricsSlice', () => ({ deleteADeploymentFrequencySetting: jest.fn(), updateDeploymentFrequencySettings: jest.fn(), selectDeploymentFrequencySettings: jest.fn().mockReturnValue([ - { id: 0, organization: '', pipelineName: '', steps: '', branches: [] }, + { id: 0, organization: 'mockOrgName', pipelineName: '1', steps: '', branches: [] }, { id: 1, organization: '', pipelineName: '', steps: '', branches: [] }, ]), selectOrganizationWarningMessage: jest.fn().mockReturnValue(null), selectPipelineNameWarningMessage: jest.fn().mockReturnValue(null), selectStepWarningMessage: jest.fn().mockReturnValue(null), selectMetricsContent: jest.fn().mockReturnValue({ pipelineCrews: [], users: [] }), - selectShouldGetPipelineConfig: jest.fn().mockReturnValue(true), + selectShouldGetPipelineConfig: jest.fn().mockImplementation(() => mockSelectShouldGetPipelineConfig), })); jest.mock('@src/context/config/configSlice', () => ({ ...jest.requireActual('@src/context/config/configSlice'), - selectPipelineOrganizations: jest.fn().mockReturnValue(['mockOrgName']), - selectPipelineNames: jest.fn().mockReturnValue(['']), + selectPipelineOrganizations: jest.fn().mockReturnValue(['mockOrgName', 'mockOrgName2']), + selectPipelineNames: jest.fn().mockImplementation(() => mockSelectPipelineNames), selectSteps: jest.fn().mockReturnValue(['']), selectBranches: jest.fn().mockReturnValue(['']), selectPipelineCrews: jest.fn().mockReturnValue(['']), - selectStepsParams: jest.fn().mockReturnValue(['']), + selectStepsParams: jest.fn().mockImplementation(() => mockSelectStepsParams), selectDateRange: jest.fn().mockReturnValue(['']), })); @@ -53,6 +71,7 @@ jest.mock('@src/hooks/useMetricsStepValidationCheckContext', () => ({ })); const mockGetPipelineToolInfoOkResponse = { + isFirstFetch: false, isLoading: false, apiCallFunc: jest.fn(), result: { @@ -67,6 +86,7 @@ const mockGetPipelineToolInfoOkResponse = { repository: 'git@github.com:au-heartbeat/Heartbeat.git', steps: [':pipeline: Upload pipeline.yml'], branches: [], + crews: [], }, ], }, @@ -80,15 +100,46 @@ jest.mock('@src/hooks/useGetPipelineToolInfoEffect', () => ({ useGetPipelineToolInfoEffect: () => mockGetPipelineToolInfoSpy, })); describe('DeploymentFrequencySettings', () => { - const setup = () => - render( + let store = null; + const setup = () => { + store = setupStore(); + return render( , ); + }; afterEach(() => { jest.clearAllMocks(); }); + beforeEach(() => { + mockSelectShouldGetPipelineConfig = true; + mockSelectPipelineNames = []; + mockGetPipelineToolInfoSpy = mockGetPipelineToolInfoOkResponse; + }); + + it('should show crew settings when select pipelineName', async () => { + mockSelectPipelineNames = ['Heartbeat']; + const { getAllByRole, getByRole } = await setup(); + await act(async () => { + await userEvent.click(getAllByRole('button', { name: LIST_OPEN })[0]); + }); + + let listBox = within(getByRole('listbox')); + await act(async () => { + await userEvent.click(listBox.getByText('mockOrgName')); + }); + await act(async () => { + await userEvent.click(getAllByRole('button', { name: LIST_OPEN })[1]); + }); + listBox = within(getByRole('listbox')); + await act(async () => { + await userEvent.click(listBox.getByText('Heartbeat')); + }); + waitFor(() => { + expect(screen.getByText('Crew setting (optional)')).toBeInTheDocument(); + }); + }); it('should render DeploymentFrequencySettings component', () => { const { getByText, getAllByText } = setup(); @@ -122,14 +173,21 @@ describe('DeploymentFrequencySettings', () => { }); const listBox = within(getByRole('listbox')); await act(async () => { - await userEvent.click(listBox.getByText('mockOrgName')); + await userEvent.click(listBox.getByText('mockOrgName2')); }); expect(updateDeploymentFrequencySettings).toHaveBeenCalledTimes(1); }); + it('show render crews component when all pipelines load completed', () => { + mockSelectShouldGetPipelineConfig = false; + setup(); + expect(screen.getByText('Crew setting (optional)')).toBeInTheDocument(); + }); + it('should display error UI when get pipeline info client returns non-200 code', () => { mockGetPipelineToolInfoSpy = { + isFirstFetch: false, isLoading: false, apiCallFunc: jest.fn(), result: { @@ -145,6 +203,7 @@ describe('DeploymentFrequencySettings', () => { it('should show loading when get pipeline info client pending', () => { mockGetPipelineToolInfoSpy = { + isFirstFetch: false, isLoading: true, apiCallFunc: jest.fn(), result: { @@ -158,6 +217,15 @@ describe('DeploymentFrequencySettings', () => { expect(screen.getByTestId(LOADING)).toBeInTheDocument(); }); + it('should not show crews part when pipeline is loading', async () => { + mockGetPipelineToolInfoSpy = { + ...mockGetPipelineToolInfoOkResponse, + isFirstFetch: true, + }; + setup(); + expect(screen.queryByText('Crews Setting')).toBeNull(); + }); + it('renders without error when errorDetail is provided', () => { const { queryByLabelText } = render(); expect(queryByLabelText('alert for token access error')).toBeInTheDocument(); diff --git a/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection.test.tsx b/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection.test.tsx index 8affbba0be..ea2b3b4a45 100644 --- a/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection.test.tsx +++ b/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection.test.tsx @@ -15,11 +15,18 @@ import { act, render, screen, waitFor, within } from '@testing-library/react'; import { metricsClient } from '@src/clients/MetricsClient'; import { setupStore } from '@test/utils/setupStoreUtil'; import userEvent from '@testing-library/user-event'; +import { HttpResponse, http } from 'msw'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; -import { rest } from 'msw'; +import { HttpStatusCode } from 'axios'; -const server = setupServer(rest.post(MOCK_SOURCE_CONTROL_VERIFY_BRANCH_URL, (req, res, ctx) => res(ctx.status(204)))); +const server = setupServer( + http.post(MOCK_SOURCE_CONTROL_VERIFY_BRANCH_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.NoContent, + }); + }), +); jest.mock('@src/context/Metrics/metricsSlice', () => ({ ...jest.requireActual('@src/context/Metrics/metricsSlice'), @@ -38,13 +45,15 @@ jest.mock('@src/context/config/configSlice', () => ({ selectStepsParams: jest.fn().mockReturnValue({ buildId: '', organizationId: '', - params: { - endTime: 1681747200000, - orgName: '', - pipelineName: '', - repository: '', - startTime: 1680537600000, - }, + params: [ + { + endTime: 1681747200000, + orgName: '', + pipelineName: '', + repository: '', + startTime: 1680537600000, + }, + ], pipelineType: 'BuildKite', token: '', }), @@ -87,6 +96,7 @@ describe('PipelineMetricSelection', () => { }; const mockHandleClickRemoveButton = jest.fn(); const mockUpdatePipeline = jest.fn(); + const mockSetLoadingCompletedNumber = jest.fn(); const setup = async ( deploymentFrequencySetting: IPipelineConfig, @@ -104,6 +114,8 @@ describe('PipelineMetricSelection', () => { onUpdatePipeline={mockUpdatePipeline} isDuplicated={isDuplicated} isInfoLoading={false} + totalPipelineNumber={2} + setLoadingCompletedNumber={mockSetLoadingCompletedNumber} /> , ); @@ -146,7 +158,6 @@ describe('PipelineMetricSelection', () => { }); it('should show step selection when select organization and pipelineName', async () => { - metricsClient.getSteps = jest.fn().mockImplementation(() => ['steps1', 'steps2']); const { getByText } = await setup( { ...deploymentFrequencySetting, organization: 'mockOrgName', pipelineName: 'mockName' }, false, @@ -161,7 +172,7 @@ describe('PipelineMetricSelection', () => { it('should show error message pop when getSteps failed', async () => { metricsClient.getSteps = jest.fn().mockImplementation(() => { - throw new Error('error message'); + return Promise.reject('error'); }); const { getByText, getByRole, getAllByRole } = await setup( { id: 0, organization: 'mockOrgName', pipelineName: 'mockName', step: '', branches: [] }, @@ -178,12 +189,14 @@ describe('PipelineMetricSelection', () => { }); await waitFor(() => { - expect(getByText('Failed to get BuildKite steps: error message')).toBeInTheDocument(); + expect(getByText('Failed to get BuildKite steps')).toBeInTheDocument(); }); - expect(mockUpdatePipeline).toHaveBeenCalledTimes(2); + expect(mockUpdatePipeline).toHaveBeenCalledTimes(3); }); it('should show no steps warning message when getSteps succeed but get no steps', async () => { - metricsClient.getSteps = jest.fn().mockReturnValue({ response: [], haveStep: false }); + metricsClient.getSteps = jest + .fn() + .mockReturnValue({ response: [], haveStep: false, pipelineCrews: [], branches: [] }); const { getByText, getByRole, getAllByRole } = await setup( { id: 0, organization: 'mockOrgName', pipelineName: 'mockName', step: '', branches: [] }, false, @@ -201,7 +214,7 @@ describe('PipelineMetricSelection', () => { await waitFor(() => { expect( getByText( - 'There is no step during this period for this pipeline! Please change the search time in the Config page!', + 'There is no step during these periods for this pipeline! Please change the search time in the Config page!', ), ).toBeInTheDocument(); @@ -210,7 +223,9 @@ describe('PipelineMetricSelection', () => { }); it('should show no steps warning message when getSteps succeed but get no steps and isShowRemoveButton is true', async () => { - metricsClient.getSteps = jest.fn().mockReturnValue({ response: [], haveStep: false }); + metricsClient.getSteps = jest + .fn() + .mockReturnValue({ response: [], haveStep: false, pipelineCrews: [], branches: [] }); const { getByRole, getAllByRole } = await setup( { id: 0, organization: 'mockOrgName', pipelineName: 'mockName', step: '', branches: [] }, true, @@ -226,12 +241,14 @@ describe('PipelineMetricSelection', () => { }); await waitFor(() => { - expect(mockHandleClickRemoveButton).toHaveBeenCalledTimes(2); + expect(mockHandleClickRemoveButton).toHaveBeenCalledTimes(0); }); }); it('should show steps selection when getSteps succeed ', async () => { - metricsClient.getSteps = jest.fn().mockReturnValue({ response: ['steps'], haveStep: true }); + metricsClient.getSteps = jest + .fn() + .mockReturnValue({ response: ['steps'], haveStep: true, pipelineCrews: [], branches: [] }); const { getByRole, getByText, getAllByRole } = await setup( { id: 0, organization: 'mockOrgName', pipelineName: 'mockName', step: '', branches: [] }, false, @@ -257,7 +274,7 @@ describe('PipelineMetricSelection', () => { it('should show branches selection when getSteps succeed ', async () => { metricsClient.getSteps = jest .fn() - .mockReturnValue({ response: ['steps'], haveStep: true, branches: ['branch1', 'branch2'] }); + .mockReturnValue({ response: ['steps'], haveStep: true, branches: ['branch1', 'branch2'], pipelineCrews: [] }); const { getByRole, getByText } = await setup( { id: 0, organization: 'mockOrgName', pipelineName: 'mockName', step: '', branches: ['branch1', 'branch2'] }, false, @@ -279,7 +296,7 @@ describe('PipelineMetricSelection', () => { it('should show not show branches when deployment setting has branches given branches does not match pipeline ', async () => { metricsClient.getSteps = jest .fn() - .mockReturnValue({ response: ['steps'], haveStep: true, branches: ['branch1', 'branch2'] }); + .mockReturnValue({ response: ['steps'], haveStep: true, branches: ['branch1', 'branch2'], pipelineCrews: [] }); const { getByRole, queryByRole, getByText } = await setup( { id: 0, organization: 'mockOrgName3', pipelineName: 'mockName3', step: '', branches: ['branch6', 'branch7'] }, false, @@ -299,7 +316,9 @@ describe('PipelineMetricSelection', () => { }); it('should show duplicated message given duplicated id', async () => { - metricsClient.getSteps = jest.fn().mockReturnValue({ response: ['steps'], haveStep: true }); + metricsClient.getSteps = jest + .fn() + .mockReturnValue({ response: ['steps'], haveStep: true, pipelineCrews: [], branches: [] }); const { getByText } = await setup( { id: 0, organization: 'mockOrgName', pipelineName: 'mockName', step: 'step1', branches: [] }, false, diff --git a/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelectionPopupTestPartialFailed.test.tsx b/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelectionPopupTestPartialFailed.test.tsx new file mode 100644 index 0000000000..ce9032322d --- /dev/null +++ b/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelectionPopupTestPartialFailed.test.tsx @@ -0,0 +1,85 @@ +import { render, waitFor } from '@testing-library/react'; +import { setupStore } from '@test/utils/setupStoreUtil'; +import { Provider } from 'react-redux'; + +import { PipelineMetricSelection } from '@src/containers/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection'; +import { IPipelineConfig, updateShouldGetPipelineConfig } from '@src/context/Metrics/metricsSlice'; +import { addNotification } from '@src/context/notification/NotificationSlice'; +import { METRICS_DATA_FAIL_STATUS } from '@src/constants/commons'; +import { PIPELINE_SETTING_TYPES } from '@test/fixtures'; + +const store = setupStore(); +let stepFailStatus = METRICS_DATA_FAIL_STATUS.NOT_FAILED; + +jest.mock('@src/context/notification/NotificationSlice', () => ({ + ...jest.requireActual('@src/context/notification/NotificationSlice'), + addNotification: jest.fn().mockReturnValue({ type: 'ADD_NEW_NOTIFICATION' }), +})); + +jest.mock('@src/hooks/useGetMetricsStepsEffect', () => ({ + ...jest.requireActual('@src/hooks/useGetMetricsStepsEffect'), + + useGetMetricsStepsEffect: jest.fn().mockImplementation(() => { + return { + stepFailedStatus: stepFailStatus, + }; + }), +})); + +describe('PipelineMetricSelection', () => { + const deploymentFrequencySetting = { + id: 0, + organization: '', + pipelineName: '', + step: '', + branches: [], + }; + const mockHandleClickRemoveButton = jest.fn(); + const mockUpdatePipeline = jest.fn(); + const mockSetLoadingCompletedNumber = jest.fn(); + + const setup = async ( + deploymentFrequencySetting: IPipelineConfig, + isShowRemoveButton: boolean, + isDuplicated: boolean, + ) => { + store.dispatch(updateShouldGetPipelineConfig(true)); + return render( + + + , + ); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should show 4xx popup when call pipeline step to get partial 4xx error', async () => { + stepFailStatus = METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_4XX; + await setup(deploymentFrequencySetting, true, false); + + await waitFor(() => { + expect(addNotification).toHaveBeenCalled(); + }); + }); + + it('should show timeout popup when call pipeline step to get partial timeout error', async () => { + stepFailStatus = METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_TIMEOUT; + await setup(deploymentFrequencySetting, true, false); + + await waitFor(() => { + expect(addNotification).toHaveBeenCalled(); + }); + }); +}); diff --git a/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/SingleSelection.test.tsx b/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/SingleSelection.test.tsx index 7262668360..5943f07aa0 100644 --- a/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/SingleSelection.test.tsx +++ b/frontend/__tests__/containers/MetricsStep/DeploymentFrequencySettings/SingleSelection.test.tsx @@ -119,6 +119,6 @@ describe('SingleSelection', () => { }); expect(mockOnGetSteps).toHaveBeenCalledTimes(1); - expect(mockUpdatePipeline).toHaveBeenCalledTimes(2); + expect(mockUpdatePipeline).toHaveBeenCalledTimes(3); }); }); diff --git a/frontend/__tests__/containers/MetricsStep/MetricsStep.test.tsx b/frontend/__tests__/containers/MetricsStep/MetricsStep.test.tsx index 49346b1642..02de1c90cc 100644 --- a/frontend/__tests__/containers/MetricsStep/MetricsStep.test.tsx +++ b/frontend/__tests__/containers/MetricsStep/MetricsStep.test.tsx @@ -1,9 +1,9 @@ import { render, waitFor, within, screen } from '@testing-library/react'; import { setupStore } from '../../utils/setupStoreUtil'; import MetricsStep from '@src/containers/MetricsStep'; +import { HttpResponse, http } from 'msw'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; -import { rest } from 'msw'; import { CLASSIFICATION_SETTING, @@ -41,9 +41,11 @@ jest.mock('@src/context/notification/NotificationSlice', () => ({ let store = setupStore(); const server = setupServer( - rest.post(MOCK_PIPELINE_GET_INFO_URL, (req, res, ctx) => - res(ctx.status(200), ctx.body(JSON.stringify(MOCK_BUILD_KITE_GET_INFO_RESPONSE))), - ), + http.post(MOCK_PIPELINE_GET_INFO_URL, () => { + return new HttpResponse(JSON.stringify(MOCK_BUILD_KITE_GET_INFO_RESPONSE), { + status: HttpStatusCode.Ok, + }); + }), ); const setup = () => @@ -195,7 +197,6 @@ describe('MetricsStep', () => { { key: 'done', value: { name: 'Done', statuses: ['PRE-DONE,', 'DONE', 'CANCEL'] } }, ]; - store.dispatch(updateShouldGetBoardConfig(true)); store.dispatch(updateMetrics(REQUIRED_DATA_LIST)); store.dispatch(updateCycleTimeSettings(cycleTimeSettingsWithTwoDoneValue)); store.dispatch(saveDoneColumn(doneColumn)); @@ -208,7 +209,13 @@ describe('MetricsStep', () => { }); it('should reset real done when change Cycle time settings DONE to other status', async () => { - server.use(rest.post(MOCK_BOARD_INFO_URL, (req, res, ctx) => res(ctx.status(500)))); + server.use( + http.post(MOCK_BOARD_INFO_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.InternalServerError, + }); + }), + ); setup(); const realDoneSettingSection = screen.getByLabelText(REAL_DONE_SETTING_SECTION); @@ -217,14 +224,20 @@ describe('MetricsStep', () => { await userEvent.click(doneSelectTrigger as HTMLInputElement); - const noneOption = within(screen.getByRole('presentation')).getByText('----'); + const noneOption = within(screen.getAllByRole('presentation')[1]).getByText('----'); await userEvent.click(noneOption); expect(realDoneSettingSection).toHaveTextContent(SELECT_CONSIDER_AS_DONE_MESSAGE); }); it('should reset real done when change Cycle time settings other status to DONE', async () => { - server.use(rest.post(MOCK_BOARD_INFO_URL, (req, res, ctx) => res(ctx.status(500)))); + server.use( + http.post(MOCK_BOARD_INFO_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.InternalServerError, + }); + }), + ); setup(); const cycleTimeSettingsSection = screen.getByLabelText(CYCLE_TIME_SETTINGS_SECTION); const realDoneSettingSection = screen.getByLabelText(REAL_DONE_SETTING_SECTION); @@ -286,9 +299,12 @@ describe('MetricsStep', () => { }); it('should be render no card container when get board card when no data', async () => { + store.dispatch(updateShouldGetBoardConfig(true)); server.use( - rest.post(MOCK_BOARD_INFO_URL, (_, res, ctx) => { - return res(ctx.status(HttpStatusCode.Ok)); + http.post(MOCK_BOARD_INFO_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.Ok, + }); }), ); @@ -304,18 +320,94 @@ describe('MetricsStep', () => { ).toBeInTheDocument(); }); + it('should be render failed message container when get 4xx error', async () => { + store.dispatch(updateShouldGetBoardConfig(true)); + server.use( + http.post(MOCK_BOARD_INFO_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.BadRequest, + }); + }), + ); + + setup(); + + await waitFor(() => { + expect(screen.getByText('Failed to get Board configuration!')).toBeInTheDocument(); + }); + expect(screen.getByText('Please go back to the previous page and check your board info!')).toBeInTheDocument(); + }); + + it('should be render popup when get partial 4xx error', async () => { + store.dispatch(updateShouldGetBoardConfig(true)); + + setup(); + + await waitFor(() => { + expect(screen.getByText('Failed to get Board configuration!')).toBeInTheDocument(); + }); + expect(screen.getByText('Please go back to the previous page and check your board info!')).toBeInTheDocument(); + }); + it('should be render form container when got board card success', async () => { + store.dispatch(updateShouldGetBoardConfig(true)); + const mockResponse = { + ignoredTargetFields: [ + { + key: 'description', + name: 'Description', + flag: false, + }, + { + key: 'customfield_10015', + name: 'Start date', + flag: false, + }, + ], + jiraColumns: [ + { + key: 'To Do', + value: { + name: 'TODO', + statuses: ['TODO'], + }, + }, + { + key: 'In Progress', + value: { + name: 'Doing', + statuses: ['DOING'], + }, + }, + ], + targetFields: [ + { + key: 'issuetype', + name: 'Issue Type', + flag: false, + }, + { + key: 'parent', + name: 'Parent', + flag: false, + }, + ], + users: [ + 'heartbeat user', + 'Yunsong Yang', + 'Yufan Wang', + 'Weiran Sun', + 'Xuebing Li', + 'Junbo Dai', + 'Wenting Yan', + 'Xingmeng Tao', + ], + }; server.use( - rest.post(MOCK_BOARD_INFO_URL, (_, res, ctx) => { - return res( - ctx.status(HttpStatusCode.Ok), - ctx.json({ - ignoredTargetFields: [], - jiraColumns: [], - targetFields: [], - users: [], - }), - ); + http.post(MOCK_BOARD_INFO_URL, () => { + return new HttpResponse(JSON.stringify(mockResponse), { + status: HttpStatusCode.Ok, + }); }), ); @@ -328,9 +420,10 @@ describe('MetricsStep', () => { }); it('should show retry button when call get info timeout', async () => { + store.dispatch(updateShouldGetBoardConfig(true)); server.use( - rest.post(MOCK_BOARD_INFO_URL, (_, res) => { - return res.networkError('NETWORK_TIMEOUT'); + http.post(MOCK_BOARD_INFO_URL, () => { + return HttpResponse.error(); }), ); setup(); diff --git a/frontend/__tests__/containers/MetricsStep/MetricsStepPopupTestPartialFailed.test.tsx b/frontend/__tests__/containers/MetricsStep/MetricsStepPopupTestPartialFailed.test.tsx new file mode 100644 index 0000000000..4fd87aebd2 --- /dev/null +++ b/frontend/__tests__/containers/MetricsStep/MetricsStepPopupTestPartialFailed.test.tsx @@ -0,0 +1,60 @@ +import { render, waitFor } from '@testing-library/react'; +import { setupStore } from '../../utils/setupStoreUtil'; +import MetricsStep from '@src/containers/MetricsStep'; +import { Provider } from 'react-redux'; + +import { addNotification } from '@src/context/notification/NotificationSlice'; +import { METRICS_DATA_FAIL_STATUS } from '@src/constants/commons'; + +let store = setupStore(); +const setup = () => + render( + + + , + ); + +jest.mock('@src/context/notification/NotificationSlice', () => ({ + ...jest.requireActual('@src/context/notification/NotificationSlice'), + addNotification: jest.fn().mockReturnValue({ type: 'ADD_NEW_NOTIFICATION' }), +})); + +let boardInfoFailStatus = METRICS_DATA_FAIL_STATUS.NOT_FAILED; + +jest.mock('@src/hooks/useGetBoardInfo', () => ({ + ...jest.requireActual('@src/hooks/useGetBoardInfo'), + + useGetBoardInfoEffect: jest.fn().mockImplementation(() => { + return { + boardInfoFailedStatus: boardInfoFailStatus, + }; + }), +})); + +describe('MetricsStep', () => { + beforeEach(() => { + store = setupStore(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should show 4xx popup when call get partial 4xx error', async () => { + boardInfoFailStatus = METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_4XX; + setup(); + + await waitFor(() => { + expect(addNotification).toHaveBeenCalled(); + }); + }); + + it('should show no cards popup when call get partial no cards error', async () => { + boardInfoFailStatus = METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_NO_CARDS; + setup(); + + await waitFor(() => { + expect(addNotification).toHaveBeenCalled(); + }); + }); +}); diff --git a/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx b/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx index 502300bdf0..1220137b0b 100644 --- a/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx +++ b/frontend/__tests__/containers/MetricsStepper/MetricsStepper.test.tsx @@ -8,9 +8,14 @@ import { PROJECT_NAME_LABEL, SAVE, STEPPER, + VERIFY, TEST_PROJECT_NAME, VELOCITY, COMMON_TIME_FORMAT, + REQUIRED_DATA, + MOCK_PIPELINE_VERIFY_URL, + MOCK_BOARD_URL_FOR_JIRA, + MOCK_REPORT_RESPONSE, } from '../../fixtures'; import { updateCycleTimeSettings, @@ -20,27 +25,22 @@ import { updateDeploymentFrequencySettings, updateTreatFlagCardAsBlock, } from '@src/context/Metrics/metricsSlice'; -import { - updateBoardVerifyState, - updateMetrics, - updatePipelineToolVerifyState, - updateSourceControlVerifyState, -} from '@src/context/config/configSlice'; -import { act, render, screen, waitFor } from '@testing-library/react'; -import { ASSIGNEE_FILTER_TYPES } from '@src/constants/resources'; +import { ASSIGNEE_FILTER_TYPES, DEFAULT_MESSAGE } from '@src/constants/resources'; +import { updateDateRange, updateMetrics } from '@src/context/config/configSlice'; +import { act, render, screen, waitFor, within } from '@testing-library/react'; import MetricsStepper from '@src/containers/MetricsStepper'; import { setupStore } from '../../utils/setupStoreUtil'; import userEvent from '@testing-library/user-event'; import { exportToJsonFile } from '@src/utils/util'; import { navigateMock } from '../../setupTests'; +import { HttpResponse, http } from 'msw'; import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; import { HttpStatusCode } from 'axios'; -import { rest } from 'msw'; import dayjs from 'dayjs'; import React from 'react'; -const START_DATE_LABEL = 'From *'; +const START_DATE_LABEL = 'From'; const TODAY = dayjs(); const INPUT_DATE_VALUE = TODAY.format('MM/DD/YYYY'); const YES = 'Yes'; @@ -57,6 +57,11 @@ const mockValidationCheckContext = { getDuplicatedPipeLineIds: jest.fn().mockReturnValue([]), }; +const mockDateRange = { + startDate: '2024-02-18T00:00:00.000+08:00', + endDate: '2024-02-28T23:59:59.999+08:00', +}; + jest.mock('@src/hooks/useMetricsStepValidationCheckContext', () => ({ useMetricsStepValidationCheckContext: () => mockValidationCheckContext, })); @@ -89,32 +94,75 @@ jest.mock('@src/utils/util', () => ({ })); jest.mock('@src/hooks/useGenerateReportEffect', () => ({ + ...jest.requireActual('@src/hooks/useGenerateReportEffect'), useGenerateReportEffect: jest.fn().mockReturnValue({ startToRequestData: jest.fn(), - startToRequestDoraData: jest.fn(), stopPollingReports: jest.fn(), - isServerError: false, - errorMessage: '', + closeReportInfosErrorStatus: jest.fn(), + closeBoardMetricsError: jest.fn(), + closePipelineMetricsError: jest.fn(), + closeSourceControlMetricsError: jest.fn(), + reportInfos: [ + { + id: mockDateRange.startDate, + timeout4Board: { message: DEFAULT_MESSAGE, shouldShow: true }, + timeout4Dora: { message: DEFAULT_MESSAGE, shouldShow: true }, + timeout4Report: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Board: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Dora: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Report: { message: DEFAULT_MESSAGE, shouldShow: true }, + shouldShowBoardMetricsError: true, + shouldShowPipelineMetricsError: true, + shouldShowSourceControlMetricsError: true, + reportData: { ...MOCK_REPORT_RESPONSE, exportValidityTime: 30 }, + }, + ], }), })); -const server = setupServer(rest.post(MOCK_REPORT_URL, (_, res, ctx) => res(ctx.status(HttpStatusCode.Ok)))); +const server = setupServer( + http.post(MOCK_REPORT_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.Ok, + }); + }), + http.post(MOCK_BOARD_URL_FOR_JIRA, () => { + return new HttpResponse(null, { + status: HttpStatusCode.NoContent, + }); + }), +); -const mockLocation = { reload: jest.fn() }; +const mockLocation = { ...window.location, reload: jest.fn() }; Object.defineProperty(window, 'location', { value: mockLocation }); let store = setupStore(); -const fillConfigPageData = async () => { +const fillAndVerifyConfigPageData = async () => { const projectNameInput = await screen.findByRole('textbox', { name: PROJECT_NAME_LABEL }); await userEvent.type(projectNameInput, TEST_PROJECT_NAME); const startDateInput = (await screen.findByRole('textbox', { name: START_DATE_LABEL })) as HTMLInputElement; await userEvent.type(startDateInput, INPUT_DATE_VALUE); - - act(() => { - store.dispatch(updateMetrics([VELOCITY])); - store.dispatch(updateBoardVerifyState(true)); - store.dispatch(updatePipelineToolVerifyState(true)); - store.dispatch(updateSourceControlVerifyState(true)); + await userEvent.click(screen.getByRole('combobox', { name: REQUIRED_DATA })); + const requireMetricsSelection = within(screen.getByRole('listbox')); + await userEvent.click(requireMetricsSelection.getByRole('option', { name: VELOCITY })); + await userEvent.keyboard('{Escape}'); + const boardConfigModule = screen.getByLabelText('Board Config'); + + expect(boardConfigModule).toBeInTheDocument(); + + const boardIdInput = within(boardConfigModule).getByRole('textbox', { name: 'Board Id' }); + await userEvent.type(boardIdInput, '2'); + const emailInput = within(boardConfigModule).getByRole('textbox', { name: 'Email' }); + await userEvent.type(emailInput, 'user@test.com'); + const siteInput = within(boardConfigModule).getByRole('textbox', { name: 'Site' }); + await userEvent.type(siteInput, 'dorametrics'); + const tokenInput = within(boardConfigModule).getByLabelText('Token *'); + await userEvent.type(tokenInput, 'mockJiraToken'); + const verifyBoardButton = within(boardConfigModule).getByText(VERIFY); + await userEvent.click(verifyBoardButton); + + await waitFor(() => { + expect(screen.getByText(NEXT)).toBeEnabled(); }); }; @@ -125,37 +173,39 @@ const fillMetricsData = () => { }; const fillMetricsPageDate = async () => { - act(() => { + await act(async () => { store.dispatch(saveTargetFields([{ name: 'mockClassification', key: 'mockClassification', flag: true }])); store.dispatch(saveUsers(['mockUsers'])); - store.dispatch(saveDoneColumn(['Done', 'Canceled'])), - store.dispatch( - updateCycleTimeSettings([ - { column: 'Testing', status: 'testing', value: 'Done' }, - { column: 'Testing', status: 'test', value: 'Done' }, - ]), - ); - store.dispatch(updateTreatFlagCardAsBlock(false)), - store.dispatch( - updateDeploymentFrequencySettings({ updateId: 0, label: 'organization', value: 'mock new organization' }), - ); + store.dispatch(saveDoneColumn(['Done', 'Canceled'])); + store.dispatch( + updateCycleTimeSettings([ + { column: 'Testing', status: 'testing', value: 'Done' }, + { column: 'Testing', status: 'test', value: 'Done' }, + ]), + ); + store.dispatch(updateTreatFlagCardAsBlock(false)); + store.dispatch( + updateDeploymentFrequencySettings({ updateId: 0, label: 'organization', value: 'mock new organization' }), + ); store.dispatch( updateDeploymentFrequencySettings({ updateId: 0, label: 'pipelineName', value: 'mock new pipelineName' }), ); store.dispatch(updateDeploymentFrequencySettings({ updateId: 0, label: 'step', value: 'mock new step' })); + store.dispatch(updateDateRange([mockDateRange])); }); }; -describe('MetricsStepper', () => { - beforeAll(() => server.listen()); - afterAll(() => server.close()); - beforeEach(() => { - store = setupStore(); - }); - afterEach(() => { - navigateMock.mockClear(); - }); +beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); +afterAll(() => server.close()); +beforeEach(() => { + store = setupStore(); +}); +afterEach(() => { + server.resetHandlers(); + navigateMock.mockClear(); +}); +describe('MetricsStepper', () => { const setup = () => render( @@ -221,34 +271,44 @@ describe('MetricsStepper', () => { }); it('should enable next when every selected component is show and verified', async () => { - setup(); - await fillConfigPageData(); - - expect(screen.getByText(NEXT)).toBeEnabled(); + server.use( + http.post(MOCK_PIPELINE_VERIFY_URL, () => { + return new HttpResponse(null, { + status: HttpStatusCode.NoContent, + }); + }), + ); + await act(async () => { + setup(); + }); + await fillAndVerifyConfigPageData(); }); it('should disable next when board component is exist but not verified successfully', async () => { setup(); act(() => { store.dispatch(updateMetrics([VELOCITY])); - store.dispatch(updateBoardVerifyState(false)); }); expect(screen.getByText(NEXT)).toBeDisabled(); }); it('should go metrics page when click next button given next button enabled', async () => { - setup(); + await act(async () => { + setup(); + }); - await fillConfigPageData(); + await fillAndVerifyConfigPageData(); await userEvent.click(screen.getByText(NEXT)); - expect(screen.getByText(METRICS)).toHaveStyle(`color:${stepperColor}`); + expect(screen.getByText(METRICS)).toHaveClass('Mui-active'); }); it('should show metrics export step when click next button given export step', async () => { - setup(); - await fillConfigPageData(); + await act(async () => { + setup(); + }); + await fillAndVerifyConfigPageData(); await userEvent.click(screen.getByText(NEXT)); await fillMetricsPageDate(); waitFor(() => { @@ -274,6 +334,7 @@ describe('MetricsStepper', () => { startDate: null, }, ], + sortType: 'DEFAULT', metrics: [], pipelineTool: undefined, projectName: '', @@ -297,6 +358,7 @@ describe('MetricsStepper', () => { startDate: null, }, ], + sortType: 'DEFAULT', metrics: ['Velocity'], pipelineTool: undefined, projectName: '', @@ -316,7 +378,6 @@ describe('MetricsStepper', () => { const expectedJson = { advancedSettings: null, assigneeFilter: ASSIGNEE_FILTER_TYPES.LAST_ASSIGNEE, - board: { boardId: '', email: '', site: '', token: '', type: 'Jira' }, calendarType: 'Regular Calendar(Weekend Considered)', dateRange: [ { @@ -325,8 +386,16 @@ describe('MetricsStepper', () => { }, ], metrics: ['Velocity'], - pipelineCrews: undefined, + board: { + type: 'Jira', + boardId: '', + email: '', + site: '', + token: '', + }, pipelineTool: undefined, + sortType: 'DEFAULT', + pipelineCrews: undefined, projectName: 'test project Name', sourceControl: undefined, classification: undefined, @@ -337,13 +406,23 @@ describe('MetricsStepper', () => { leadTime: undefined, reworkTimesSettings: DEFAULT_REWORK_SETTINGS, }; - setup(); + await act(() => { + setup(); + }); - await fillConfigPageData(); + await fillAndVerifyConfigPageData(); await userEvent.click(screen.getByText(NEXT)); + const saveButton = screen.getByText(SAVE); + expect(screen.getByText(METRICS)).toHaveClass('Mui-active'); + + waitFor(() => { + expect(saveButton).toBeInTheDocument(); + }); await userEvent.click(screen.getByText(SAVE)); - expect(exportToJsonFile).toHaveBeenCalledWith(expectedFileName, expectedJson); + await waitFor(() => { + expect(exportToJsonFile).toHaveBeenCalledWith(expectedFileName, expectedJson); + }); }, 50000); it('should export json file when click save button in report page given all content is empty', async () => { @@ -355,8 +434,8 @@ describe('MetricsStepper', () => { calendarType: 'Regular Calendar(Weekend Considered)', dateRange: [ { - endDate: dayjs().endOf('date').add(0, 'day').format(COMMON_TIME_FORMAT), - startDate: dayjs().startOf('date').format(COMMON_TIME_FORMAT), + endDate: mockDateRange.endDate, + startDate: mockDateRange.startDate, }, ], metrics: ['Velocity'], @@ -366,6 +445,7 @@ describe('MetricsStepper', () => { sourceControl: undefined, classification: ['mockClassification'], crews: ['mockUsers'], + sortType: 'DEFAULT', cycleTime: { jiraColumns: [ { @@ -384,13 +464,20 @@ describe('MetricsStepper', () => { }, }; - setup(); - await fillConfigPageData(); + await act(() => { + setup(); + }); + await fillAndVerifyConfigPageData(); await userEvent.click(screen.getByText(NEXT)); + + expect(screen.getByText(METRICS)).toHaveClass('Mui-active'); + await fillMetricsPageDate(); + waitFor(() => { expect(screen.getByText(NEXT)).toBeInTheDocument(); }); + await userEvent.click(screen.getByText(NEXT)); await waitFor(() => { diff --git a/frontend/__tests__/containers/ReportButtonGroup.test.tsx b/frontend/__tests__/containers/ReportButtonGroup.test.tsx index 2e01a9fc53..1c1b89e8b7 100644 --- a/frontend/__tests__/containers/ReportButtonGroup.test.tsx +++ b/frontend/__tests__/containers/ReportButtonGroup.test.tsx @@ -1,31 +1,95 @@ -import { EXPORT_METRIC_DATA, MOCK_REPORT_RESPONSE } from '../fixtures'; +import { EXPORT_BOARD_DATA, EXPORT_METRIC_DATA, EXPORT_PIPELINE_DATA } from '../fixtures'; import { ReportButtonGroup } from '@src/containers/ReportButtonGroup'; +import { DateRangeRequestResult } from '@src/containers/ReportStep'; +import { render, renderHook, screen } from '@testing-library/react'; +import { useExportCsvEffect } from '@src/hooks/useExportCsvEffect'; import { setupStore } from '@test/utils/setupStoreUtil'; -import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Provider } from 'react-redux'; -describe('test', () => { +jest.mock('@src/hooks/useExportCsvEffect', () => ({ + useExportCsvEffect: jest.fn().mockReturnValue({ + fetchExportData: jest.fn(), + isExpired: false, + }), +})); + +describe('ReportButtonGroup', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + const mockHandler = jest.fn(); - const mockData = { - ...MOCK_REPORT_RESPONSE, - exportValidityTime: 30, - reportMetricsError: { - boardMetricsError: { - status: 401, - message: 'Unauthorized', - }, - pipelineMetricsError: { - status: 401, - message: 'Unauthorized', - }, - sourceControlMetricsError: { - status: 401, - message: 'Unauthorized', - }, + const buttonNames = [EXPORT_METRIC_DATA, EXPORT_BOARD_DATA, EXPORT_PIPELINE_DATA]; + const basicMockError = { + boardMetricsError: { + status: 500, + message: 'mockError', }, + pipelineMetricsError: { + status: 500, + message: 'mockError', + }, + sourceControlMetricsError: { + status: 500, + message: 'mockError', + }, + }; + const nullMockError = { + boardMetricsError: null, + pipelineMetricsError: null, + sourceControlMetricsError: null, }; - const setup = () => { + const firstBasicMockDateRangeRequestResult = { + startDate: '2024-01-01T00:00:00.000+08:00', + endDate: '2024-01-14T23:59:59.000+08:00', + overallMetricsCompleted: true, + boardMetricsCompleted: true, + doraMetricsCompleted: true, + reportMetricsError: nullMockError, + }; + const secondBasicMockDateRangeRequestResult = { + startDate: '2024-01-15T00:00:00.000+08:00', + endDate: '2024-01-31T23:59:59.000+08:00', + overallMetricsCompleted: true, + boardMetricsCompleted: true, + doraMetricsCompleted: true, + reportMetricsError: nullMockError, + }; + + const successMockData: DateRangeRequestResult[] = [ + firstBasicMockDateRangeRequestResult, + secondBasicMockDateRangeRequestResult, + ]; + const pendingMockData: DateRangeRequestResult[] = [ + firstBasicMockDateRangeRequestResult, + { + ...secondBasicMockDateRangeRequestResult, + overallMetricsCompleted: false, + boardMetricsCompleted: false, + doraMetricsCompleted: false, + }, + ]; + const partialFailedMockData: DateRangeRequestResult[] = [ + firstBasicMockDateRangeRequestResult, + { + ...secondBasicMockDateRangeRequestResult, + reportMetricsError: basicMockError, + }, + ]; + const allFailedMockData: DateRangeRequestResult[] = [ + { + ...firstBasicMockDateRangeRequestResult, + reportMetricsError: basicMockError, + }, + { + ...secondBasicMockDateRangeRequestResult, + reportMetricsError: basicMockError, + }, + ]; + + const setup = (dateRangeRequestResults: DateRangeRequestResult[]) => { const store = setupStore(); render( @@ -36,18 +100,107 @@ describe('test', () => { isShowExportPipelineButton={true} handleBack={mockHandler} handleSave={mockHandler} - reportData={mockData} - startDate={''} - endDate={''} - csvTimeStamp={1239013} + csvTimeStamp={1715250961572} + dateRangeRequestResults={dateRangeRequestResults} /> , ); }; - it('test', () => { - setup(); + it('should all buttons be clickable given the request successfully finishes', () => { + setup(successMockData); + + expect(screen.getByRole('button', { name: EXPORT_METRIC_DATA })).not.toBeDisabled(); + expect(screen.getByRole('button', { name: EXPORT_BOARD_DATA })).not.toBeDisabled(); + expect(screen.getByRole('button', { name: EXPORT_PIPELINE_DATA })).not.toBeDisabled(); + }); + + it.each(buttonNames)( + 'should be able to export all the overall metrics CSV files when clicking the %s button given the request successfully finishes', + async (buttonName) => { + setup(successMockData); + const exportButton = screen.getByRole('button', { name: buttonName }); + expect(exportButton).not.toBeDisabled(); + + await userEvent.click(exportButton); + + expect(screen.getByText('2024/01/01 - 2024/01/14')).toBeInTheDocument(); + expect(screen.getByText('2024/01/15 - 2024/01/31')).toBeInTheDocument(); + expect(screen.getAllByRole('checkbox')[0]).not.toBeDisabled(); + expect(screen.getAllByRole('checkbox')[1]).not.toBeDisabled(); + }, + ); + + it('should export data buttons be not clickable given the CSV file for one of the dataRanges is still in the process of generating.', () => { + setup(pendingMockData); + + expect(screen.getByRole('button', { name: EXPORT_METRIC_DATA })).toBeDisabled(); + expect(screen.getByRole('button', { name: EXPORT_BOARD_DATA })).toBeDisabled(); + expect(screen.getByRole('button', { name: EXPORT_PIPELINE_DATA })).toBeDisabled(); + }); + + it.each(buttonNames)( + 'should not be able to export the CSV file when clicking the %s button given an error occurs for the dataRanges', + async (buttonName) => { + setup(partialFailedMockData); + const exportButton = screen.getByRole('button', { name: buttonName }); + expect(exportButton).not.toBeDisabled(); + + await userEvent.click(exportButton); + + expect(screen.getByText('2024/01/15 - 2024/01/31')).toBeInTheDocument(); + const checkbox = screen.getAllByRole('checkbox')[1]; + expect(checkbox).toBeDisabled(); + }, + ); + + it.each(buttonNames)( + 'should not open download dialog when clicking the %s button given only setting one dataRange', + async (buttonName) => { + setup([firstBasicMockDateRangeRequestResult]); + const exportButton = screen.getByRole('button', { name: buttonName }); + expect(exportButton).not.toBeDisabled(); + + await userEvent.click(exportButton); + + expect(screen.queryByText('Select the time period for the exporting data')).not.toBeInTheDocument(); + }, + ); + + it('should close download dialog when clicking the close button given the download dialog is open', async () => { + setup(successMockData); + const exportMetricDataButton = screen.getByRole('button', { name: EXPORT_METRIC_DATA }); + expect(exportMetricDataButton).not.toBeDisabled(); + await userEvent.click(exportMetricDataButton); + + const closeButton = screen.getByTestId('CloseIcon'); + await userEvent.click(closeButton); + + expect(screen.queryByText('Select the time period for the exporting data')).not.toBeInTheDocument(); + }); + + it('should close download dialog and download csv file when clicking the confirm button given the download dialog is open and one of the dataRanges is checked', async () => { + const { result } = renderHook(() => useExportCsvEffect()); + setup(successMockData); + const exportMetricDataButton = screen.getByRole('button', { name: EXPORT_METRIC_DATA }); + expect(exportMetricDataButton).not.toBeDisabled(); + await userEvent.click(exportMetricDataButton); + + const checkbox = screen.getAllByRole('checkbox')[0]; + expect(checkbox).not.toBeDisabled(); + await userEvent.click(checkbox); + + await userEvent.click(screen.getByText('Confirm')); + + expect(screen.queryByText('Select the time period for the exporting data')).not.toBeInTheDocument(); + expect(result.current.fetchExportData).toBeCalledTimes(1); + }); + + it(`should not be able to click the export buttons when all dataRanges encounter errors`, async () => { + setup(allFailedMockData); - expect(screen.queryByRole('button', { name: EXPORT_METRIC_DATA })).toBeDisabled(); + expect(screen.getByRole('button', { name: EXPORT_METRIC_DATA })).toBeDisabled(); + expect(screen.getByRole('button', { name: EXPORT_BOARD_DATA })).toBeDisabled(); + expect(screen.getByRole('button', { name: EXPORT_PIPELINE_DATA })).toBeDisabled(); }); }); diff --git a/frontend/__tests__/containers/ReportStep/DownloadDialog.test.tsx b/frontend/__tests__/containers/ReportStep/DownloadDialog.test.tsx new file mode 100644 index 0000000000..4ef4372bbb --- /dev/null +++ b/frontend/__tests__/containers/ReportStep/DownloadDialog.test.tsx @@ -0,0 +1,107 @@ +import { DateRangeItem, DownloadDialog } from '@src/containers/ReportStep/DownloadDialog'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +describe('DownloadDialog', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + const handleCloseFn = jest.fn(); + const downloadCSVFileFn = jest.fn(); + const mockData = [ + { + startDate: '2024-01-01', + endDate: '2024-01-14', + disabled: false, + }, + { + startDate: '2024-01-15', + endDate: '2024-01-31', + disabled: false, + }, + ]; + + const setup = (dateRangeList: DateRangeItem[]) => { + render( + , + ); + }; + + it('should show all dateRanges in DownloadDialog', () => { + setup(mockData); + + expect(screen.getByText('2024/01/01 - 2024/01/14')).toBeInTheDocument(); + expect(screen.getByText('2024/01/15 - 2024/01/31')).toBeInTheDocument(); + expect(screen.getAllByRole('checkbox')[0]).not.toBeDisabled(); + expect(screen.getAllByRole('checkbox')[1]).not.toBeDisabled(); + expect(screen.getByText('Confirm')).toBeDisabled(); + }); + + it('should not be clickable given there is an error fetching data for this dataRange', () => { + const mockDataWithDisabledDateRange = [ + ...mockData, + { + startDate: '2024-02-01', + endDate: '2024-02-14', + disabled: true, + }, + ]; + setup(mockDataWithDisabledDateRange); + const checkbox = screen.getAllByRole('checkbox')[2]; + + expect(checkbox).toBeDisabled(); + }); + + it('should confirm button be clickable when choosing one dateRange', async () => { + setup(mockData); + const checkbox = screen.getAllByRole('checkbox')[0]; + expect(checkbox).not.toBeDisabled(); + expect(screen.getByText('Confirm')).toBeDisabled(); + + await userEvent.click(checkbox); + + expect(screen.getByText('Confirm')).not.toBeDisabled(); + }); + + it('should close download dialog when clicking the close button', async () => { + setup(mockData); + + const closeButton = screen.getByTestId('CloseIcon'); + await userEvent.click(closeButton); + + expect(handleCloseFn).toBeCalledTimes(1); + }); + + it('should close download dialog and download csv file when clicking the confirm button given that a dataRange is checked', async () => { + setup(mockData); + const checkbox = screen.getAllByRole('checkbox')[0]; + expect(checkbox).not.toBeDisabled(); + await userEvent.click(checkbox); + const confirmButton = screen.getByText('Confirm'); + expect(confirmButton).not.toBeDisabled(); + + await userEvent.click(confirmButton); + + expect(handleCloseFn).toBeCalledTimes(1); + expect(downloadCSVFileFn).toBeCalledTimes(1); + }); + + it('should not check the dataRange when clicking on the checkbox given that the dataRange is already checked', async () => { + setup(mockData); + const checkbox = screen.getAllByRole('checkbox')[0]; + expect(checkbox).not.toBeDisabled(); + await userEvent.click(checkbox); + expect(checkbox).toBeChecked(); + + await userEvent.click(checkbox); + + expect(checkbox).not.toBeChecked(); + }); +}); diff --git a/frontend/__tests__/containers/ReportStep/ReportDetail/dora.test.tsx b/frontend/__tests__/containers/ReportStep/ReportDetail/dora.test.tsx index a847b689c4..bb8e7ebd90 100644 --- a/frontend/__tests__/containers/ReportStep/ReportDetail/dora.test.tsx +++ b/frontend/__tests__/containers/ReportStep/ReportDetail/dora.test.tsx @@ -20,13 +20,13 @@ describe('DoraDetail', () => { describe('Deployment Frequency', () => { it('should show deploymentFrequencyList when deploymentFrequencyList data is existing', () => { (reportMapper as jest.Mock).mockReturnValue({ - deploymentFrequencyList: [{ id: 0, name: 'name1', valuesList: [{ name: 'test1', value: 1 }] }], + deploymentFrequencyList: [{ id: 0, name: 'name1', valueList: [{ value: 1 }] }], }); render(); const deploymentFrequencyTable = screen.getByTestId('Deployment Frequency'); expect(screen.getByText('Deployment Frequency')).toBeInTheDocument(); expect(deploymentFrequencyTable).toBeInTheDocument(); - expect(within(deploymentFrequencyTable).queryAllByTestId('tr').length).toBe(2); + expect(within(deploymentFrequencyTable).queryAllByTestId('tr').length).toBe(1); }); it('should not show deploymentFrequencyList when deploymentFrequencyList data is not existing', () => { @@ -62,13 +62,13 @@ describe('DoraDetail', () => { describe('Dev Change Failure Rate', () => { it('should show devChangeFailureRateList when devChangeFailureRateList data is existing', () => { (reportMapper as jest.Mock).mockReturnValue({ - devChangeFailureRateList: [{ id: 0, name: 'name1', valuesList: [{ name: 'test1', value: 1 }] }], + devChangeFailureRateList: [{ id: 0, name: 'name1', valueList: [{ value: 1 }] }], }); render(); const devChangeFailureRateTable = screen.getByTestId('Dev Change Failure Rate'); expect(screen.getByText('Dev Change Failure Rate')).toBeInTheDocument(); expect(devChangeFailureRateTable).toBeInTheDocument(); - expect(within(devChangeFailureRateTable).queryAllByTestId('tr').length).toBe(2); + expect(within(devChangeFailureRateTable).queryAllByTestId('tr').length).toBe(1); }); it('should not show devChangeFailureRateList when devChangeFailureRateList data is not existing', () => { @@ -83,13 +83,13 @@ describe('DoraDetail', () => { describe('Dev Mean Time To Recovery', () => { it('should show devMeanTimeToRecoveryList when devMeanTimeToRecoveryList data is existing', () => { (reportMapper as jest.Mock).mockReturnValue({ - devMeanTimeToRecoveryList: [{ id: 0, name: 'name1', valuesList: [{ name: 'test1', value: 1 }] }], + devMeanTimeToRecoveryList: [{ id: 0, name: 'name1', valueList: [{ value: 1 }] }], }); render(); const devMeanTimeToRecoveryTable = screen.getByTestId('Dev Mean Time To Recovery'); expect(screen.getByText('Dev Mean Time To Recovery')).toBeInTheDocument(); expect(devMeanTimeToRecoveryTable).toBeInTheDocument(); - expect(within(devMeanTimeToRecoveryTable).queryAllByTestId('tr').length).toBe(2); + expect(within(devMeanTimeToRecoveryTable).queryAllByTestId('tr').length).toBe(1); }); it('should not show devMeanTimeToRecoveryList when devMeanTimeToRecoveryList data is not existing', () => { diff --git a/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx b/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx index 0f8ae03d09..742d349632 100644 --- a/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx +++ b/frontend/__tests__/containers/ReportStep/ReportStep.test.tsx @@ -7,7 +7,6 @@ import { EXPORT_METRIC_DATA, EXPORT_PIPELINE_DATA, LEAD_TIME_FOR_CHANGES, - MOCK_DATE_RANGE, MOCK_JIRA_VERIFY_RESPONSE, MOCK_REPORT_RESPONSE, PREVIOUS, @@ -17,28 +16,30 @@ import { SHOW_MORE, } from '../../fixtures'; import { - TDateRange, + DateRangeList, updateDateRange, updateJiraVerifyResponse, updateMetrics, updatePipelineToolVerifyResponse, } from '@src/context/config/configSlice'; import { addADeploymentFrequencySetting, updateDeploymentFrequencySettings } from '@src/context/Metrics/metricsSlice'; +import { act, render, renderHook, screen, waitFor } from '@testing-library/react'; +import { closeNotification } from '@src/context/notification/NotificationSlice'; import { addNotification } from '@src/context/notification/NotificationSlice'; import { useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect'; -import { render, renderHook, screen, waitFor } from '@testing-library/react'; +import { DEFAULT_MESSAGE, MESSAGE } from '@src/constants/resources'; import { useExportCsvEffect } from '@src/hooks/useExportCsvEffect'; import { backStep } from '@src/context/stepper/StepperSlice'; import { setupStore } from '../../utils/setupStoreUtil'; import userEvent from '@testing-library/user-event'; import ReportStep from '@src/containers/ReportStep'; -import { MESSAGE } from '@src/constants/resources'; import { Provider } from 'react-redux'; -import React from 'react'; +import { ReactNode } from 'react'; jest.mock('@src/context/notification/NotificationSlice', () => ({ ...jest.requireActual('@src/context/notification/NotificationSlice'), addNotification: jest.fn().mockReturnValue({ type: 'ADD_NOTIFICATION' }), + closeNotification: jest.fn().mockReturnValue({ type: 'CLOSE_NOTIFICATION' }), })); jest.mock('@src/context/stepper/StepperSlice', () => ({ @@ -54,12 +55,14 @@ jest.mock('@src/hooks/useExportCsvEffect', () => ({ })); jest.mock('@src/hooks/useGenerateReportEffect', () => ({ + ...jest.requireActual('@src/hooks/useGenerateReportEffect'), useGenerateReportEffect: jest.fn().mockReturnValue({ startToRequestData: jest.fn(), - startToRequestDoraData: jest.fn(), stopPollingReports: jest.fn(), - isServerError: false, - errorMessage: '', + closeReportInfosErrorStatus: jest.fn(), + closeBoardMetricsError: jest.fn(), + closePipelineMetricsError: jest.fn(), + closeSourceControlMetricsError: jest.fn(), }), })); @@ -77,20 +80,60 @@ jest.mock('@src/utils/util', () => ({ formatMillisecondsToHours: jest.fn().mockImplementation((time) => time / 60 / 60 / 1000), })); -let store = null; +let store = setupStore(); + +const emptyValueDateRange = { + startDate: '2024-02-04T00:00:00.000+08:00', + endDate: '2024-02-17T23:59:59.999+08:00', +}; + +const fullValueDateRange = { + startDate: '2024-02-18T00:00:00.000+08:00', + endDate: '2024-02-28T23:59:59.999+08:00', +}; + describe('Report Step', () => { - const { result: reportHook } = renderHook(() => useGenerateReportEffect()); + const { result: reportHook } = renderHook(() => useGenerateReportEffect(), { + wrapper: ({ children }: { children: ReactNode }) => { + return {children}; + }, + }); beforeEach(() => { + store = setupStore(); resetReportHook(); }); const resetReportHook = async () => { - reportHook.current.startToRequestData = jest.fn(); - reportHook.current.stopPollingReports = jest.fn(); - reportHook.current.reportData = { ...MOCK_REPORT_RESPONSE, exportValidityTime: 30 }; + reportHook.current.reportInfos = [ + { + id: fullValueDateRange.startDate, + timeout4Board: { message: DEFAULT_MESSAGE, shouldShow: true }, + timeout4Dora: { message: DEFAULT_MESSAGE, shouldShow: true }, + timeout4Report: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Board: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Dora: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Report: { message: DEFAULT_MESSAGE, shouldShow: true }, + shouldShowBoardMetricsError: true, + shouldShowPipelineMetricsError: true, + shouldShowSourceControlMetricsError: true, + reportData: { ...MOCK_REPORT_RESPONSE, exportValidityTime: 30 }, + }, + { + id: emptyValueDateRange.startDate, + timeout4Board: { message: DEFAULT_MESSAGE, shouldShow: true }, + timeout4Dora: { message: DEFAULT_MESSAGE, shouldShow: true }, + timeout4Report: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Board: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Dora: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Report: { message: DEFAULT_MESSAGE, shouldShow: true }, + shouldShowBoardMetricsError: true, + shouldShowPipelineMetricsError: true, + shouldShowSourceControlMetricsError: true, + reportData: { ...EMPTY_REPORT_VALUES }, + }, + ]; }; const handleSaveMock = jest.fn(); - const setup = (params: string[], dateRange?: TDateRange) => { - store = setupStore(); + const setup = (params: string[], dateRange: DateRangeList = [fullValueDateRange]) => { dateRange && store.dispatch(updateDateRange(dateRange)); store.dispatch( updateJiraVerifyResponse({ @@ -129,13 +172,12 @@ describe('Report Step', () => { ); }; afterEach(() => { - store = null; jest.clearAllMocks(); }); describe('render correctly', () => { it('should render report page', () => { - setup(REQUIRED_DATA_LIST); + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); expect(screen.getByText('Board Metrics')).toBeInTheDocument(); expect(screen.getByText('Velocity')).toBeInTheDocument(); @@ -148,9 +190,7 @@ describe('Report Step', () => { }); it('should render loading page when report data is empty', () => { - reportHook.current.reportData = EMPTY_REPORT_VALUES; - - setup(REQUIRED_DATA_LIST); + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); expect(screen.getAllByTestId('loading-page')).toHaveLength(7); }); @@ -169,7 +209,6 @@ describe('Report Step', () => { it('should render the velocity component with correct props', async () => { setup([REQUIRED_DATA_LIST[1]]); - expect(screen.getByText('20')).toBeInTheDocument(); expect(screen.getByText('14')).toBeInTheDocument(); }); @@ -255,7 +294,7 @@ describe('Report Step', () => { it.each([[REQUIRED_DATA_LIST[2]], [REQUIRED_DATA_LIST[5]]])( 'should render detail page when clicking show more button given metric %s', async (requiredData) => { - setup([requiredData], MOCK_DATE_RANGE); + setup([requiredData]); await userEvent.click(screen.getByText(SHOW_MORE)); @@ -340,8 +379,8 @@ describe('Report Step', () => { expect(result.current.fetchExportData).toBeCalledWith({ csvTimeStamp: 0, dataType: 'pipeline', - endDate: '', - startDate: '', + endDate: '2024-02-28T23:59:59.999+08:00', + startDate: '2024-02-18T00:00:00.000+08:00', }); }); }); @@ -377,8 +416,8 @@ describe('Report Step', () => { expect(result.current.fetchExportData).toBeCalledWith({ csvTimeStamp: 0, dataType: 'board', - endDate: '', - startDate: '', + endDate: '2024-02-28T23:59:59.999+08:00', + startDate: '2024-02-18T00:00:00.000+08:00', }); }); }); @@ -403,8 +442,8 @@ describe('Report Step', () => { expect(result.current.fetchExportData).toBeCalledWith({ csvTimeStamp: 0, dataType: 'metric', - endDate: '', - startDate: '', + endDate: '2024-02-28T23:59:59.999+08:00', + startDate: '2024-02-18T00:00:00.000+08:00', }); }); @@ -419,40 +458,47 @@ describe('Report Step', () => { const error = 'error'; it('should call addNotification when having timeout4Board error', () => { - reportHook.current.timeout4Board = error; + reportHook.current.reportInfos = reportHook.current.reportInfos.slice(1); + reportHook.current.reportInfos[0].timeout4Board = { message: error, shouldShow: true }; - setup(REQUIRED_DATA_LIST); + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); + expect(addNotification).toHaveBeenCalledTimes(1); - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.LOADING_TIMEOUT('Board metrics'), type: 'error', }); }); it('should call addNotification when having timeout4Dora error', () => { - reportHook.current.timeout4Dora = error; + reportHook.current.reportInfos = reportHook.current.reportInfos.slice(1); + reportHook.current.reportInfos[0].timeout4Dora = { message: error, shouldShow: true }; - setup(REQUIRED_DATA_LIST); + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.LOADING_TIMEOUT('DORA metrics'), type: 'error', }); }); it('should call addNotification when having timeout4Report error', () => { - reportHook.current.timeout4Report = error; + reportHook.current.reportInfos = reportHook.current.reportInfos.slice(1); + reportHook.current.reportInfos[0].timeout4Report = { message: error, shouldShow: true }; - setup(REQUIRED_DATA_LIST); + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.LOADING_TIMEOUT('Report'), type: 'error', }); }); it('should call addNotification when having boardMetricsError', () => { - reportHook.current.reportData = { + reportHook.current.reportInfos[0].reportData = { ...MOCK_REPORT_RESPONSE, reportMetricsError: { boardMetricsError: { @@ -466,14 +512,15 @@ describe('Report Step', () => { setup(REQUIRED_DATA_LIST); - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.FAILED_TO_GET_DATA('Board Metrics'), type: 'error', }); }); it('should call addNotification when having pipelineMetricsError', () => { - reportHook.current.reportData = { + reportHook.current.reportInfos[0].reportData = { ...MOCK_REPORT_RESPONSE, reportMetricsError: { boardMetricsError: null, @@ -486,15 +533,16 @@ describe('Report Step', () => { }; setup(REQUIRED_DATA_LIST); - - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledTimes(2); + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.FAILED_TO_GET_DATA('Buildkite'), type: 'error', }); }); it('should call addNotification when having sourceControlMetricsError', () => { - reportHook.current.reportData = { + reportHook.current.reportInfos[0].reportData = { ...MOCK_REPORT_RESPONSE, reportMetricsError: { boardMetricsError: null, @@ -508,48 +556,52 @@ describe('Report Step', () => { setup(REQUIRED_DATA_LIST); - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.FAILED_TO_GET_DATA('GitHub'), type: 'error', }); }); it('should call addNotification when having generalError4Board error', () => { - reportHook.current.generalError4Board = error; + reportHook.current.reportInfos[1].generalError4Board = { message: error, shouldShow: true }; - setup(REQUIRED_DATA_LIST); + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.FAILED_TO_REQUEST, type: 'error', }); }); it('should call addNotification when having generalError4Dora error', () => { - reportHook.current.generalError4Dora = error; + reportHook.current.reportInfos[1].generalError4Dora = { message: error, shouldShow: true }; - setup(REQUIRED_DATA_LIST); + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.FAILED_TO_REQUEST, type: 'error', }); }); it('should call addNotification when having generalError4Report error', () => { - reportHook.current.generalError4Report = error; + reportHook.current.reportInfos[1].generalError4Report = { message: error, shouldShow: true }; - setup(REQUIRED_DATA_LIST); + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); - expect(addNotification).toBeCalledWith({ + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), message: MESSAGE.FAILED_TO_REQUEST, type: 'error', }); }); it('should retry startToRequestData when click the retry button in Board Metrics', async () => { - reportHook.current.generalError4Report = error; - setup(REQUIRED_DATA_LIST); + reportHook.current.reportInfos[1].generalError4Report = { message: error, shouldShow: true }; + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); await userEvent.click(screen.getAllByText(RETRY)[0]); @@ -559,8 +611,8 @@ describe('Report Step', () => { }); it('should retry startToRequestData when click the retry button in Dora Metrics', async () => { - reportHook.current.generalError4Report = error; - setup(REQUIRED_DATA_LIST); + reportHook.current.reportInfos[1].generalError4Report = { message: error, shouldShow: true }; + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); await userEvent.click(screen.getAllByText(RETRY)[1]); @@ -568,5 +620,69 @@ describe('Report Step', () => { expect(useGenerateReportEffect().startToRequestData).toHaveBeenCalledTimes(2); }); }); + + it('should not show notification when sending request', async () => { + reportHook.current.hasPollingStarted = true; + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); + + expect(addNotification).toHaveBeenCalledTimes(0); + }); + + it('should not show notification given the requests all failed', () => { + reportHook.current.hasPollingStarted = false; + reportHook.current.reportInfos[0].reportData = undefined; + reportHook.current.reportInfos[1].reportData = undefined; + setup(REQUIRED_DATA_LIST, [fullValueDateRange]); + expect(addNotification).toHaveBeenCalledTimes(0); + }); + + it('should show "file will expire ..." notification given the request is successful', () => { + reportHook.current.hasPollingStarted = false; + setup(REQUIRED_DATA_LIST, [fullValueDateRange]); + expect(addNotification).toHaveBeenCalledWith({ + message: MESSAGE.EXPIRE_INFORMATION(30), + }); + }); + + it('should not show notifications given shown once', () => { + reportHook.current.reportInfos = reportHook.current.reportInfos.slice(1); + reportHook.current.reportInfos[0].generalError4Report = { shouldShow: false, message: 'error' }; + reportHook.current.reportInfos[0].generalError4Dora = { shouldShow: false, message: 'error' }; + reportHook.current.reportInfos[0].generalError4Board = { shouldShow: false, message: 'error' }; + reportHook.current.reportInfos[0].timeout4Dora = { shouldShow: false, message: 'error' }; + reportHook.current.reportInfos[0].timeout4Board = { shouldShow: false, message: 'error' }; + reportHook.current.reportInfos[0].timeout4Report = { shouldShow: false, message: 'error' }; + reportHook.current.reportInfos[0].reportData!.reportMetricsError = { + boardMetricsError: { status: 400, message: 'error' }, + pipelineMetricsError: { status: 400, message: 'error' }, + sourceControlMetricsError: { status: 400, message: 'error' }, + }; + reportHook.current.reportInfos[0].shouldShowBoardMetricsError = false; + reportHook.current.reportInfos[0].shouldShowPipelineMetricsError = false; + reportHook.current.reportInfos[0].shouldShowSourceControlMetricsError = false; + setup(REQUIRED_DATA_LIST, [emptyValueDateRange]); + expect(addNotification).toHaveBeenCalledTimes(0); + }); + + it('should close error notification when change dateRange', async () => { + reportHook.current.reportInfos[1].timeout4Board = { shouldShow: true, message: 'error' }; + const { getByTestId, getByText } = setup(REQUIRED_DATA_LIST, [fullValueDateRange, emptyValueDateRange]); + const expandMoreIcon = getByTestId('ExpandMoreIcon'); + await act(async () => { + await userEvent.click(expandMoreIcon); + }); + const secondDateRange = await getByText(/2024\/02\/04/); + + await userEvent.click(secondDateRange); + await userEvent.click(expandMoreIcon); + const firstDateRange = screen.getByText(/2024\/02\/18/); + await userEvent.click(firstDateRange); + expect(addNotification).toHaveBeenCalledWith({ + id: expect.any(String), + message: MESSAGE.LOADING_TIMEOUT('Board metrics'), + type: 'error', + }); + expect(closeNotification).toHaveBeenCalledTimes(1); + }); }); }); diff --git a/frontend/__tests__/context/boardSlice.test.ts b/frontend/__tests__/context/boardSlice.test.ts index 636268bd06..916ccb5cfe 100644 --- a/frontend/__tests__/context/boardSlice.test.ts +++ b/frontend/__tests__/context/boardSlice.test.ts @@ -1,24 +1,8 @@ -import boardReducer, { - updateBoard, - updateBoardVerifyState, - updateJiraVerifyResponse, -} from '@src/context/config/configSlice'; +import boardReducer, { updateBoard, updateJiraVerifyResponse } from '@src/context/config/configSlice'; import { MOCK_JIRA_VERIFY_RESPONSE } from '../fixtures'; import initialConfigState from '../initialConfigState'; describe('board reducer', () => { - it('should return false when handle initial state', () => { - const result = boardReducer(undefined, { type: 'unknown' }); - - expect(result.board.isVerified).toEqual(false); - }); - - it('should return true when handle changeBoardVerifyState given isBoardVerified is true', () => { - const result = boardReducer(initialConfigState, updateBoardVerifyState(true)); - - expect(result.board.isVerified).toEqual(true); - }); - it('should update board fields when change board fields input', () => { const board = boardReducer(initialConfigState, updateBoard({ boardId: '1' })); diff --git a/frontend/__tests__/context/configSlice.test.ts b/frontend/__tests__/context/configSlice.test.ts index a301863cb5..b11f0150a8 100644 --- a/frontend/__tests__/context/configSlice.test.ts +++ b/frontend/__tests__/context/configSlice.test.ts @@ -3,11 +3,13 @@ import configReducer, { selectSteps, updateCalendarType, updateDateRange, + updateDateRangeSortType, updateMetrics, updateProjectCreatedState, updateProjectName, } from '@src/context/config/configSlice'; import { CHINA_CALENDAR, CONFIG_PAGE_VERIFY_IMPORT_ERROR_MESSAGE, REGULAR_CALENDAR, VELOCITY } from '../fixtures'; +import { SortType } from '@src/containers/ConfigStep/DateRangePicker/types'; import { setupStore } from '@test/utils/setupStoreUtil'; import initialConfigState from '../initialConfigState'; @@ -50,6 +52,13 @@ describe('config reducer', () => { expect(config.dateRange[0].endDate).toEqual(''); }); + it('should update date range sort type with given sort type', () => { + const newSortType = SortType.DEFAULT; + const config = configReducer(initialConfigState, updateDateRangeSortType(newSortType)).basic; + + expect(config.sortType).toEqual(newSortType); + }); + it('should isProjectCreated is false when import file', () => { const config = configReducer(initialConfigState, updateProjectCreatedState(false)); @@ -165,6 +174,7 @@ describe('config reducer', () => { payload: { ...MockBasicState, dateRange: mockDateRange, + sortType: SortType.DEFAULT, }, }; @@ -194,4 +204,53 @@ describe('config reducer', () => { expect(config.basic.dateRange[0]).toEqual(mockDateRange); }); + + it('should set warning message when imported sortType is invalid', () => { + const initialState = { + ...initialConfigState, + isProjectCreated: false, + }; + const mockDateRange = [ + { + startDate: '2024-01-15T00:00:00.000+08:00', + endDate: '2024-01-16T00:00:00.000+08:00', + }, + { + startDate: '2024-01-17T00:00:00.000+08:00', + endDate: '2024-01-18T00:00:00.000+08:00', + }, + ]; + const action = { + type: 'config/updateBasicConfigState', + payload: { ...MockBasicState, dateRange: mockDateRange, sortType: 'test' }, + }; + const config = configReducer(initialState, action); + expect(config.warningMessage).toBe(CONFIG_PAGE_VERIFY_IMPORT_ERROR_MESSAGE); + }); + + it('should not set warning message when imported date ranges has length 1 and importedSortType is null', () => { + const initialState = { + ...initialConfigState, + isProjectCreated: false, + }; + const action = { + type: 'config/updateBasicConfigState', + payload: { ...MockBasicState, dateRange: [MockBasicState.dateRange[0]], sortType: null }, + }; + const config = configReducer(initialState, action); + expect(config.warningMessage).toBe(null); + }); + + it('should set warning message when imported date ranges has length 1 and importedSortType is valid', () => { + const initialState = { + ...initialConfigState, + isProjectCreated: false, + }; + const action = { + type: 'config/updateBasicConfigState', + payload: { ...MockBasicState, dateRange: [MockBasicState.dateRange[0]], sortType: SortType.DEFAULT }, + }; + const config = configReducer(initialState, action); + expect(config.warningMessage).toBe(null); + }); }); diff --git a/frontend/__tests__/context/metricsSlice.test.ts b/frontend/__tests__/context/metricsSlice.test.ts index 8f9d4a80f9..443f87d511 100644 --- a/frontend/__tests__/context/metricsSlice.test.ts +++ b/frontend/__tests__/context/metricsSlice.test.ts @@ -20,6 +20,7 @@ import saveMetricsSettingReducer, { selectReworkTimesSettings, selectShouldGetBoardConfig, selectShouldGetPipelineConfig, + selectShouldRetryPipelineConfig, selectStepWarningMessage, selectTreatFlagCardAsBlock, setCycleTimeSettingsType, @@ -34,6 +35,7 @@ import saveMetricsSettingReducer, { updateReworkTimesSettings, updateShouldGetBoardConfig, updateShouldGetPipelineConfig, + updateShouldRetryPipelineConfig, updateTreatFlagCardAsBlock, } from '@src/context/Metrics/metricsSlice'; import { @@ -49,6 +51,7 @@ import { store } from '@src/store'; const initState = { shouldGetBoardConfig: true, shouldGetPipeLineConfig: true, + shouldRetryPipelineConfig: false, jiraColumns: [], targetFields: [], users: [], @@ -61,6 +64,7 @@ const initState = { classification: [], treatFlagCardAsBlock: true, assigneeFilter: ASSIGNEE_FILTER_TYPES.LAST_ASSIGNEE, + displayFlagCardDropWarning: true, importedData: { importedCrews: [], importedAssigneeFilter: ASSIGNEE_FILTER_TYPES.LAST_ASSIGNEE, @@ -304,6 +308,32 @@ describe('saveMetricsSetting reducer', () => { expect(savedMetricsSetting.doneColumn).toEqual(['DONE']); }); + it('should not be able to show conflict warning and check the flag card as block', () => { + const mockUpdateMetricsStateArguments = { + ...mockJiraResponse, + isProjectCreated: true, + }; + const savedMetricsSetting = saveMetricsSettingReducer( + { + ...initState, + cycleTimeSettingsType: CYCLE_TIME_SETTINGS_TYPES.BY_STATUS, + importedData: { + ...initState.importedData, + importedCrews: ['User B', 'User C'], + importedClassification: ['issuetype'], + importedCycleTime: { + importedCycleTimeSettings: [{ DOING: 'Doing' }, { TESTING: 'Testing' }, { DONE: 'Done' }], + importedTreatFlagCardAsBlock: false, + }, + importedDoneStatus: ['DONE'], + }, + }, + updateMetricsState(mockUpdateMetricsStateArguments), + ); + expect(savedMetricsSetting.displayFlagCardDropWarning).toEqual(false); + expect(savedMetricsSetting.treatFlagCardAsBlock).toEqual(true); + }); + it('should update metricsState given cycleTimeSettingsType is by status', () => { const mockUpdateMetricsStateArguments = { ...mockJiraResponse, @@ -336,6 +366,7 @@ describe('saveMetricsSetting reducer', () => { { column: 'Testing', status: 'TESTING', value: 'Testing' }, ]); expect(savedMetricsSetting.doneColumn).toEqual(['DONE']); + expect(savedMetricsSetting.displayFlagCardDropWarning).toEqual(true); }); it('should update metricsState given its value changed given isProjectCreated is false and selectedDoneColumns and cycleTimeSettingsType is byStatus', () => { @@ -562,6 +593,17 @@ describe('saveMetricsSetting reducer', () => { expect(savedPipelineCrews.pipelineCrews).toBe(crews); }); + it('should return empty array given crews is undefined', () => { + const savedPipelineCrews = saveMetricsSettingReducer(initState, savePipelineCrews(undefined)); + + expect(savedPipelineCrews.pipelineCrews).toEqual([]); + }); + + it('should update ShouldRetryPipelineConfig', async () => { + store.dispatch(updateShouldRetryPipelineConfig(true)); + expect(selectShouldRetryPipelineConfig(store.getState())).toEqual(true); + }); + it('should set cycle time setting type', () => { const setCycleTimeSettingsTypeResult = saveMetricsSettingReducer( initState, @@ -614,7 +656,7 @@ describe('saveMetricsSetting reducer', () => { ...initState, deploymentFrequencySettings: [{ id: 1, organization: '', pipelineName: '', step: '', branches: [] }], }, - updateDeploymentFrequencySettings({ updateId: 1, label: 'Steps', value: 'value' }), + updateDeploymentFrequencySettings({ updateId: 1, label: 'Step', value: 'value' }), ); expect(updateDeploymentFrequencySettingsResult.deploymentFrequencySettings[0].step).toEqual('value'); @@ -658,7 +700,7 @@ describe('saveMetricsSetting reducer', () => { id: 2, organization: 'mockOrganization2', pipelineName: 'mockPipelineName3', - step: 'mockStep3', + step: '', branches: [], }, ], @@ -683,8 +725,7 @@ describe('saveMetricsSetting reducer', () => { step: 'mockStep1', branches: [], }, - { id: 1, organization: 'mockOrganization1', pipelineName: '', step: 'mockStep2', branches: [] }, - { id: 2, organization: '', pipelineName: '', step: 'mockStep3', branches: [] }, + { id: 1, organization: 'mockOrganization1', pipelineName: '', step: '', branches: [] }, ], leadTimeForChanges: [ { id: 0, organization: 'mockOrganization1', pipelineName: 'mockPipelineName1', step: '', branches: [] }, @@ -717,12 +758,13 @@ describe('saveMetricsSetting reducer', () => { pipelineName: 'mockPipelineName2', step: 'mockStep2', branches: [], + isStepEmptyString: false, }, { id: 2, organization: 'mockOrganization2', pipelineName: 'mockPipelineName3', - step: 'mockStep3', + step: '', branches: [], }, ], @@ -747,8 +789,13 @@ describe('saveMetricsSetting reducer', () => { step: 'mockStep1', branches: [], }, - { id: 1, organization: 'mockOrganization1', pipelineName: '', step: 'mockStep2', branches: [] }, - { id: 2, organization: '', pipelineName: '', step: 'mockStep3', branches: [] }, + { + id: 1, + organization: 'mockOrganization1', + pipelineName: '', + step: '', + branches: [], + }, ], leadTimeForChanges: [ { id: 0, organization: 'mockOrganization1', pipelineName: 'mockPipelineName1', step: '', branches: [] }, @@ -769,7 +816,9 @@ describe('saveMetricsSetting reducer', () => { }, pipelineCrews: [], expectSetting: { - deploymentFrequencySettings: [{ id: 0, organization: '', pipelineName: '', step: '', branches: [] }], + deploymentFrequencySettings: [ + { id: 0, organization: '', pipelineName: '', step: '', branches: [], isStepEmptyString: false }, + ], leadTimeForChanges: [{ id: 0, organization: '', pipelineName: '', step: '', branches: [] }], deploymentWarningMessage: [], leadTimeWarningMessage: [], @@ -788,7 +837,9 @@ describe('saveMetricsSetting reducer', () => { }, pipelineCrews: [], expectSetting: { - deploymentFrequencySettings: [{ id: 1, organization: '', pipelineName: '', step: '', branches: [] }], + deploymentFrequencySettings: [ + { id: 1, organization: '', pipelineName: '', step: '', branches: [], isStepEmptyString: false }, + ], leadTimeForChanges: [{ id: 0, organization: '', pipelineName: '', step: '', branches: [] }], deploymentWarningMessage: [], leadTimeWarningMessage: [], @@ -851,7 +902,7 @@ describe('saveMetricsSetting reducer', () => { id: 0, organization: 'mockOrganization1', pipelineName: 'mockPipelineName1', - step: '', + step: 'mockStep1', branches: ['branch1'], }, { id: 1, organization: 'mockOrganization1', pipelineName: 'mockPipelineName2', step: '', branches: [] }, @@ -893,10 +944,22 @@ describe('saveMetricsSetting reducer', () => { step: 'mockStep1', branches: [], }, - { id: 1, organization: 'mockOrganization1', pipelineName: 'mockPipelineName2', step: '', branches: [] }, + { + id: 1, + organization: 'mockOrganization1', + pipelineName: 'mockPipelineName2', + step: '', + branches: [], + isStepEmptyString: true, + }, ], expectedWarning: [ - { id: 0, organization: null, pipelineName: null, step: null }, + { + id: 0, + organization: null, + pipelineName: null, + step: null, + }, { id: 1, organization: null, pipelineName: null, step: null }, ], }, @@ -911,7 +974,7 @@ describe('saveMetricsSetting reducer', () => { id: 0, organization: 'mockOrganization1', pipelineName: 'mockPipelineName1', - step: '', + step: 'mockStep1', branches: ['branch1'], }, { @@ -919,7 +982,8 @@ describe('saveMetricsSetting reducer', () => { organization: 'mockOrganization1', pipelineName: 'mockPipelineName2', step: '', - branches: ['branch1'], + branches: [], + isStepEmptyString: true, }, ], expectedWarning: [ @@ -943,13 +1007,25 @@ describe('saveMetricsSetting reducer', () => { id: 0, organization: 'mockOrganization1', pipelineName: 'mockPipelineName1', - step: '', + step: 'mockStep1', branches: ['branch1'], }, - { id: 1, organization: 'mockOrganization1', pipelineName: 'mockPipelineName2', step: '', branches: [] }, + { + id: 1, + organization: 'mockOrganization1', + pipelineName: 'mockPipelineName2', + step: '', + branches: [], + isStepEmptyString: true, + }, ], expectedWarning: [ - { id: 0, organization: null, pipelineName: null, step: null }, + { + id: 0, + organization: null, + pipelineName: null, + step: null, + }, { id: 1, organization: null, @@ -969,10 +1045,17 @@ describe('saveMetricsSetting reducer', () => { id: 0, organization: 'mockOrganization1', pipelineName: 'mockPipelineName1', - step: '', + step: 'mockStep1', branches: ['branch1'], }, - { id: 1, organization: 'mockOrganization1', pipelineName: 'mockPipelineName2', step: '', branches: [] }, + { + id: 1, + organization: 'mockOrganization1', + pipelineName: 'mockPipelineName2', + step: '', + branches: [], + isStepEmptyString: true, + }, ], expectedWarning: [ { id: 0, organization: null, pipelineName: null, step: null }, @@ -1422,6 +1505,7 @@ describe('saveMetricsSetting reducer', () => { orgName: 'mockOrganization1', repository: 'mockRepository1', steps: ['mock step 1', 'mock step 2'], + isStepEmptyString: false, }, ]; const mockSteps = ['mockStep']; @@ -1460,7 +1544,7 @@ describe('saveMetricsSetting reducer', () => { it('should return status of initial state', () => { expect(selectShouldGetBoardConfig(store.getState())).toBeFalsy(); expect(selectShouldGetPipelineConfig(store.getState())).toBeFalsy(); - expect(selectDeploymentFrequencySettings(store.getState()).length).toBeGreaterThan(1); + expect(selectDeploymentFrequencySettings(store.getState()).length).toBeGreaterThan(0); expect(selectReworkTimesSettings(store.getState())).toStrictEqual({ excludeStates: [], reworkState: null }); expect(selectCycleTimeSettings(store.getState())).toEqual([]); expect(selectMetricsContent(store.getState()).assigneeFilter).toEqual('lastAssignee'); @@ -1479,7 +1563,7 @@ describe('saveMetricsSetting reducer', () => { }); it('should return step warning message given its id and type', () => { - expect(selectStepWarningMessage(store.getState(), 0)).toBeNull(); + expect(selectStepWarningMessage(store.getState(), 0)).toEqual(STEP_WARNING_MESSAGE); expect(selectStepWarningMessage(store.getState(), 1)).toEqual(STEP_WARNING_MESSAGE); }); }); diff --git a/frontend/__tests__/context/pipelineToolSlice.test.ts b/frontend/__tests__/context/pipelineToolSlice.test.ts index 691ea6963e..0044d28786 100644 --- a/frontend/__tests__/context/pipelineToolSlice.test.ts +++ b/frontend/__tests__/context/pipelineToolSlice.test.ts @@ -7,7 +7,6 @@ import { updatePipelineTool, updatePipelineToolVerifyResponse, updatePipelineToolVerifyResponseSteps, - updatePipelineToolVerifyState, } from '@src/context/config/configSlice'; import { MOCK_BUILD_KITE_VERIFY_RESPONSE, PIPELINE_TOOL_TYPES } from '../fixtures'; import configReducer from '@src/context/config/configSlice'; @@ -25,6 +24,7 @@ describe('pipelineTool reducer', () => { orgName: 'mockOrgName', repository: 'mockRepository', steps: ['step1', 'step2'], + crews: [], }, ], }; @@ -38,6 +38,7 @@ describe('pipelineTool reducer', () => { orgName: 'mockOrgName', repository: 'mockRepository', steps: ['step1', 'step2'], + crews: [], }, { id: 'mockId2', @@ -46,6 +47,7 @@ describe('pipelineTool reducer', () => { orgName: 'mockOrgName', repository: 'mockRepository', steps: ['step3', 'step4'], + crews: [], }, ], }; @@ -57,18 +59,6 @@ describe('pipelineTool reducer', () => { }, ]; - it('should set isPipelineToolVerified false when handle initial state', () => { - const result = configReducer(undefined, { type: 'unknown' }); - - expect(result.pipelineTool.isVerified).toEqual(false); - }); - - it('should set isPipelineToolVerified true when handle updatePipelineToolVerifyState given isPipelineToolVerified is true', () => { - const result = configReducer(initialConfigState, updatePipelineToolVerifyState(true)); - - expect(result.pipelineTool.isVerified).toEqual(true); - }); - it('should update pipelineTool fields when change pipelineTool fields input', () => { const config = configReducer(initialConfigState, updatePipelineTool({ token: 'abcd' })); @@ -83,7 +73,6 @@ describe('pipelineTool reducer', () => { type: PIPELINE_TOOL_TYPES.BUILD_KITE, token: '', }, - isVerified: false, isShow: false, verifiedResponse: { pipelineList: [ @@ -95,9 +84,9 @@ describe('pipelineTool reducer', () => { repository: 'mock repository url', steps: [], branches: [], + crews: [], }, ], - pipelineCrews: [], }, }, }; @@ -131,7 +120,6 @@ describe('pipelineTool reducer', () => { type: PIPELINE_TOOL_TYPES.BUILD_KITE, token: '', }, - isVerified: false, isShow: false, verifiedResponse: { pipelineList: [ @@ -143,9 +131,9 @@ describe('pipelineTool reducer', () => { repository: 'mock repository url', steps: [], branches: [], + crews: [], }, ], - pipelineCrews: [], }, }, }; @@ -168,6 +156,7 @@ describe('pipelineTool reducer', () => { repository: 'mock repository url', steps: [], branches: [], + crews: [], }, ]); }); @@ -238,13 +227,15 @@ describe('pipelineTool reducer', () => { expect(selectStepsParams(store.getState(), 'mockOrgName', 'mockName')).toEqual({ buildId: 'mockId', organizationId: 'mockOrgId', - params: { - endTime: dayjs(MOCK_DATE_RANGE[0].endDate).endOf('date').valueOf(), - orgName: 'mockOrgName', - pipelineName: 'mockName', - repository: 'mockRepository', - startTime: dayjs(MOCK_DATE_RANGE[0].startDate).startOf('date').valueOf(), - }, + params: [ + { + endTime: dayjs(MOCK_DATE_RANGE[0].endDate).endOf('date').valueOf(), + orgName: 'mockOrgName', + pipelineName: 'mockName', + repository: 'mockRepository', + startTime: dayjs(MOCK_DATE_RANGE[0].startDate).startOf('date').valueOf(), + }, + ], pipelineType: 'BuildKite', token: '', }); @@ -254,13 +245,15 @@ describe('pipelineTool reducer', () => { expect(selectStepsParams(store.getState(), '', '')).toEqual({ buildId: '', organizationId: '', - params: { - endTime: dayjs(MOCK_DATE_RANGE[0].endDate).endOf('date').valueOf(), - orgName: '', - pipelineName: '', - repository: '', - startTime: dayjs(MOCK_DATE_RANGE[0].startDate).startOf('date').valueOf(), - }, + params: [ + { + endTime: dayjs(MOCK_DATE_RANGE[0].endDate).endOf('date').valueOf(), + orgName: '', + pipelineName: '', + repository: '', + startTime: dayjs(MOCK_DATE_RANGE[0].startDate).startOf('date').valueOf(), + }, + ], pipelineType: 'BuildKite', token: '', }); diff --git a/frontend/__tests__/context/sourceControlSlice.test.ts b/frontend/__tests__/context/sourceControlSlice.test.ts index 6c46a1289a..f8f0c1f645 100644 --- a/frontend/__tests__/context/sourceControlSlice.test.ts +++ b/frontend/__tests__/context/sourceControlSlice.test.ts @@ -1,24 +1,11 @@ import sourceControlReducer, { updateSourceControl, updateSourceControlVerifiedResponse, - updateSourceControlVerifyState, } from '@src/context/config/configSlice'; import { MOCK_GITHUB_VERIFY_RESPONSE } from '../fixtures'; import initialConfigState from '../initialConfigState'; describe('sourceControl reducer', () => { - it('should set isSourceControlVerified false when handle initial state', () => { - const sourceControl = sourceControlReducer(undefined, { type: 'unknown' }); - - expect(sourceControl.sourceControl.isVerified).toEqual(false); - }); - - it('should return true when handle changeSourceControlVerifyState given isSourceControlVerified is true', () => { - const sourceControl = sourceControlReducer(initialConfigState, updateSourceControlVerifyState(true)); - - expect(sourceControl.sourceControl.isVerified).toEqual(true); - }); - it('should update sourceControl fields when change sourceControl fields input', () => { const sourceControl = sourceControlReducer(initialConfigState, updateSourceControl({ token: 'token' })); diff --git a/frontend/__tests__/context/stepperSlice.test.ts b/frontend/__tests__/context/stepperSlice.test.ts index 14d5d69cee..9f0bec1803 100644 --- a/frontend/__tests__/context/stepperSlice.test.ts +++ b/frontend/__tests__/context/stepperSlice.test.ts @@ -20,7 +20,9 @@ describe('stepper reducer', () => { { stepNumber: 0, timeStamp: 0, - shouldMetricsLoad: true, + shouldMetricsLoaded: true, + metricsPageFailedTimeRangeInfos: {}, + reportPageFailedTimeRangeInfos: {}, }, nextStep(), ); @@ -33,7 +35,9 @@ describe('stepper reducer', () => { { stepNumber: 0, timeStamp: 0, - shouldMetricsLoad: true, + shouldMetricsLoaded: true, + metricsPageFailedTimeRangeInfos: {}, + reportPageFailedTimeRangeInfos: {}, }, backStep(), ); @@ -46,7 +50,9 @@ describe('stepper reducer', () => { { stepNumber: 2, timeStamp: 0, - shouldMetricsLoad: true, + shouldMetricsLoaded: true, + metricsPageFailedTimeRangeInfos: {}, + reportPageFailedTimeRangeInfos: {}, }, backStep(), ); @@ -60,7 +66,9 @@ describe('stepper reducer', () => { { stepNumber: 2, timeStamp: 0, - shouldMetricsLoad: true, + shouldMetricsLoaded: true, + metricsPageFailedTimeRangeInfos: {}, + reportPageFailedTimeRangeInfos: {}, }, updateTimeStamp(mockTime), ); diff --git a/frontend/__tests__/fixtures.ts b/frontend/__tests__/fixtures.ts index b88860ccac..aedd61ce43 100644 --- a/frontend/__tests__/fixtures.ts +++ b/frontend/__tests__/fixtures.ts @@ -1,6 +1,8 @@ import { CSVReportRequestDTO, ReportRequestDTO } from '@src/clients/report/dto/request'; +import { SortType } from '@src/containers/ConfigStep/DateRangePicker/types'; import { ReportResponseDTO } from '@src/clients/report/dto/response'; import { SOURCE_CONTROL_TYPES } from '@src/constants/resources'; +import { IStepsParams } from '@src/clients/MetricsClient'; import { METRIC_TYPES } from '@src/constants/commons'; export const PROJECT_NAME = 'Heartbeat'; @@ -108,7 +110,7 @@ export const MOCK_SOURCE_CONTROL_VERIFY_TOKEN_URL = `${BASE_URL}/source-control/ export const MOCK_SOURCE_CONTROL_VERIFY_BRANCH_URL = `${BASE_URL}/source-control/:type/repos/branches/verify`; export const MOCK_REPORT_URL = `${BASE_URL}/reports`; export const MOCK_VERSION_URL = `${BASE_URL}/version`; -export const MOCK_EXPORT_CSV_URL = `${BASE_URL}/reports/:dataType/:fileName`; +export const MOCK_EXPORT_CSV_URL = `${BASE_URL}/reports/:dataType/:csvTimeStamp`; export const VERSION_RESPONSE = { version: '1.11', @@ -224,6 +226,7 @@ export const MOCK_GENERATE_REPORT_REQUEST_PARAMS: ReportRequestDTO = { export const IMPORTED_NEW_CONFIG_FIXTURE = { projectName: 'ConfigFileForImporting', metrics: ['Velocity', 'Cycle time', 'Classification', 'Lead time for changes'], + sortType: SortType.DEFAULT, dateRange: [ { startDate: '2023-03-16T00:00:00.000+08:00', @@ -285,10 +288,14 @@ export const MOCK_IMPORT_FILE = { metrics: [], }; -export const MOCK_DATE_RANGE = [ +export const MockedDateRanges = [ { - startDate: '2023-04-04T00:00:00+08:00', - endDate: '2023-04-18T00:00:00+08:00', + startDate: '2024-02-04T00:00:00.000+08:00', + endDate: '2024-02-17T23:59:59.999+08:00', + }, + { + startDate: '2024-02-18T00:00:00.000+08:00', + endDate: '2024-02-28T23:59:59.999+08:00', }, ]; @@ -366,13 +373,22 @@ export enum PIPELINE_SETTING_TYPES { export const CONFIRM_DIALOG_DESCRIPTION = 'All the filled data will be cleared. Continue to Home page?'; export const MOCK_GET_STEPS_PARAMS = { - params: { - pipelineName: 'mock pipeline name', - repository: 'mock repository', - orgName: 'mock orgName', - startTime: 1212112121212, - endTime: 1313131313131, - }, + params: [ + { + pipelineName: 'mock pipeline name', + repository: 'mock repository', + orgName: 'mock orgName', + startTime: 1212112121212, + endTime: 1313131313131, + }, + { + pipelineName: 'mock pipeline name', + repository: 'mock repository', + orgName: 'mock orgName', + startTime: 1212112121214, + endTime: 1313131313134, + }, + ] as IStepsParams[], buildId: 'mockBuildId', organizationId: 'mockOrganizationId', pipelineType: 'BuildKite', @@ -467,7 +483,6 @@ export const MOCK_REPORT_RESPONSE: ReportResponseDTO = { fromAnalysis: null, fromInDev: null, fromBlock: 111, - fromFlag: null, fromReview: 111, fromWaitingForTesting: 111, fromTesting: null, @@ -640,9 +655,20 @@ export const MOCK_SOURCE_CONTROL_VERIFY_ERROR_CASE_TEXT = 'Token is incorrect!'; export const MOCK_PIPELINE_VERIFY_UNAUTHORIZED_TEXT = 'Token is incorrect!'; export const MOCK_PIPELINE_VERIFY_FORBIDDEN_ERROR_TEXT = 'Forbidden request, please change your token with correct access permission.'; +export const UNKNOWN_ERROR_TEXT = 'Unknown error'; export const FAKE_TOKEN = 'fake-token'; +export const FAKE_DATE_EARLIER = { + startDate: '2024-02-01T00:00:00.000+08:00', + endDate: '2024-02-14T23:59:59.999+08:00', +}; + +export const FAKE_DATE_LATER = { + startDate: '2024-03-01T00:00:00.000+08:00', + endDate: '2024-03-14T23:59:59.999+08:00', +}; + export const FAKE_PIPELINE_TOKEN = 'bkua_mockTokenMockTokenMockTokenMockToken1234'; export const ADVANCED_SETTINGS_TITLE = 'Advanced settings'; @@ -673,3 +699,7 @@ export const TIME_RANGE_ERROR_MESSAGE = { }; export const COMMON_TIME_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; + +export const PIPELINE_TOOL_TOKEN_INPUT_LABEL = 'input token'; + +export const TIMEOUT_ALERT_TEST_ID = 'timeoutAlert'; diff --git a/frontend/__tests__/hooks/reportMapper/changeFailureRate.test.tsx b/frontend/__tests__/hooks/reportMapper/changeFailureRate.test.tsx index f8ae3f238e..05dfd09e3b 100644 --- a/frontend/__tests__/hooks/reportMapper/changeFailureRate.test.tsx +++ b/frontend/__tests__/hooks/reportMapper/changeFailureRate.test.tsx @@ -23,23 +23,12 @@ describe('dev change failure rate data mapper', () => { { id: 0, name: 'fs-platform-onboarding/ :shipit: deploy to PROD', - valuesList: [ + valueList: [ { - name: 'Dev change failure rate', value: '0.00%(0/3)', }, ], }, - { - id: 1, - name: 'Average', - valuesList: [ - { - name: 'Dev change failure rate', - value: '0.00%(0/12)', - }, - ], - }, ]; const mappedDevChangeFailureRate = devChangeFailureRateMapper(mockDevChangeFailureRateRes); diff --git a/frontend/__tests__/hooks/reportMapper/deploymentFrequency.test.tsx b/frontend/__tests__/hooks/reportMapper/deploymentFrequency.test.tsx index 4583784366..a3bc1023f8 100644 --- a/frontend/__tests__/hooks/reportMapper/deploymentFrequency.test.tsx +++ b/frontend/__tests__/hooks/reportMapper/deploymentFrequency.test.tsx @@ -33,23 +33,12 @@ describe('deployment frequency data mapper', () => { { id: 0, name: 'fs-platform-onboarding/ :shipit: deploy to PROD', - valuesList: [ + valueList: [ { - name: 'Deployment frequency', value: '0.30', }, ], }, - { - id: 1, - name: 'Average', - valuesList: [ - { - name: 'Deployment frequency', - value: '0.40', - }, - ], - }, ]; const mappedDeploymentFrequency = deploymentFrequencyMapper(mockDeploymentFrequencyRes); diff --git a/frontend/__tests__/hooks/reportMapper/meanTimeToRecovery.test.tsx b/frontend/__tests__/hooks/reportMapper/meanTimeToRecovery.test.tsx index 7605c059e4..451e76d164 100644 --- a/frontend/__tests__/hooks/reportMapper/meanTimeToRecovery.test.tsx +++ b/frontend/__tests__/hooks/reportMapper/meanTimeToRecovery.test.tsx @@ -19,19 +19,8 @@ describe('dev mean time to recovery data mapper', () => { { id: 0, name: 'fs-platform-onboarding/ :shipit: deploy to PROD', - valuesList: [ + valueList: [ { - name: 'Dev mean time to recovery', - value: '45.03', - }, - ], - }, - { - id: 1, - name: 'Average', - valuesList: [ - { - name: 'Dev mean time to recovery', value: '45.03', }, ], @@ -60,19 +49,8 @@ describe('dev mean time to recovery data mapper', () => { { id: 0, name: 'fs-platform-onboarding/ :shipit: deploy to PROD', - valuesList: [ + valueList: [ { - name: 'Dev mean time to recovery', - value: '0.00', - }, - ], - }, - { - id: 1, - name: 'Average', - valuesList: [ - { - name: 'Dev mean time to recovery', value: '0.00', }, ], @@ -101,19 +79,8 @@ describe('dev mean time to recovery data mapper', () => { { id: 0, name: 'fs-platform-onboarding/ :shipit: deploy to PROD', - valuesList: [ - { - name: 'Dev mean time to recovery', - value: '0.00', - }, - ], - }, - { - id: 1, - name: 'Average', - valuesList: [ + valueList: [ { - name: 'Dev mean time to recovery', value: '0.00', }, ], diff --git a/frontend/__tests__/hooks/reportMapper/report.test.tsx b/frontend/__tests__/hooks/reportMapper/report.test.tsx index e78629a51b..56e745f02f 100644 --- a/frontend/__tests__/hooks/reportMapper/report.test.tsx +++ b/frontend/__tests__/hooks/reportMapper/report.test.tsx @@ -42,31 +42,19 @@ export const EXPECTED_REPORT_VALUES = { { id: 0, name: 'fs-platform-onboarding/ :shipit: deploy to PROD', - valuesList: [ + valueList: [ { - name: 'Deployment frequency', value: '0.30', }, ], }, - { - id: 1, - name: 'Average', - valuesList: [ - { - name: 'Deployment frequency', - value: '0.40', - }, - ], - }, ], devMeanTimeToRecoveryList: [ { id: 0, name: 'Heartbeat/:react: Build Frontend', - valuesList: [ + valueList: [ { - name: 'Dev mean time to recovery', value: '4.32', }, ], @@ -74,9 +62,8 @@ export const EXPECTED_REPORT_VALUES = { { id: 1, name: 'Heartbeat/:cloudformation: Deploy infra', - valuesList: [ + valueList: [ { - name: 'Dev mean time to recovery', value: '0.00', }, ], @@ -84,23 +71,12 @@ export const EXPECTED_REPORT_VALUES = { { id: 2, name: 'Heartbeat/:rocket: Run e2e', - valuesList: [ + valueList: [ { - name: 'Dev mean time to recovery', value: '7.67', }, ], }, - { - id: 3, - name: 'Average', - valuesList: [ - { - name: 'Dev mean time to recovery', - value: '4.00', - }, - ], - }, ], leadTimeForChangesList: [ { @@ -126,23 +102,12 @@ export const EXPECTED_REPORT_VALUES = { { id: 0, name: 'fs-platform-onboarding/ :shipit: deploy to PROD', - valuesList: [ + valueList: [ { - name: 'Dev change failure rate', value: '0.00%(0/2)', }, ], }, - { - id: 1, - name: 'Average', - valuesList: [ - { - name: 'Dev change failure rate', - value: '0.00%(0/6)', - }, - ], - }, ], exportValidityTimeMin: 30, reworkList: [ @@ -171,7 +136,7 @@ export const EXPECTED_REPORT_VALUES = { ], }, { - id: 5, + id: 4, name: ( From review to in dev @@ -185,7 +150,7 @@ export const EXPECTED_REPORT_VALUES = { ], }, { - id: 6, + id: 5, name: ( From waiting for testing to in dev @@ -199,7 +164,7 @@ export const EXPECTED_REPORT_VALUES = { ], }, { - id: 8, + id: 7, name: ( From done to in dev @@ -213,7 +178,7 @@ export const EXPECTED_REPORT_VALUES = { ], }, { - id: 9, + id: 8, name: Total rework cards, valueList: [ { @@ -223,7 +188,7 @@ export const EXPECTED_REPORT_VALUES = { ], }, { - id: 10, + id: 9, name: Rework cards ratio, valueList: [ { diff --git a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx index cf7411582c..59474be55c 100644 --- a/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx +++ b/frontend/__tests__/hooks/useGenerateReportEffect.test.tsx @@ -1,13 +1,30 @@ -import { MOCK_GENERATE_REPORT_REQUEST_PARAMS, MOCK_REPORT_RESPONSE, MOCK_RETRIEVE_REPORT_RESPONSE } from '../fixtures'; -import { useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect'; +import { + GeneralErrorKey, + IReportError, + IReportInfo, + useGenerateReportEffect, + IUseGenerateReportEffect, + TimeoutErrorKey, +} from '@src/hooks/useGenerateReportEffect'; +import { + MOCK_GENERATE_REPORT_REQUEST_PARAMS, + MOCK_REPORT_RESPONSE, + MOCK_RETRIEVE_REPORT_RESPONSE, + MockedDateRanges, +} from '../fixtures'; +import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; +import { updateDateRange } from '@src/context/config/configSlice'; import { act, renderHook, waitFor } from '@testing-library/react'; import { reportClient } from '@src/clients/report/ReportClient'; +import { setupStore } from '@test/utils/setupStoreUtil'; import { TimeoutError } from '@src/errors/TimeoutError'; import { UnknownError } from '@src/errors/UnknownError'; +import { METRIC_TYPES } from '@src/constants/commons'; +import React, { ReactNode } from 'react'; +import { Provider } from 'react-redux'; import { HttpStatusCode } from 'axios'; import clearAllMocks = jest.clearAllMocks; import resetAllMocks = jest.resetAllMocks; -import { METRIC_TYPES } from '@src/constants/commons'; const MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE = { ...MOCK_GENERATE_REPORT_REQUEST_PARAMS, @@ -18,11 +35,24 @@ const MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE = { metricTypes: [METRIC_TYPES.DORA], }; +let store = setupStore(); + +const Wrapper = ({ children }: { children: ReactNode }) => { + return {children}; +}; + +const setup = () => + renderHook(() => useGenerateReportEffect(), { + wrapper: Wrapper, + }); + describe('use generate report effect', () => { afterAll(() => { clearAllMocks(); }); beforeEach(() => { + store = setupStore(); + store.dispatch(updateDateRange(MockedDateRanges)); jest.useFakeTimers(); }); afterEach(() => { @@ -30,200 +60,215 @@ describe('use generate report effect', () => { jest.useRealTimers(); }); - it('should set "Data loading failed" for board metrics when board data retrieval times out', async () => { - reportClient.retrieveByUrl = jest.fn().mockRejectedValue(new TimeoutError('5xx error', 503)); - - const { result } = renderHook(() => useGenerateReportEffect()); - - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); - expect(result.current.timeout4Board).toEqual('Data loading failed'); - }); - }); - - it('should call polling report and setTimeout when request board data given pollingReport response return 204', async () => { - reportClient.polling = jest - .fn() - .mockImplementation(async () => ({ status: HttpStatusCode.NoContent, response: MOCK_REPORT_RESPONSE })); + it('should set "Data loading failed" for all board metrics when board data retrieval times out', async () => { reportClient.retrieveByUrl = jest .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); + .mockRejectedValue(new TimeoutError('timeout error', AXIOS_REQUEST_ERROR_CODE.TIMEOUT)); + reportClient.polling = jest.fn(); - const { result } = renderHook(() => useGenerateReportEffect()); + const { result } = setup(); - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); + await act(async () => { + await result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); }); - jest.runOnlyPendingTimers(); - - await waitFor(() => { - expect(reportClient.polling).toHaveBeenCalledTimes(1); - }); + expect(result.current.reportInfos.length).toEqual(2); + expect(result.current.reportInfos[0].timeout4Board.message).toEqual('Data loading failed'); + expect(result.current.reportInfos[0].timeout4Board.shouldShow).toEqual(true); + expect(result.current.reportInfos[0].reportData).toEqual(undefined); + expect(result.current.reportInfos[1].timeout4Board.message).toEqual('Data loading failed'); + expect(result.current.reportInfos[1].timeout4Board.shouldShow).toEqual(true); + expect(result.current.reportInfos[1].reportData).toEqual(undefined); + expect(reportClient.polling).toHaveBeenCalledTimes(0); }); - it('should call polling report more than one time when metrics is loading', async () => { - reportClient.polling = jest.fn().mockImplementation(async () => ({ - status: HttpStatusCode.NoContent, - response: { ...MOCK_REPORT_RESPONSE, allMetricsCompleted: false }, - })); + it('should set "Data loading failed" for dora metrics when dora data retrieval times out', async () => { reportClient.retrieveByUrl = jest .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); + .mockRejectedValueOnce(new TimeoutError('timeout error', AXIOS_REQUEST_ERROR_CODE.TIMEOUT)) + .mockResolvedValueOnce(async () => MOCK_RETRIEVE_REPORT_RESPONSE); - const { result } = renderHook(() => useGenerateReportEffect()); + reportClient.polling = jest + .fn() + .mockImplementation(async () => ({ status: HttpStatusCode.Ok, response: MOCK_REPORT_RESPONSE })); + const { result } = setup(); - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); - }); - act(() => { - jest.advanceTimersByTime(10000); + await act(async () => { + await result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE); }); - await waitFor(() => { - expect(reportClient.polling).toHaveBeenCalledTimes(2); - }); + expect(result.current.reportInfos[0].timeout4Dora.message).toEqual('Data loading failed'); + expect(result.current.reportInfos[0].timeout4Dora.shouldShow).toEqual(true); + expect(result.current.reportInfos[0].reportData).toEqual(undefined); + expect(result.current.reportInfos[1].timeout4Dora.message).toEqual(''); + expect(result.current.reportInfos[1].reportData).toBeTruthy(); }); - it('should call polling report only once when request board data given dora data retrieval is called before', async () => { + it('should call polling report and setTimeout when request board data given pollingReport response return 200', async () => { reportClient.polling = jest .fn() - .mockImplementation(async () => ({ status: HttpStatusCode.NoContent, response: MOCK_REPORT_RESPONSE })); - reportClient.retrieveByUrl = jest - .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); + .mockImplementation(async () => ({ status: HttpStatusCode.Ok, response: MOCK_REPORT_RESPONSE })); + reportClient.retrieveByUrl = jest.fn().mockImplementation(async () => MOCK_RETRIEVE_REPORT_RESPONSE); - const { result } = renderHook(() => useGenerateReportEffect()); + const { result } = setup(); await waitFor(() => { result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE); }); jest.runOnlyPendingTimers(); await waitFor(() => { - expect(reportClient.polling).toHaveBeenCalledTimes(1); - }); - }); - - it('should set "Data loading failed" for dora metrics when dora data retrieval times out', async () => { - reportClient.retrieveByUrl = jest.fn().mockRejectedValue(new TimeoutError('5xx error', 503)); - - const { result } = renderHook(() => useGenerateReportEffect()); - - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE); - expect(result.current.timeout4Dora).toEqual('Data loading failed'); - }); - }); - - it('should set "Data loading failed" for report when polling times out', async () => { - reportClient.polling = jest.fn().mockImplementation(async () => { - throw new TimeoutError('5xx error', 503); - }); - - reportClient.retrieveByUrl = jest - .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); - - const { result } = renderHook(() => useGenerateReportEffect()); - - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); - expect(result.current.timeout4Report).toEqual('Data loading failed'); + expect(reportClient.polling).toHaveBeenCalledTimes(2); }); }); - it('should call polling report and setTimeout when request dora data given pollingReport response return 204', async () => { + it('should call polling report more than one time when metrics is loading', async () => { reportClient.polling = jest .fn() - .mockImplementation(async () => ({ status: HttpStatusCode.NoContent, response: MOCK_REPORT_RESPONSE })); - + .mockReturnValueOnce({ + status: HttpStatusCode.Ok, + response: { ...MOCK_REPORT_RESPONSE, allMetricsCompleted: false }, + }) + .mockRejectedValue(new TimeoutError('timeout error', AXIOS_REQUEST_ERROR_CODE.TIMEOUT)) + .mockReturnValueOnce({ + status: HttpStatusCode.Ok, + response: { ...MOCK_REPORT_RESPONSE, allMetricsCompleted: true }, + }); reportClient.retrieveByUrl = jest .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); + .mockReturnValueOnce(MOCK_RETRIEVE_REPORT_RESPONSE) + .mockReturnValueOnce({ ...MOCK_RETRIEVE_REPORT_RESPONSE, callbackUrl: '/url/1234' }); - const { result } = renderHook(() => useGenerateReportEffect()); + const { result } = setup(); - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE); + await act(async () => { + await result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + jest.advanceTimersByTime(10000); }); - jest.runOnlyPendingTimers(); - - await waitFor(() => { - expect(reportClient.polling).toHaveBeenCalledTimes(1); - }); + expect(reportClient.polling).toHaveBeenCalledTimes(3); + expect(result.current.reportInfos[0][TimeoutErrorKey[METRIC_TYPES.ALL] as keyof IReportError].message).toEqual( + 'Data loading failed', + ); + expect(result.current.reportInfos[0][TimeoutErrorKey[METRIC_TYPES.ALL] as keyof IReportError].shouldShow).toEqual( + true, + ); }); - it('should call polling report only once when request dora data given board data retrieval is called before', async () => { + it('should call polling report only once when request board data given dora data retrieval is called before', async () => { reportClient.polling = jest .fn() - .mockImplementation(async () => ({ status: HttpStatusCode.NoContent, response: MOCK_REPORT_RESPONSE })); + .mockImplementation(async () => ({ status: HttpStatusCode.Ok, response: MOCK_REPORT_RESPONSE })); + reportClient.retrieveByUrl = jest.fn().mockImplementation(async () => MOCK_RETRIEVE_REPORT_RESPONSE); - reportClient.retrieveByUrl = jest - .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); - - const { result } = renderHook(() => useGenerateReportEffect()); + const { result } = setup(); - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE); - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); + await waitFor(async () => { + await result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); + await result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE); }); jest.runOnlyPendingTimers(); - await waitFor(() => { - expect(reportClient.polling).toHaveBeenCalledTimes(1); - }); + expect(reportClient.polling).toHaveBeenCalledTimes(2); }); - it('should set "Data loading failed" for board metric when request board data given UnknownException', async () => { + it.each([ + { + params: MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE, + errorKey: GeneralErrorKey[METRIC_TYPES.BOARD], + }, + { + params: MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE, + errorKey: GeneralErrorKey[METRIC_TYPES.DORA], + }, + { + params: MOCK_GENERATE_REPORT_REQUEST_PARAMS, + errorKey: GeneralErrorKey[METRIC_TYPES.ALL], + }, + ])('should set "Data loading failed" for board metric when request given UnknownException', async (_) => { reportClient.retrieveByUrl = jest.fn().mockRejectedValue(new UnknownError()); - const { result } = renderHook(() => useGenerateReportEffect()); + const { result } = setup(); - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); - expect(result.current.generalError4Board).toEqual('Data loading failed'); + await act(async () => { + await result.current.startToRequestData(_.params); }); - }); + const errorKey = _.errorKey as keyof IReportError; - it('should set "Data loading failed" for dora metric when request dora data given UnknownException', async () => { - reportClient.retrieveByUrl = jest.fn().mockRejectedValue(new UnknownError()); + expect(result.current.reportInfos[0][errorKey].message).toEqual('Data loading failed'); + expect(result.current.reportInfos[0][errorKey].shouldShow).toEqual(true); + expect(result.current.reportInfos[1][errorKey].message).toEqual('Data loading failed'); + expect(result.current.reportInfos[1][errorKey].shouldShow).toEqual(true); + }); - const { result } = renderHook(() => useGenerateReportEffect()); + it.each([ + { + errorKey: 'boardMetricsError', + stateKey: 'shouldShowBoardMetricsError', + updateMethod: 'closeBoardMetricsError', + }, + { + errorKey: 'pipelineMetricsError', + stateKey: 'shouldShowPipelineMetricsError', + updateMethod: 'closePipelineMetricsError', + }, + { + errorKey: 'sourceControlMetricsError', + stateKey: 'shouldShowSourceControlMetricsError', + updateMethod: 'closeSourceControlMetricsError', + }, + ])('should update the report error status when call the update method', async (_) => { + reportClient.polling = jest.fn().mockImplementation(async () => ({ + status: HttpStatusCode.Ok, + response: { + ...MOCK_REPORT_RESPONSE, + reportMetricsError: { + [_.errorKey]: { + status: 400, + message: 'error', + }, + }, + }, + })); + reportClient.retrieveByUrl = jest.fn().mockImplementation(async () => MOCK_RETRIEVE_REPORT_RESPONSE); + const { result } = setup(); - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_DORA_METRIC_TYPE); - expect(result.current.generalError4Dora).toEqual('Data loading failed'); + await act(async () => { + await result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); }); - }); - it('should set "Data loading failed" for report when polling given UnknownException', async () => { - reportClient.polling = jest.fn().mockRejectedValue(new UnknownError()); - reportClient.retrieveByUrl = jest - .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); - - const { result } = renderHook(() => useGenerateReportEffect()); + expect(result.current.reportInfos[0][_.stateKey as keyof IReportInfo]).toEqual(true); + expect(result.current.reportInfos[1][_.stateKey as keyof IReportInfo]).toEqual(true); - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); - expect(result.current.generalError4Report).toEqual('Data loading failed'); + await act(async () => { + const updateMethod = result.current[_.updateMethod as keyof IUseGenerateReportEffect] as (id: string) => void; + updateMethod(MockedDateRanges[0].startDate); }); - }); - - it('should set "Data loading failed" for report when all data retrieval times out', async () => { - reportClient.retrieveByUrl = jest.fn().mockRejectedValue(new TimeoutError('5xx error', 503)); - const { result } = renderHook(() => useGenerateReportEffect()); + expect(result.current.reportInfos[0][_.stateKey as keyof IReportInfo]).toEqual(false); + expect(result.current.reportInfos[1][_.stateKey as keyof IReportInfo]).toEqual(true); + }); - await waitFor(() => { - result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); - expect(result.current.timeout4Report).toEqual('Data loading failed'); + it('should update the network error status when call the update method', async () => { + reportClient.retrieveByUrl = jest.fn().mockImplementation(async () => MOCK_RETRIEVE_REPORT_RESPONSE); + reportClient.polling = jest + .fn() + .mockRejectedValue(new TimeoutError('timeout error', AXIOS_REQUEST_ERROR_CODE.TIMEOUT)); + const { result } = setup(); + await act(async () => { + await result.current.startToRequestData(MOCK_GENERATE_REPORT_REQUEST_PARAMS_WITH_BOARD_METRIC_TYPE); + }); + expect(result.current.reportInfos[0].timeout4Dora.shouldShow).toEqual(true); + expect(result.current.reportInfos[1].timeout4Dora.shouldShow).toEqual(true); + await act(async () => { + await result.current.closeReportInfosErrorStatus( + MockedDateRanges[0].startDate, + TimeoutErrorKey[METRIC_TYPES.DORA], + ); }); + expect(result.current.reportInfos[0].timeout4Dora.shouldShow).toEqual(false); + expect(result.current.reportInfos[1].timeout4Dora.shouldShow).toEqual(true); }); }); diff --git a/frontend/__tests__/hooks/useGetBoardInfo.test.tsx b/frontend/__tests__/hooks/useGetBoardInfo.test.tsx index 700ea171af..ff6ac62485 100644 --- a/frontend/__tests__/hooks/useGetBoardInfo.test.tsx +++ b/frontend/__tests__/hooks/useGetBoardInfo.test.tsx @@ -1,9 +1,13 @@ +import { MOCK_BOARD_INFO_URL, FAKE_TOKEN, FAKE_DATE_EARLIER, FAKE_DATE_LATER } from '@test/fixtures'; +import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; import { useGetBoardInfoEffect } from '@src/hooks/useGetBoardInfo'; import { renderHook, act, waitFor } from '@testing-library/react'; -import { MOCK_BOARD_INFO_URL, FAKE_TOKEN } from '@test/fixtures'; +import { setupStore } from '@test/utils/setupStoreUtil'; +import React, { ReactNode } from 'react'; +import { HttpResponse, http } from 'msw'; +import { Provider } from 'react-redux'; import { setupServer } from 'msw/node'; import { HttpStatusCode } from 'axios'; -import { rest } from 'msw'; const server = setupServer(); @@ -14,17 +18,32 @@ const mockBoardConfig = { site: 'fake', email: 'fake@fake.com', token: FAKE_TOKEN, - startTime: null, - endTime: null, + dateRanges: [ + { + startDate: FAKE_DATE_LATER.startDate, + endDate: FAKE_DATE_LATER.endDate, + }, + { + startDate: FAKE_DATE_EARLIER.startDate, + endDate: FAKE_DATE_EARLIER.endDate, + }, + ], }; + describe('use get board info', () => { beforeAll(() => server.listen()); afterAll(() => { jest.clearAllMocks(); server.close(); }); + const store = setupStore(); + const wrapper = ({ children }: { children: ReactNode }) => { + return {children}; + }; + const setup = () => renderHook(() => useGetBoardInfoEffect(), { wrapper }); + it('should got init data when hook render', () => { - const { result } = renderHook(() => useGetBoardInfoEffect()); + const { result } = setup(); expect(result.current.isLoading).toBe(false); expect(result.current.errorMessage).toMatchObject({}); }); @@ -35,26 +54,36 @@ describe('use get board info', () => { 'No card within selected date range!', 'Please go back to the previous page and change your collection date, or check your board info!', ], - [HttpStatusCode.BadRequest, 'Invalid input!', 'Please go back to the previous page and check your board info!'], + [ + HttpStatusCode.BadRequest, + 'Failed to get Board configuration!', + 'Please go back to the previous page and check your board info!', + ], [ HttpStatusCode.Unauthorized, - 'Unauthorized request!', + 'Failed to get Board configuration!', 'Please go back to the previous page and check your board info!', ], [ HttpStatusCode.Forbidden, - 'Forbidden request!', - 'Please go back to the previous page and change your board token with correct access permission.', + 'Failed to get Board configuration!', + 'Please go back to the previous page and check your board info!', + ], + [ + HttpStatusCode.NotFound, + 'Failed to get Board configuration!', + 'Please go back to the previous page and check your board info!', ], - [HttpStatusCode.NotFound, 'Not found!', 'Please go back to the previous page and check your board info!'], ])('should got error message when got code is %s', async (code, title, message) => { server.use( - rest.post(MOCK_BOARD_INFO_URL, (_, res, ctx) => { - return res(ctx.status(code)); + http.post(MOCK_BOARD_INFO_URL, () => { + return new HttpResponse(null, { + status: code, + }); }), ); - const { result } = renderHook(() => useGetBoardInfoEffect()); + const { result } = setup(); await act(() => { result.current.getBoardInfo(mockBoardConfig); }); @@ -64,4 +93,124 @@ describe('use get board info', () => { }); expect(result.current.errorMessage.message).toEqual(message); }); + + it('should get data when mock 4xx error', async () => { + const mockResponse = { + ignoredTargetFields: [ + { + key: 'description', + name: 'Description', + flag: false, + }, + ], + jiraColumns: [ + { + key: 'To Do', + value: { + name: 'TODO', + statuses: ['TODO'], + }, + }, + ], + targetFields: [ + { + key: 'issuetype', + name: 'Issue Type', + flag: false, + }, + ], + users: ['heartbeat user'], + }; + server.use( + http.post(MOCK_BOARD_INFO_URL, () => { + return new HttpResponse(JSON.stringify(mockResponse), { + status: HttpStatusCode.BadRequest, + }); + }), + ); + const { result } = setup(); + await act(() => { + result.current.getBoardInfo(mockBoardConfig); + }); + + await waitFor(() => { + expect(result.current.errorMessage.title).toEqual('Failed to get Board configuration!'); + }); + expect(result.current.errorMessage.message).toEqual( + 'Please go back to the previous page and check your board info!', + ); + }); + + it('should get data when mock 3xx error', async () => { + server.use( + http.post(MOCK_BOARD_INFO_URL, () => { + return new HttpResponse( + JSON.stringify({ + code: AXIOS_REQUEST_ERROR_CODE.TIMEOUT, + }), + { + status: HttpStatusCode.Unused, + }, + ); + }), + ); + const { result } = setup(); + await act(() => { + result.current.getBoardInfo(mockBoardConfig); + }); + + await waitFor(() => { + expect(result.current.errorMessage.title).toEqual('Failed to get Board configuration!'); + }); + expect(result.current.errorMessage.message).toEqual( + 'Please go back to the previous page and check your board info!', + ); + }); + + it('should get data when status is OK', async () => { + const mockResponse = { + ignoredTargetFields: [ + { + key: 'description', + name: 'Description', + flag: false, + }, + ], + jiraColumns: [ + { + key: 'To Do', + value: { + name: 'TODO', + statuses: ['TODO'], + }, + }, + ], + targetFields: [ + { + key: 'issuetype', + name: 'Issue Type', + flag: false, + }, + ], + users: ['heartbeat user'], + }; + + server.use( + http.post( + MOCK_BOARD_INFO_URL, + () => { + return new HttpResponse(JSON.stringify(mockResponse), { + status: HttpStatusCode.Ok, + }); + }, + { + once: true, + }, + ), + ); + const { result } = setup(); + act(() => { + result.current.getBoardInfo(mockBoardConfig); + }); + }); }); diff --git a/frontend/__tests__/hooks/useGetMetricsStepsEffect.test.tsx b/frontend/__tests__/hooks/useGetMetricsStepsEffect.test.tsx index 413fdce00e..9d5e68a303 100644 --- a/frontend/__tests__/hooks/useGetMetricsStepsEffect.test.tsx +++ b/frontend/__tests__/hooks/useGetMetricsStepsEffect.test.tsx @@ -1,45 +1,155 @@ -import { ERROR_MESSAGE_TIME_DURATION, MOCK_GET_STEPS_PARAMS } from '../fixtures'; import { useGetMetricsStepsEffect } from '@src/hooks/useGetMetricsStepsEffect'; -import { InternalServerError } from '@src/errors/InternalServerError'; +import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; +import { act, renderHook, waitFor } from '@testing-library/react'; +import { METRICS_DATA_FAIL_STATUS } from '@src/constants/commons'; import { metricsClient } from '@src/clients/MetricsClient'; -import { act, renderHook } from '@testing-library/react'; -import { HttpStatusCode } from 'axios'; +import { setupStore } from '@test/utils/setupStoreUtil'; +import { TimeoutError } from '@src/errors/TimeoutError'; +import { MOCK_GET_STEPS_PARAMS } from '../fixtures'; +import React, { ReactNode } from 'react'; +import { Provider } from 'react-redux'; + +const mockDispatch = jest.fn(); +jest.mock('@src/context/Metrics/metricsSlice', () => ({ + ...jest.requireActual('@src/context/Metrics/metricsSlice'), + updateShouldRetryPipelineConfig: jest.fn(), +})); + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useDispatch: () => mockDispatch, +})); describe('use get steps effect', () => { const { params, buildId, organizationId, pipelineType, token } = MOCK_GET_STEPS_PARAMS; + const store = setupStore(); + const wrapper = ({ children }: { children: ReactNode }) => { + return {children}; + }; + + const setup = () => renderHook(() => useGetMetricsStepsEffect(), { wrapper }); it('should init data state when render hook', async () => { const { result } = renderHook(() => useGetMetricsStepsEffect()); expect(result.current.isLoading).toEqual(false); }); + it('should get the union set from steps res', async () => { + metricsClient.getSteps = jest + .fn() + .mockReturnValueOnce({ + response: ['a', 'b', 'c'], + haveStep: true, + branches: ['branchA', 'branchB'], + pipelineCrews: ['crewA', 'crewB'], + }) + .mockReturnValueOnce({ + response: ['a', 'd', 'e'], + haveStep: true, + branches: ['branchC', 'branchD'], + pipelineCrews: [], + }) + .mockReturnValueOnce({ + response: [], + haveStep: false, + branches: [], + pipelineCrews: [], + }); + const { result } = renderHook(() => useGetMetricsStepsEffect()); + let res; + await act(async () => { + res = await result.current.getSteps(params, buildId, organizationId, pipelineType, token); + }); + expect(res).toEqual({ + response: ['a', 'b', 'c', 'd', 'e'], + haveStep: true, + branches: ['branchA', 'branchB', 'branchC', 'branchD'], + pipelineCrews: ['crewA', 'crewB'], + }); + }); + + it('should get the steps failed status when partial 4xx response from steps res', async () => { + metricsClient.getSteps = jest + .fn() + .mockReturnValueOnce({ + response: ['a', 'b', 'c'], + haveStep: true, + branches: ['branchA', 'branchB'], + pipelineCrews: ['crewA', 'crewB'], + }) + .mockRejectedValue({ + code: 400, + }); + const { result } = renderHook(() => useGetMetricsStepsEffect()); + await act(async () => { + await result.current.getSteps(params, buildId, organizationId, pipelineType, token); + }); + expect(result.current.stepFailedStatus).toEqual(METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_4XX); + }); + + it('should get the steps failed status when partial timeout response from steps res', async () => { + metricsClient.getSteps = jest + .fn() + .mockReturnValueOnce({ + response: ['a', 'b', 'c'], + haveStep: true, + branches: ['branchA', 'branchB'], + pipelineCrews: ['crewA', 'crewB'], + }) + .mockRejectedValue({ + code: 'NETWORK_TIMEOUT', + }); + const { result } = renderHook(() => useGetMetricsStepsEffect()); + await act(async () => { + await result.current.getSteps(params, buildId, organizationId, pipelineType, token); + }); + expect(result.current.stepFailedStatus).toEqual(METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_TIMEOUT); + }); + it('should set error message when get steps throw error', async () => { jest.useFakeTimers(); metricsClient.getSteps = jest.fn().mockImplementation(() => { - throw new Error('error'); + return Promise.reject('error'); }); - const { result } = renderHook(() => useGetMetricsStepsEffect()); + const { result } = setup(); expect(result.current.isLoading).toEqual(false); - act(() => { - result.current.getSteps(params, buildId, organizationId, pipelineType, token); - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + await act(async () => { + await result.current.getSteps(params, buildId, organizationId, pipelineType, token); }); - expect(result.current.errorMessage).toEqual(''); + expect(result.current.errorMessage).toEqual('Failed to get BuildKite steps'); + + jest.runAllTimers(); + + await waitFor(() => { + expect(result.current.errorMessage).toEqual(''); + }); }); - it('should set error message when get steps response status 500', async () => { + it('should set error message when get steps responses are failed', async () => { metricsClient.getSteps = jest.fn().mockImplementation(() => { - throw new InternalServerError('error message', HttpStatusCode.InternalServerError, 'fake description'); + return Promise.reject('error'); }); - const { result } = renderHook(() => useGetMetricsStepsEffect()); + const { result } = setup(); + await act(async () => { + await result.current.getSteps(params, buildId, organizationId, pipelineType, token); + }); + + expect(result.current.errorMessage).toEqual('Failed to get BuildKite steps'); + }); - act(() => { - result.current.getSteps(params, buildId, organizationId, pipelineType, token); + it('should set error message when get steps responses are timeout', async () => { + metricsClient.getSteps = jest.fn().mockImplementation(() => { + return Promise.reject(new TimeoutError('error', AXIOS_REQUEST_ERROR_CODE.TIMEOUT)); + }); + const { result } = setup(); + await act(async () => { + await result.current.getSteps(params, buildId, organizationId, pipelineType, token); }); - expect(result.current.errorMessage).toEqual('Failed to get BuildKite steps: error message'); + expect(result.current.errorMessage).toEqual('Failed to get BuildKite steps: timeout'); + expect(mockDispatch).toHaveBeenCalledTimes(7); }); }); diff --git a/frontend/__tests__/hooks/useVerifyBoardEffect.test.tsx b/frontend/__tests__/hooks/useVerifyBoardEffect.test.tsx index 9a781cd93f..e45e580c72 100644 --- a/frontend/__tests__/hooks/useVerifyBoardEffect.test.tsx +++ b/frontend/__tests__/hooks/useVerifyBoardEffect.test.tsx @@ -1,161 +1,154 @@ -import { useVerifyBoardEffect, useVerifyBoardStateInterface } from '@src/hooks/useVerifyBoardEffect'; -import { act, renderHook, waitFor } from '@testing-library/react'; -import { FAKE_TOKEN } from '@test/fixtures'; -import { HttpStatusCode } from 'axios'; - +import { boardConfigDefaultValues } from '@src/containers/ConfigStep/Form/useDefaultValues'; +import { boardConfigSchema } from '@src/containers/ConfigStep/Form/schema'; +import { useVerifyBoardEffect } from '@src/hooks/useVerifyBoardEffect'; import { InternalServerError } from '@src/errors/InternalServerError'; import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; import { UnauthorizedError } from '@src/errors/UnauthorizedError'; import { boardClient } from '@src/clients/board/BoardClient'; import { NotFoundError } from '@src/errors/NotFoundError'; import { TimeoutError } from '@src/errors/TimeoutError'; -import { BOARD_TYPES } from '@test/fixtures'; - -const mockDispatch = jest.fn(); -jest.mock('react-redux', () => ({ - ...jest.requireActual('react-redux'), - useDispatch: () => mockDispatch, -})); - -jest.mock('@src/hooks/useAppDispatch', () => ({ - useAppSelector: () => ({ type: BOARD_TYPES.JIRA }), - useAppDispatch: jest.fn(() => jest.fn()), -})); - -const updateFields = (result: { current: useVerifyBoardStateInterface }) => { - result.current.updateField('Board Id', '1'); - result.current.updateField('Email', 'fake@qq.com'); - result.current.updateField('Site', 'fake'); - result.current.updateField('Token', FAKE_TOKEN); +import { FormProvider } from '@test/utils/FormProvider'; +import { setupStore } from '../utils/setupStoreUtil'; +import { renderHook } from '@testing-library/react'; +import { Provider } from 'react-redux'; +import { HttpStatusCode } from 'axios'; +import { ReactNode } from 'react'; + +const setErrorSpy = jest.fn(); +const resetSpy = jest.fn(); + +jest.mock('react-hook-form', () => { + return { + ...jest.requireActual('react-hook-form'), + useFormContext: () => { + const { useFormContext } = jest.requireActual('react-hook-form'); + const originals = useFormContext(); + return { + ...originals, + setError: (...args: [string, { message: string }]) => setErrorSpy(...args), + reset: (...args: [string, { message: string }]) => resetSpy(...args), + }; + }, + }; +}); + +const HookWrapper = ({ children }: { children: ReactNode }) => { + const store = setupStore(); + return ( + + + {children} + + + ); +}; + +const setup = () => { + const { result } = renderHook(useVerifyBoardEffect, { wrapper: HookWrapper }); + + return { result }; }; describe('use verify board state', () => { + beforeEach(() => { + setErrorSpy.mockClear(); + resetSpy.mockClear(); + }); afterAll(() => { jest.clearAllMocks(); }); it('should got initial data state when hook render given none input', async () => { - const { result } = renderHook(() => useVerifyBoardEffect()); + const { result } = setup(); expect(result.current.isLoading).toBe(false); expect(result.current.fields.length).toBe(5); }); - it('should got email and token fields error message when call verify function given a invalid token', async () => { - const mockedError = new UnauthorizedError('', HttpStatusCode.Unauthorized, ''); - boardClient.getVerifyBoard = jest.fn().mockImplementation(() => Promise.reject(mockedError)); - - const { result } = renderHook(() => useVerifyBoardEffect()); - await act(async () => { - await updateFields(result); - await result.current.verifyJira(); - }); - - const emailFiled = result.current.fields.find((field) => field.key === 'Email'); - const tokenField = result.current.fields.find((field) => field.key === 'Token'); - expect(emailFiled?.verifiedError).toBe('Email is incorrect!'); - expect(tokenField?.verifiedError).toBe( - 'Token is invalid, please change your token with correct access permission!', + it('should keep verified values when call verify function given a valid token', async () => { + const mockedOkResponse = { + response: 'ok', + }; + boardClient.getVerifyBoard = jest.fn().mockImplementation(() => Promise.resolve(mockedOkResponse)); + + const { result } = setup(); + await result.current.verifyJira(); + + expect(resetSpy).toHaveBeenCalledWith( + { + type: 'Jira', + boardId: '', + email: '', + site: '', + token: '', + }, + { keepValues: true }, ); }); - it('should clear email validatedError when updateField by Email given fetch error ', async () => { + it('should got email and token fields error message when call verify function given a invalid token', async () => { const mockedError = new UnauthorizedError('', HttpStatusCode.Unauthorized, ''); boardClient.getVerifyBoard = jest.fn().mockImplementation(() => Promise.reject(mockedError)); - const { result } = renderHook(() => useVerifyBoardEffect()); - await act(async () => { - await updateFields(result); - await result.current.verifyJira(); - }); + const { result } = setup(); + await result.current.verifyJira(); - const emailFiled = result.current.fields.find((field) => field.key === 'Email'); - expect(emailFiled?.verifiedError).toBe('Email is incorrect!'); - - await act(async () => { - await result.current.updateField('Email', 'fake@qq.com'); + expect(setErrorSpy).toHaveBeenCalledWith('email', { message: 'Email is incorrect!' }); + expect(setErrorSpy).toHaveBeenCalledWith('token', { + message: 'Token is invalid, please change your token with correct access permission!', }); - const emailText = result.current.fields.find((field) => field.key === 'Email'); - expect(emailText?.verifiedError).toBe(''); }); it('should got site field error message when call verify function given a invalid site', async () => { const mockedError = new NotFoundError('site is incorrect', HttpStatusCode.NotFound, 'site is incorrect'); boardClient.getVerifyBoard = jest.fn().mockImplementation(() => Promise.reject(mockedError)); - const { result } = renderHook(() => useVerifyBoardEffect()); - await act(async () => { - await updateFields(result); - await result.current.verifyJira(); - }); - - await waitFor(() => { - const site = result.current.fields.find((field) => field.key === 'Site'); + const { result } = setup(); + await result.current.verifyJira(); - expect(site?.verifiedError).toBe('Site is incorrect!'); - }); + expect(setErrorSpy).toHaveBeenCalledWith('site', { message: 'Site is incorrect!' }); }); it('should got board id field error message when call verify function given a invalid board id', async () => { const mockedError = new NotFoundError('boardId is incorrect', HttpStatusCode.NotFound, 'boardId is incorrect'); boardClient.getVerifyBoard = jest.fn().mockImplementation(() => Promise.reject(mockedError)); - const { result } = renderHook(() => useVerifyBoardEffect()); - await act(() => { - updateFields(result); - result.current.verifyJira(); - }); + const { result } = setup(); + await result.current.verifyJira(); - await waitFor(() => { - const boardId = result.current.fields.find((field) => field.key === 'Board Id'); - expect(boardId?.verifiedError).toBe('Board Id is incorrect!'); - }); + expect(setErrorSpy).toHaveBeenCalledWith('boardId', { message: 'Board Id is incorrect!' }); }); it('should got token fields error message when call verify function given a unknown error', async () => { const mockedError = new InternalServerError('', HttpStatusCode.ServiceUnavailable, ''); boardClient.getVerifyBoard = jest.fn().mockImplementation(() => Promise.reject(mockedError)); - const { result } = renderHook(() => useVerifyBoardEffect()); - await act(async () => { - await updateFields(result); - await result.current.verifyJira(); - }); + const { result } = setup(); + await result.current.verifyJira(); - const tokenField = result.current.fields.find((field) => field.key === 'Token'); - expect(tokenField?.verifiedError).toBe('Unknown error'); + expect(setErrorSpy).toHaveBeenCalledWith('token', { message: 'Unknown error' }); }); - it('should clear all verified error messages when update a verified error field', async () => { - const mockedError = new UnauthorizedError('', HttpStatusCode.Unauthorized, ''); + it('should set timeout is true given getVerifyBoard api is timeout', async () => { + const mockedError = new TimeoutError('', AXIOS_REQUEST_ERROR_CODE.TIMEOUT); boardClient.getVerifyBoard = jest.fn().mockImplementation(() => Promise.reject(mockedError)); - const { result } = renderHook(() => useVerifyBoardEffect()); - await act(() => { - updateFields(result); - result.current.verifyJira(); - }); - await waitFor(() => { - result.current.updateField('Token', 'fake-token-new'); - }); + const { result } = setup(); + await result.current.verifyJira(); - const emailFiled = result.current.fields.find((field) => field.key === 'Email'); - const tokenField = result.current.fields.find((field) => field.key === 'Token'); - expect(emailFiled?.verifiedError).toBe(''); - expect(tokenField?.verifiedError).toBe(''); + expect(setErrorSpy).toHaveBeenCalledWith('token', { message: 'Timeout!' }); }); - it('should set timeout is true given getVerifyBoard api is timeout', async () => { - const mockedError = new TimeoutError('', AXIOS_REQUEST_ERROR_CODE.TIMEOUT); - boardClient.getVerifyBoard = jest.fn().mockImplementation(() => Promise.reject(mockedError)); + it('should clear all verified error messages when call resetFeilds', async () => { + const { result } = setup(); - const { result } = renderHook(() => useVerifyBoardEffect()); - await act(() => { - result.current.verifyJira(); - }); + result.current.resetFields(); - await waitFor(() => { - const isVerifyTimeOut = result.current.isVerifyTimeOut; - expect(isVerifyTimeOut).toBe(true); + expect(resetSpy).toHaveBeenCalledWith({ + type: 'Jira', + boardId: '', + email: '', + site: '', + token: '', }); }); }); diff --git a/frontend/__tests__/hooks/useVerifyPipelineToolEffect.test.tsx b/frontend/__tests__/hooks/useVerifyPipelineToolEffect.test.tsx index f8e8bd4da1..d379a3a39b 100644 --- a/frontend/__tests__/hooks/useVerifyPipelineToolEffect.test.tsx +++ b/frontend/__tests__/hooks/useVerifyPipelineToolEffect.test.tsx @@ -1,87 +1,136 @@ import { MOCK_PIPELINE_VERIFY_FORBIDDEN_ERROR_TEXT, - MOCK_PIPELINE_VERIFY_REQUEST_PARAMS, MOCK_PIPELINE_VERIFY_UNAUTHORIZED_TEXT, + UNKNOWN_ERROR_TEXT, } from '../fixtures'; +import { pipelineToolDefaultValues } from '@src/containers/ConfigStep/Form/useDefaultValues'; import { useVerifyPipelineToolEffect } from '@src/hooks/useVerifyPipelineToolEffect'; import { pipelineToolClient } from '@src/clients/pipeline/PipelineToolClient'; +import { pipelineToolSchema } from '@src/containers/ConfigStep/Form/schema'; import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; -import { act, renderHook, waitFor } from '@testing-library/react'; +import { FormProvider } from '@test/utils/FormProvider'; +import { setupStore } from '../utils/setupStoreUtil'; +import { renderHook } from '@testing-library/react'; +import { Provider } from 'react-redux'; import { HttpStatusCode } from 'axios'; +import { ReactNode } from 'react'; + +const setErrorSpy = jest.fn(); +const resetSpy = jest.fn(); + +jest.mock('react-hook-form', () => { + return { + ...jest.requireActual('react-hook-form'), + useFormContext: () => { + const { useFormContext } = jest.requireActual('react-hook-form'); + const originals = useFormContext(); + return { + ...originals, + setError: (...args: [string, { message: string }]) => { + setErrorSpy(...args); + }, + reset: (...args: [string, { message: string }]) => resetSpy(...args), + }; + }, + }; +}); -const mockDispatch = jest.fn(); -jest.mock('react-redux', () => ({ - ...jest.requireActual('react-redux'), - useDispatch: () => mockDispatch, -})); +const HookWrapper = ({ children }: { children: ReactNode }) => { + const store = setupStore(); -describe('use verify pipelineTool state', () => { - it('should return empty error message when call verify feature given client returns 204', async () => { - pipelineToolClient.verify = jest.fn().mockResolvedValue({ - code: HttpStatusCode.NoContent, - }); + return ( + + + {children} + + + ); +}; - const { result } = renderHook(() => useVerifyPipelineToolEffect()); +const setup = () => { + const { result } = renderHook(useVerifyPipelineToolEffect, { wrapper: HookWrapper }); - act(() => { - result.current.verifyPipelineTool(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS); - }); + return { result }; +}; - await waitFor(() => { - expect(result.current.verifiedError).toEqual(''); - expect(result.current.isLoading).toEqual(false); - }); +describe('use verify pipelineTool state', () => { + beforeEach(() => { + setErrorSpy.mockClear(); + resetSpy.mockClear(); }); - - it('should set error message when verifying pipeline given response status 401', async () => { - pipelineToolClient.verify = jest.fn().mockResolvedValue({ - code: HttpStatusCode.Unauthorized, - errorTitle: MOCK_PIPELINE_VERIFY_UNAUTHORIZED_TEXT, - }); - - const { result } = renderHook(() => useVerifyPipelineToolEffect()); - - act(() => { - result.current.verifyPipelineTool(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS); - }); - - await waitFor(() => { - expect(result.current.verifiedError).toEqual(MOCK_PIPELINE_VERIFY_UNAUTHORIZED_TEXT); - }); + afterAll(() => { + jest.clearAllMocks(); }); - - it('should clear error message when explicitly call clear function given error message exists', async () => { - pipelineToolClient.verify = jest - .fn() - .mockResolvedValue({ code: HttpStatusCode.Forbidden, errorTitle: MOCK_PIPELINE_VERIFY_FORBIDDEN_ERROR_TEXT }); - const { result } = renderHook(() => useVerifyPipelineToolEffect()); - - act(() => { - result.current.verifyPipelineTool(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS); - }); - - await waitFor(() => { - expect(result.current.verifiedError).toEqual(MOCK_PIPELINE_VERIFY_FORBIDDEN_ERROR_TEXT); + it('should keep verified values when call verify feature given client returns 204', async () => { + pipelineToolClient.verify = jest.fn().mockResolvedValue({ + code: HttpStatusCode.NoContent, }); - result.current.clearVerifiedError(); + const { result } = setup(); + await result.current.verifyPipelineTool(); - await waitFor(() => { - expect(result.current.verifiedError).toEqual(''); - }); + expect(resetSpy).toHaveBeenCalledWith({ type: 'BuildKite', token: '' }, { keepValues: true }); }); - it('should set timeout is true when verify api is timeout', async () => { - pipelineToolClient.verify = jest.fn().mockResolvedValue({ code: AXIOS_REQUEST_ERROR_CODE.TIMEOUT }); - - const { result } = renderHook(() => useVerifyPipelineToolEffect()); - await act(() => { - result.current.verifyPipelineTool(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS); - }); - - await waitFor(() => { - const isVerifyTimeOut = result.current.isVerifyTimeOut; - expect(isVerifyTimeOut).toBe(true); + const errorScenarios = [ + { + mock: { + code: HttpStatusCode.Unauthorized, + errorTitle: MOCK_PIPELINE_VERIFY_UNAUTHORIZED_TEXT, + }, + field: 'token', + status: '401', + message: 'Token is incorrect!', + }, + { + mock: { + code: HttpStatusCode.Forbidden, + errorTitle: MOCK_PIPELINE_VERIFY_FORBIDDEN_ERROR_TEXT, + }, + field: 'token', + status: '403', + message: 'Forbidden request, please change your token with correct access permission.', + }, + { + mock: { + code: HttpStatusCode.ServiceUnavailable, + errorTitle: UNKNOWN_ERROR_TEXT, + }, + field: 'token', + status: 'Unknown', + message: 'Unknown error', + }, + { + mock: { + code: AXIOS_REQUEST_ERROR_CODE.TIMEOUT, + errorTitle: '', + }, + field: 'token', + status: 'Timeout', + message: 'Timeout!', + }, + ]; + + it.each(errorScenarios)( + 'should set $field error message when verifying pipeline given response status', + async ({ mock, field, message }) => { + pipelineToolClient.verify = jest.fn().mockResolvedValue(mock); + + const { result } = setup(); + await result.current.verifyPipelineTool(); + + expect(setErrorSpy).toHaveBeenCalledWith(field, { message }); + }, + ); + + it('should clear all verified error messages when call resetFeilds', async () => { + const { result } = setup(); + + result.current.resetFields(); + + expect(resetSpy).toHaveBeenCalledWith({ + type: 'BuildKite', + token: '', }); }); }); diff --git a/frontend/__tests__/hooks/useVerifySourceControlTokenEffect.test.tsx b/frontend/__tests__/hooks/useVerifySourceControlTokenEffect.test.tsx index 21951c67e7..4602088f6c 100644 --- a/frontend/__tests__/hooks/useVerifySourceControlTokenEffect.test.tsx +++ b/frontend/__tests__/hooks/useVerifySourceControlTokenEffect.test.tsx @@ -1,94 +1,119 @@ -import { MOCK_SOURCE_CONTROL_VERIFY_ERROR_CASE_TEXT, MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS } from '../fixtures'; import { useVerifySourceControlTokenEffect } from '@src/hooks/useVerifySourceControlTokenEffect'; +import { sourceControlDefaultValues } from '@src/containers/ConfigStep/Form/useDefaultValues'; +import { MOCK_PIPELINE_VERIFY_UNAUTHORIZED_TEXT, UNKNOWN_ERROR_TEXT } from '../fixtures'; import { sourceControlClient } from '@src/clients/sourceControl/SourceControlClient'; -import { ContextProvider } from '@src/hooks/useMetricsStepValidationCheckContext'; +import { sourceControlSchema } from '@src/containers/ConfigStep/Form/schema'; import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; -import { act, renderHook, waitFor } from '@testing-library/react'; +import { FormProvider } from '@test/utils/FormProvider'; import { setupStore } from '../utils/setupStoreUtil'; +import { renderHook } from '@testing-library/react'; import { Provider } from 'react-redux'; import { HttpStatusCode } from 'axios'; -import React from 'react'; +import { ReactNode } from 'react'; + +const setErrorSpy = jest.fn(); +const resetSpy = jest.fn(); + +jest.mock('react-hook-form', () => { + return { + ...jest.requireActual('react-hook-form'), + useFormContext: () => { + const { useFormContext } = jest.requireActual('react-hook-form'); + const originals = useFormContext(); + return { + ...originals, + setError: (...args: [string, { message: string }]) => setErrorSpy(...args), + reset: (...args: [string, { message: string }]) => resetSpy(...args), + }; + }, + }; +}); + +const HookWrapper = ({ children }: { children: ReactNode }) => { + const store = setupStore(); + return ( + + + {children} + + + ); +}; describe('use verify sourceControl token', () => { const setup = () => { - const store = setupStore(); - const wrapper = ({ children }: { children: React.ReactNode }) => ( - - {children} - - ); - const { result } = renderHook(() => useVerifySourceControlTokenEffect(), { wrapper }); + const { result } = renderHook(useVerifySourceControlTokenEffect, { wrapper: HookWrapper }); return { result }; }; - it('should initial data state when render hook', async () => { + it('should keep verified values when call verify function given a valid token', async () => { const { result } = setup(); - - expect(result.current.isLoading).toEqual(false); - }); - - it('should set error message when get verify sourceControl throw error', async () => { sourceControlClient.verifyToken = jest.fn().mockResolvedValue({ code: HttpStatusCode.NoContent, }); - const { result } = setup(); - - act(() => { - result.current.verifyToken(MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS); - }); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - }); - await waitFor(() => expect(result.current.verifiedError).toBeUndefined()); - }); - it('should set error message when get verify sourceControl response status 401', async () => { - sourceControlClient.verifyToken = jest.fn().mockResolvedValue({ - code: HttpStatusCode.Unauthorized, - errorTitle: MOCK_SOURCE_CONTROL_VERIFY_ERROR_CASE_TEXT, - }); - const { result } = setup(); + await result.current.verifyToken(); - act(() => { - result.current.verifyToken(MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS); - }); - - await waitFor(() => { - expect(result.current.isLoading).toEqual(false); - }); - await waitFor(() => { - expect(result.current.verifiedError).toEqual(MOCK_SOURCE_CONTROL_VERIFY_ERROR_CASE_TEXT); - }); - }); - - it('should clear error message when call clearErrorMessage', async () => { - sourceControlClient.verifyToken = jest.fn().mockResolvedValue({ - code: HttpStatusCode.Unauthorized, - errorTitle: MOCK_SOURCE_CONTROL_VERIFY_ERROR_CASE_TEXT, - }); - const { result } = setup(); - - await act(() => result.current.verifyToken(MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS)); - await act(() => result.current.clearVerifiedError()); - - await waitFor(() => { - expect(result.current.verifiedError).toEqual(''); - }); + expect(resetSpy).toHaveBeenCalledWith( + { + type: 'GitHub', + token: '', + }, + { keepValues: true }, + ); }); - it('should isVerifyTimeOut and isShowAlert is true when api timeout', async () => { - sourceControlClient.verifyToken = jest.fn().mockResolvedValue({ - code: AXIOS_REQUEST_ERROR_CODE.TIMEOUT, - }); + const errorScenarios = [ + { + mock: { + code: HttpStatusCode.Unauthorized, + errorTitle: MOCK_PIPELINE_VERIFY_UNAUTHORIZED_TEXT, + }, + field: 'token', + status: '401', + message: 'Token is incorrect!', + }, + { + mock: { + code: HttpStatusCode.ServiceUnavailable, + errorTitle: UNKNOWN_ERROR_TEXT, + }, + field: 'token', + status: 'Unknown', + message: 'Unknown error', + }, + { + mock: { + code: AXIOS_REQUEST_ERROR_CODE.TIMEOUT, + errorTitle: '', + }, + field: 'token', + status: 'Timeout', + message: 'Timeout!', + }, + ]; + + it.each(errorScenarios)( + 'should set $field error message when verifying pipeline given response status', + async ({ mock, field, message }) => { + sourceControlClient.verifyToken = jest.fn().mockResolvedValue(mock); + + const { result } = setup(); + await result.current.verifyToken(); + + expect(setErrorSpy).toHaveBeenCalledWith(field, { message }); + }, + ); + + it('should clear all verified error messages when call resetFeilds', async () => { const { result } = setup(); - await act(() => result.current.verifyToken(MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS)); + result.current.resetFields(); - await waitFor(() => { - expect(result.current.isVerifyTimeOut).toBeTruthy(); - expect(result.current.isShowAlert).toBeTruthy(); + expect(resetSpy).toHaveBeenCalledWith({ + type: 'GitHub', + token: '', }); }); }); diff --git a/frontend/__tests__/initialConfigState.ts b/frontend/__tests__/initialConfigState.ts index dd04ea4754..d71e47f4a3 100644 --- a/frontend/__tests__/initialConfigState.ts +++ b/frontend/__tests__/initialConfigState.ts @@ -1,4 +1,5 @@ import { BOARD_TYPES, PIPELINE_TOOL_TYPES, REGULAR_CALENDAR } from './fixtures'; +import { SortType } from '@src/containers/ConfigStep/DateRangePicker/types'; import { BasicConfigState } from '@src/context/config/configSlice'; import { SOURCE_CONTROL_TYPES } from '@src/constants/resources'; @@ -13,6 +14,7 @@ const initialConfigState: BasicConfigState = { endDate: null, }, ], + sortType: SortType.DEFAULT, metrics: [], }, board: { @@ -24,7 +26,6 @@ const initialConfigState: BasicConfigState = { site: '', token: '', }, - isVerified: false, isShow: false, verifiedResponse: { jiraColumns: [], @@ -37,11 +38,9 @@ const initialConfigState: BasicConfigState = { type: PIPELINE_TOOL_TYPES.BUILD_KITE, token: '', }, - isVerified: false, isShow: false, verifiedResponse: { pipelineList: [], - pipelineCrews: [], }, }, sourceControl: { @@ -49,7 +48,6 @@ const initialConfigState: BasicConfigState = { type: SOURCE_CONTROL_TYPES.GITHUB, token: '', }, - isVerified: false, isShow: false, verifiedResponse: { repoList: [], diff --git a/frontend/__tests__/updatedConfigState.ts b/frontend/__tests__/updatedConfigState.ts index c76d6819ea..e359353db6 100644 --- a/frontend/__tests__/updatedConfigState.ts +++ b/frontend/__tests__/updatedConfigState.ts @@ -21,7 +21,6 @@ const updatedConfigState = { site: '', token: '', }, - isVerified: false, isShow: false, verifiedResponse: { jiraColumns: [], @@ -34,7 +33,6 @@ const updatedConfigState = { type: PIPELINE_TOOL_TYPES.BUILD_KITE, token: '', }, - isVerified: false, isShow: false, verifiedResponse: { pipelineList: [], @@ -45,7 +43,6 @@ const updatedConfigState = { type: SOURCE_CONTROL_TYPES.GITHUB, token: '', }, - isVerified: false, isShow: false, verifiedResponse: { repoList: [], diff --git a/frontend/__tests__/utils/FormProvider.tsx b/frontend/__tests__/utils/FormProvider.tsx new file mode 100644 index 0000000000..e4afe55d4f --- /dev/null +++ b/frontend/__tests__/utils/FormProvider.tsx @@ -0,0 +1,20 @@ +import { useForm, FormProvider as RHFProvider } from 'react-hook-form'; +import { InferType, AnySchema, ObjectSchema } from 'yup'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { ReactNode } from 'react'; + +interface IFormProviderProps { + children: ReactNode; + defaultValues: InferType; + schema: T; +} + +export const FormProvider = ({ defaultValues, children, schema }: IFormProviderProps>) => { + const formMethods = useForm>({ + defaultValues, + resolver: yupResolver(schema), + mode: 'onChange', + }); + + return {children}; +}; diff --git a/frontend/__tests__/utils/Util.test.tsx b/frontend/__tests__/utils/Util.test.tsx index 87a04b3dd6..6d08f05895 100644 --- a/frontend/__tests__/utils/Util.test.tsx +++ b/frontend/__tests__/utils/Util.test.tsx @@ -1,4 +1,5 @@ import { + combineBoardInfo, convertCycleTimeSettings, exportToJsonFile, filterAndMapCycleTimeSettings, @@ -13,10 +14,13 @@ import { sortDateRanges, sortDisabledOptions, transformToCleanedBuildKiteEmoji, + updateResponseCrews, } from '@src/utils/util'; import { CleanedBuildKiteEmoji, OriginBuildKiteEmoji } from '@src/constants/emojis/emoji'; import { CYCLE_TIME_SETTINGS_TYPES, METRICS_CONSTANTS } from '@src/constants/resources'; import { ICycleTimeSetting, IPipelineConfig } from '@src/context/Metrics/metricsSlice'; +import { IPipeline } from '@src/context/config/pipelineTool/verifyResponseSlice'; +import { BoardInfoResponse } from '@src/hooks/useGetBoardInfo'; import { EMPTY_STRING } from '@src/constants/commons'; import { PIPELINE_TOOL_TYPES } from '../fixtures'; @@ -429,3 +433,175 @@ describe('sortDateRanges function', () => { expect(sortedDateRanges).toStrictEqual(expectResult.reverse()); }); }); + +describe('combineBoardInfo function', () => { + const boardInfoResponses: BoardInfoResponse[] = [ + { + ignoredTargetFields: [ + { + key: 'description', + name: 'Description', + flag: 'false', + }, + { + key: 'customfield_10015', + name: 'Start date', + flag: 'false', + }, + ], + jiraColumns: [ + { + key: 'To Do', + value: '{ name: TODO, statuses: [TODO]}', + }, + { + key: 'In Progress', + value: '{ name: DOING, statuses: [DOING]}', + }, + ], + targetFields: [ + { + key: 'issuetype', + name: 'Issue Type', + flag: 'false', + }, + { + key: 'parent', + name: 'Parent', + flag: 'false', + }, + ], + users: ['heartbeat user', 'Yunsong Yang'], + }, + { + ignoredTargetFields: [ + { + key: 'description', + name: 'Description', + flag: 'false', + }, + { + key: 'customfield_10015', + name: 'Start date', + flag: 'false', + }, + ], + jiraColumns: [ + { + key: 'To Do', + value: '{ name: TODO, statuses: [TODO]}', + }, + { + key: 'In Progress', + value: '{ name: DOING, statuses: [DOING]}', + }, + ], + targetFields: [ + { + key: 'issuetype', + name: 'Issue Type', + flag: 'false', + }, + { + key: 'parent', + name: 'Parent', + flag: 'false', + }, + ], + users: [ + 'heartbeat user', + 'Yunsong Yang', + 'Yufan Wang', + 'Weiran Sun', + 'Xuebing Li', + 'Junbo Dai', + 'Wenting Yan', + 'Xingmeng Tao', + ], + }, + ]; + const expectResults = { + ignoredTargetFields: [ + { + key: 'description', + name: 'Description', + flag: 'false', + }, + { + key: 'customfield_10015', + name: 'Start date', + flag: 'false', + }, + ], + jiraColumns: [ + { + key: 'To Do', + value: '{ name: TODO, statuses: [TODO]}', + }, + { + key: 'In Progress', + value: '{ name: DOING, statuses: [DOING]}', + }, + ], + targetFields: [ + { + key: 'issuetype', + name: 'Issue Type', + flag: 'false', + }, + { + key: 'parent', + name: 'Parent', + flag: 'false', + }, + ], + users: [ + 'heartbeat user', + 'Yunsong Yang', + 'Yufan Wang', + 'Weiran Sun', + 'Xuebing Li', + 'Junbo Dai', + 'Wenting Yan', + 'Xingmeng Tao', + ], + }; + + it('should combine board info', () => { + const combineBoardData = combineBoardInfo(boardInfoResponses); + expect(combineBoardData).toStrictEqual(expectResults); + }); +}); + +describe('updateResponseCrews function', () => { + const mockData = { + id: '0', + name: 'pipelineName', + orgId: '', + orgName: 'orgName', + repository: '', + steps: [] as string[], + branches: [] as string[], + crews: ['a', 'b', 'c'], + } as IPipeline; + it('should update crews when pipelineName and org both matched', () => { + const expectData = [ + { + ...mockData, + crews: [], + }, + ]; + const result = updateResponseCrews('orgName', 'pipelineName', [mockData]); + expect(result).toEqual(expectData); + }); + + it('should not update crews when pipelineName or org not matched', () => { + const expectData = [ + { + ...mockData, + }, + ]; + const result = updateResponseCrews('xxx', 'xxx', [mockData]); + expect(result).toEqual(expectData); + }); +}); diff --git a/frontend/e2e/fixtures/create-new/board-data-without-block-column.csv b/frontend/e2e/fixtures/create-new/board-data-without-block-column.csv new file mode 100644 index 0000000000..4e27e05de3 --- /dev/null +++ b/frontend/e2e/fixtures/create-new/board-data-without-block-column.csv @@ -0,0 +1,4 @@ +"Issue key","Summary","Issue Type","Status","Status Date","Story Points","assignee","Reporter","Project Key","Project Name","Priority","Parent Summary","Sprint","Labels","Cycle Time","Cycle Time / Story Points","Analysis Days","In Dev Days","Waiting Days","Testing Days","Block Days","Review Days","OriginCycleTime: TESTING","OriginCycleTime: WAITING FOR TESTING","OriginCycleTime: TO DO","OriginCycleTime: IN DEV","OriginCycleTime: REVIEW","OriginCycleTime: FLAG","Rework: total - In dev","Rework: from Block","Rework: from Waiting for testing","Rework: from Done" +"TNB-1","ADM-898-card1","Task","Done","2024-04-08","0.0","Shiqi Yuan","heartbeat user","TNB","Test-no-block","Medium",,,"","0","","0","0","0","0","0","0","0","0","0","0.01","0","0.01","1","1","0","0" +"TNB-2","ADM-898-card2","Task","Done","2024-04-08","0.0","Shiqi Yuan","heartbeat user","TNB","Test-no-block","Medium",,,"","0","","0","0","0","0","0","0","0","0","0","0.01","0","0.01","2","1","0","1" +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, diff --git a/frontend/e2e/fixtures/create-new/board-data.csv b/frontend/e2e/fixtures/create-new/board-data.csv index 9cce0e14a2..6cf9b22a98 100644 --- a/frontend/e2e/fixtures/create-new/board-data.csv +++ b/frontend/e2e/fixtures/create-new/board-data.csv @@ -1,24 +1,22 @@ -"Issue key","Summary","Issue Type","Status","Status Date","Story Points","assignee","Reporter","Project Key","Project Name","Priority","Parent Summary","Sprint","Labels","Cycle Time","Story testing-1","Flagged","Fix versions","Partner","Time tracking","Story point estimate","QA","Feature/Operation","Story testing-2","Cycle Time / Story Points","Analysis Days","In Dev Days","Waiting Days","Testing Days","Block Days","Review Days","OriginCycleTime: TODO","OriginCycleTime: TESTING","OriginCycleTime: WAIT FOR TEST","OriginCycleTime: DOING","OriginCycleTime: REVIEW","OriginCycleTime: FLAG","OriginCycleTime: BLOCKED",Rework: total - In dev,Rework: from Block,Rework: from Review,Rework: from Waiting for testing,Rework: from Testing,Rework: from Done -"ADM-735","[backend]identify the source of the error when generate reports encounter exception","Task","Done","2024-01-19","1.0","Yunsong Yang","Yunsong Yang","ADM","Auto Dora Metrics","Medium","Precise on Metrics","Sprint 28","Stream2","7.70","1.0","","","","None","1.0","","","","7.70","0","2.02","1.81","0","0","3.87","3.03","0","1.81","2.02","3.87","0","0","0","0","0","0","0","0" -"ADM-708","[Backend] Verify board and obtain board data with new API","Task","Done","2024-01-19","3.0","Weiran Sun","heartbeat user","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream1","9.95","1.0","","","","None","3.0","","","","3.32","0","4.00","0.93","1.04","0.98","3.00","7.10","1.04","0.93","4.00","3.00","0","0.98","2","2","0","0","0","0" -"ADM-699","[Frontend] Optimize the 4xx&504 error display of report overview","Task","Done","2024-01-18","2.0","heartbeat user","heartbeat user","ADM","Auto Dora Metrics","Medium","Performance Improvement","Sprint 28","Stream2","10.93","1.0","","","","None","2.0","","","","5.46","0","5.14","0.04","0.78","2.01","2.96","10.75","0.78","0.04","5.14","2.96","0","2.01","2","2","0","0","0","0" -"ADM-717","[Backend] Verify github and obtain github data with new API","Task","Done","2024-01-17","2.0","Junbo Dai","Yufan Wang","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream1","8.09","1.0","","","","None","2.0","Weiran Sun","","","4.04","0","2.83","2.72","0.05","2.14","0.35","6.00","0.05","2.72","2.83","0.35","0","2.14","3","3","0","0","0","0" -"ADM-724","[Spike] redesign board verify API to meet business requirements","Spike","Done","2024-01-17","1.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream1","12.94","","","","","None","1.0","","","","12.94","0","1.08","1.99","0","7.65","2.22","0.27","0","1.99","1.08","2.22","0","7.65","2","2","0","0","0","0" -"ADM-652","[Frontend]Generate the separate modules detail report","Task","Done","2024-01-17","3.0","Xuebing Li","heartbeat user","ADM","Auto Dora Metrics","Medium","Performance Improvement","Sprint 28","Stream2","10.15","1.0","","","","None","3.0","","","","3.38","0","5.94","1.35","1.87","0.72","0.27","22.87","1.87","1.35","5.94","0.27","0","0.72","1","1","0","0","0","0" -"ADM-683","[Frontend] UI refine for the date picker in report page","Task","Done","2024-01-17","1.0","heartbeat user","heartbeat user","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream2","8.92","1.0","","","","None","1.0","","","","8.92","0","3.00","0.10","1.84","3.00","0.98","15.05","1.84","0.10","3.00","0.98","0","3.00","1","1","0","0","0","0" -"ADM-669","[Frontend] UI refine for notification pop up change in report page","Task","Done","2024-01-17","1.0","heartbeat user","heartbeat user","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream2","7.13","1.0","","","","None","1.0","","","","7.13","0","4.22","0.02","1.16","0","1.73","17.80","1.16","0.02","4.22","1.73","0","0","0","0","0","0","0","0" -"ADM-709","[Backend] Verify buildkite and obtain buildkite data with new API","Task","Done","2024-01-15","3.0","Xinyi Wang","heartbeat user","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 27","Stream1","6.85","1.0","","","","None","3.0","","","","2.28","0","2.81","0.07","0.78","0","3.19","8.03","0.78","0.07","2.81","3.19","0","0",,,,,, -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -"ADM-806","[BE]no need to obtain pipeline data twice in backend","Bug","Review","2024-02-26","2.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream2,v1.1.5","0","","","","","None","2.0","","","","0","0","2.89","0","0","0","0.04","8.10","0","0","2.89","0.04","0","0",,,,,, -"ADM-813","[FE]add new field 'Advance' in metrics page","Task","Review","2024-02-26","2.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream2,v1.1.5","0","1.0","","","","None","2.0","","","","0","0","4.78","0","0","4.68","0.53","0.19","0","0","4.78","0.53","0","4.68",,,,,, -"ADM-677","[Spike]Investigate Github graphQL API about replacing existing REST API","Spike","Blocked","2024-02-21","2.0","Junbo Dai","Yichen Wang","ADM","Auto Dora Metrics","Medium","Performance Improvement","Sprint 30","Stream1","0","","","","","None","2.0","","","","0","0","1.05","0","0","10.17","0","38.43","0","0","1.05","0","0","10.17",,,,,, -"ADM-819","[BE]cache doesn't work in one case","Bug","Doing","2024-02-26","2.0","Shiqi Yuan","Yufan Wang","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream2,v1.1.5","0","","","","","None","2.0","","","","0","0","3.14","0","0","1.05","0","0.84","0","0","3.14","0","0","1.05",,,,,, -"ADM-797","[BE]The add flag as block logic is not working","Bug","Doing","2024-02-26","2.0","heartbeat user","Wenting Yan","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream2,v1.1.5","0","","","","","None","2.0","","","","0","0","7.13","0","0","5.00","0","2.03","0","0","7.38","0","1.05","5.80",,,,,, -"ADM-829","jump home page when user click next button in config page","Bug","Doing","2024-02-23","2.0","Junbo Dai","Yufan Wang","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream1,v1.1.5","0","","","","","None","2.0","","","","0","0","1.03","0","0","0","0","1.17","0","0","1.03","0","0","0",,,,,, -"ADM-812","[FE]metrics page needs to retain the modified data","Bug","Doing","2024-02-23","2.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream1","0","","","","","None","2.0","","","","0","0","3.04","0","0","1.03","0","6.67","0","0","3.04","0","0","1.03",,,,,, -"ADM-809","[E2E] build ""import a new project"" scenario","Task","Doing","2024-02-22","2.0","heartbeat user","Xingmeng Tao","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream1,v1.1.5","0","1.0","","","","None","2.0","","","","0","0","2.24","0","0","0","0","8.00","0","0","2.24","0","0","0",,,,,, -"ADM-808","[E2E] build ""Create a new Project"" scenario","Task","Doing","2024-02-19","3.5","heartbeat user","Xingmeng Tao","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream1,v1.1.5","0","1.0","","","","None","3.5","","","","0","0","8.04","0","0","1.99","0","0.95","0","0","8.04","0","0","1.99",,,,,, -"ADM-825","[E2E] build ""page jumps"" scenario","Task","TODO",,"2.0",,"Yufan Wang","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream1,v1.1.5","0","1.0","","","","None","2.0","","","","0","0","0","0","0","0","0","0","0","0","0","0","0","0",,,,,, -"ADM-820","user was misguided to home page when they want to enter metrics page","Bug","TODO",,"0.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream2","0","","","","","None","","","","","","0","0","0","0","0","0","0","0","0","0","0","0","0",,,,,, -"ADM-833","[E2E] build ""unhappy path"" scenario","Task","TODO",,"0.0",,"heartbeat user","ADM","Auto Dora Metrics","Medium",,"Sprint 30","Stream1","0","1.0","","","","None","","","","","","0","0","0","0","0","0","0","0","0","0","0","0","0",,,,,, -"ADM-789","refactor E2E-step2","Task","TODO",,"1.0",,"Yufan Wang","ADM","Auto Dora Metrics","High",,"Sprint 30","Stream2","0","1.0","","","","None","1.0","","","","0","0","0","0","0","0","0","0","0","0","0","0","0","0",,,,,, +"Issue key","Summary","Issue Type","Status","Status Date","Story Points","assignee","Reporter","Project Key","Project Name","Priority","Parent Summary","Sprint","Labels","Cycle Time","Fix versions","Partner","Time tracking","Story testing-1","Flagged","Story point estimate","QA","Feature/Operation","Story testing-2","Cycle Time / Story Points","Analysis Days","In Dev Days","Waiting Days","Testing Days","Block Days","Review Days","OriginCycleTime: TODO","OriginCycleTime: TESTING","OriginCycleTime: WAIT FOR TEST","OriginCycleTime: DOING","OriginCycleTime: REVIEW","OriginCycleTime: BLOCKED","Rework: total - In dev","Rework: from Block","Rework: from Review","Rework: from Waiting for testing","Rework: from Testing","Rework: from Done" +"ADM-735","[backend]identify the source of the error when generate reports encounter exception","Task","Done","2024-01-19","1.0","Yunsong Yang","Yunsong Yang","ADM","Auto Dora Metrics","Medium","Precise on Metrics","Sprint 28","Stream2","7.70","","","None","1.0","","1.0","","","","7.70","0","2.02","1.81","0","0","3.87","3.03","0","1.81","2.02","3.87","0","0","0","0","0","0","0" +"ADM-708","[Backend] Verify board and obtain board data with new API","Task","Done","2024-01-19","3.0","Weiran Sun","heartbeat user","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream1","9.95","","","None","1.0","","3.0","","","","3.32","0","4.00","0.93","1.04","0.98","3.00","7.10","1.04","0.93","4.00","3.00","0.98","2","2","0","0","0","0" +"ADM-699","[Frontend] Optimize the 4xx&504 error display of report overview","Task","Done","2024-01-18","2.0","heartbeat user","heartbeat user","ADM","Auto Dora Metrics","Medium","Performance Improvement","Sprint 28","Stream2","10.93","","","None","1.0","","2.0","","","","5.46","0","5.14","0.04","0.78","2.01","2.96","10.75","0.78","0.04","5.14","2.96","2.01","2","2","0","0","0","0" +"ADM-717","[Backend] Verify github and obtain github data with new API","Task","Done","2024-01-17","2.0","Junbo Dai","Yufan Wang","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream1","8.09","","","None","1.0","","2.0","Weiran Sun","","","4.04","0","2.83","2.72","0.05","2.14","0.35","6.00","0.05","2.72","2.83","0.35","2.14","3","3","0","0","0","0" +"ADM-724","[Spike] redesign board verify API to meet business requirements","Spike","Done","2024-01-17","1.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream1","12.94","","","None","","","1.0","","","","12.94","0","1.08","1.99","0","7.65","2.22","0.27","0","1.99","1.08","2.22","7.65","2","2","0","0","0","0" +"ADM-652","[Frontend]Generate the separate modules detail report","Task","Done","2024-01-17","3.0","Xuebing Li","heartbeat user","ADM","Auto Dora Metrics","Medium","Performance Improvement","Sprint 28","Stream2","10.15","","","None","1.0","","3.0","","","","3.38","0","5.94","1.35","1.87","0.72","0.27","22.87","1.87","1.35","5.94","0.27","0.72","1","1","0","0","0","0" +"ADM-683","[Frontend] UI refine for the date picker in report page","Task","Done","2024-01-17","1.0","heartbeat user","heartbeat user","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream2","8.92","","","None","1.0","","1.0","","","","8.92","0","3.00","0.10","1.84","3.00","0.98","15.05","1.84","0.10","3.00","0.98","3.00","1","1","0","0","0","0" +"ADM-669","[Frontend] UI refine for notification pop up change in report page","Task","Done","2024-01-17","1.0","heartbeat user","heartbeat user","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream2","7.13","","","None","1.0","","1.0","","","","7.13","0","4.22","0.02","1.16","0","1.73","17.80","1.16","0.02","4.22","1.73","0","0","0","0","0","0","0" +"ADM-709","[Backend] Verify buildkite and obtain buildkite data with new API","Task","Done","2024-01-15","3.0","Xinyi Wang","heartbeat user","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 27","Stream1","6.85","","","None","1.0","","3.0","","","","2.28","0","2.81","0.07","0.78","0","3.19","8.03","0.78","0.07","2.81","3.19","0","0","0","0","0","0","0" +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +"ADM-879","[FE]Generate data reports for multiple time ranges","Story","Testing","2024-05-10","3.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium","Charting","Sprint 36","1.1.7","0","","","None","","","3.0","","","","0","0","12.04","4.04","3.75","1.00","0","19.82","3.75","4.04","12.04","0","1.00",,,,,, +"ADM-924","[FE] Export data in report list page","Story","Review","2024-05-15","3.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium","Charting","Sprint 36","1.1.7,Stream2","0","","","None","","","3.0","","","","0","0","12.77","0","0","3.25","3.97","1.15","0","0","12.77","3.97","3.25",,,,,, +"ADM-937","[BE] Pipeline crew setting could filter out dependentbot pipeline","Bug","Blocked","2024-05-15","2.0","heartbeat user","Yichen Wang","ADM","Auto Dora Metrics","Medium","Precise on Metrics","Sprint 36","1.1.7","0","","","None","","","2.0","","","","0","0","2.83","4.02","0","3.05","0.90","2.25","0","4.02","2.83","0.90","3.05",,,,,, +"ADM-881","[FE] Export data in report chart page","Story","Blocked","2024-05-11","1.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium","Charting","Sprint 36","1.1.7","0","","","None","","","1.0","","","","0","0","3.63","0","0","4.18","0","32.84","0","0","3.63","0","4.18",,,,,, +"ADM-907","[FE]update Readme about 'charting'","Task","Blocked","2024-04-28","1.0","Yufan Wang","Yufan Wang","ADM","Auto Dora Metrics","Medium","Charting","Sprint 36","1.1.7,Stream2","0","","","None","1.0","","1.0","","","","0","0","1.81","0","0","11.04","0","17.28","0","0","1.81","0","11.04",,,,,, +"ADM-880","[FE] Generate chart - board metrics","Story","Doing","2024-05-15","3.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Highest","Charting","Sprint 36","1.1.7","0","","","None","","","3.0","","","","0","0","8.26","0","0","0.74","0","31.66","0","0","8.26","0","0.74",,,,,, +"ADM-744","[FE] add pop-up when board token & email are incorrect","Story","Doing","2024-05-14","1.0","Weiran Sun","Yufan Wang","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 36","Stream1","0","","","None","","","1.0","","","","0","0","0.83","0","0","0","0","86.91","0","0","0.83","0","0",,,,,, +"ADM-938","[FE] Add a mark to the failed time range in report page","Story","Doing","2024-05-14","1.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium","Charting","Sprint 36","1.1.7","0","","","None","","","1.0","","","","0","0","3.01","0","0","1.02","0","4.95","0","0","3.01","0","1.02",,,,,, +"ADM-906","[E2E] build 'charting' scenario-happy path","Task","Doing","2024-05-09","3.0","Chao Wang","Yufan Wang","ADM","Auto Dora Metrics","Medium","Charting","Sprint 36","1.1.7","0","","YinYuan Zhou","None","1.0","","3.0","","","","0","0","6.83","0","0","0","0","23.31","0","0","6.83","0","0",,,,,, +"ADM-908","[FE] Generate chart - dora metrics","Story","Doing","2024-05-06","2.0","heartbeat user","Yufan Wang","ADM","Auto Dora Metrics","Medium","Charting","Sprint 36","1.1.7,Stream2","0","","","None","","","2.0","","","","0","0","14.61","0","0","4.16","0","11.36","0","0","14.61","0","4.16",,,,,, +"ADM-945","[FE]optimize the time range settings","Bug","TODO",,"2.0",,"Yufan Wang","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 36","","0","","","None","","","2.0","","","","0","0","0","0","0","0","0","0","0","0","0","0","0",,,,,, diff --git a/frontend/e2e/fixtures/create-new/config-step.ts b/frontend/e2e/fixtures/create-new/config-step.ts index 4333ed331d..df7b1efc67 100644 --- a/frontend/e2e/fixtures/create-new/config-step.ts +++ b/frontend/e2e/fixtures/create-new/config-step.ts @@ -6,6 +6,7 @@ export const config = { endDate: '2024-01-19T23:59:59.999+08:00', }, ], + sortType: 'DEFAULT', calendarType: 'Calendar with Chinese Holiday', metrics: [ 'Velocity', @@ -33,3 +34,23 @@ export const config = { token: process.env.E2E_TOKEN_GITHUB as string, }, }; + +export const configWithoutBlockColumn = { + projectName: 'Heartbeat Metrics', + dateRange: [ + { + startDate: '2024-04-07T00:00:00.000+08:00', + endDate: '2024-04-08T23:59:59.999+08:00', + }, + ], + sortType: 'DEFAULT', + calendarType: 'Calendar with Chinese Holiday', + metrics: ['Cycle time'], + board: { + type: 'Jira', + boardId: '33', + email: 'heartbeatuser2023@gmail.com', + site: 'dorametrics', + token: process.env.E2E_TOKEN_JIRA as string, + }, +}; diff --git a/frontend/e2e/fixtures/create-new/metric-data.csv b/frontend/e2e/fixtures/create-new/metric-data.csv index 7402eda4b6..4fcc230604 100644 --- a/frontend/e2e/fixtures/create-new/metric-data.csv +++ b/frontend/e2e/fixtures/create-new/metric-data.csv @@ -26,17 +26,17 @@ "Classifications","Story testing-2 / None","100.00" "Classifications","Story testing-1 / 1.0","88.89" "Classifications","Story testing-1 / None","11.11" +"Classifications","Project / Auto Dora Metrics","100.00" "Classifications","Sprint / Sprint 26","11.11" "Classifications","Sprint / Sprint 27","100.00" "Classifications","Sprint / Sprint 28","88.89" -"Classifications","Project / Auto Dora Metrics","100.00" "Classifications","Flagged / None","100.00" "Classifications","Fix versions / None","100.00" "Classifications","Priority / Medium","100.00" "Classifications","Partner / None","100.00" +"Classifications","Time tracking / None","100.00" "Classifications","Labels / Stream1","44.44" "Classifications","Labels / Stream2","55.56" -"Classifications","Time tracking / None","100.00" "Classifications","Story point estimate / 1.0","44.44" "Classifications","Story point estimate / 2.0","22.22" "Classifications","Story point estimate / 3.0","33.33" diff --git a/frontend/e2e/fixtures/create-new/metrics-step.ts b/frontend/e2e/fixtures/create-new/metrics-step.ts index 9420861234..9d916dab9b 100644 --- a/frontend/e2e/fixtures/create-new/metrics-step.ts +++ b/frontend/e2e/fixtures/create-new/metrics-step.ts @@ -6,6 +6,7 @@ export const config = { endDate: '2024-01-19T23:59:59.999+08:00', }, ], + sortType: 'DEFAULT', calendarType: 'Calendar with Chinese Holiday', metrics: [ 'Velocity', @@ -47,7 +48,27 @@ export const config = { 'Yunsong Yang', ], assigneeFilter: 'lastAssignee', - pipelineCrews: ['guzhongren', 'heartbeat-user', 'Unknown'], + pipelineCrews: [ + 'Chao', + 'GuangbinMa', + 'JiangRu1', + 'Jianxun.Ma', + 'Nathan Wang', + 'Steveay', + 'Yunsong', + 'andrea999', + 'guzhongren', + 'junbo dai', + 'junbo.dai', + 'mjx20045912', + 'neomgb', + 'sqsq5566', + 'weiran.sun', + 'xuebing', + 'yichen.wang', + '李雪冰', + 'Unknown', + ], cycleTime: { type: 'byColumn', jiraColumns: [ @@ -73,7 +94,7 @@ export const config = { Done: 'Done', }, ], - treatFlagCardAsBlock: true, + treatFlagCardAsBlock: false, }, doneStatus: ['DONE'], classification: [ @@ -98,6 +119,7 @@ export const config = { deployment: [ { id: 0, + isStepEmptyString: true, organization: 'Thoughtworks-Heartbeat', pipelineName: 'Heartbeat', step: ':rocket: Deploy prod', @@ -112,6 +134,7 @@ export const modifiedConfig = { startDate: '2024-01-15T00:00:00.000+08:00', endDate: '2024-01-19T23:59:59.999+08:00', }, + sortType: 'DEFAULT', calendarType: 'Calendar with Chinese Holiday', metrics: [ 'Velocity', @@ -143,7 +166,27 @@ export const modifiedConfig = { }, crews: ['heartbeat user', 'Weiran Sun'], assigneeFilter: 'lastAssignee', - pipelineCrews: ['guzhongren', 'heartbeat-user'], + pipelineCrews: [ + 'Chao', + 'GuangbinMa', + 'JiangRu1', + 'Jianxun.Ma', + 'Nathan Wang', + 'Steveay', + 'Yunsong', + 'andrea999', + 'guzhongren', + 'junbo dai', + 'junbo.dai', + 'mjx20045912', + 'neomgb', + 'sqsq5566', + 'weiran.sun', + 'xuebing', + 'yichen.wang', + '李雪冰', + 'Unknown', + ], cycleTime: { type: 'byStatus', jiraColumns: [ @@ -174,6 +217,29 @@ export const modifiedConfig = { pipelineName: 'Heartbeat', step: ':rocket: Deploy prod', branches: ['main', 'gh-pages'], + isStepEmptyString: false, }, ], }; + +export const configWithoutBlockColumn = { + crews: ['Shiqi Yuan'], + cycleTime: { + type: 'byColumn', + jiraColumns: [ + { + 'TO DO': 'To do', + }, + { + 'IN DEV': 'In Dev', + }, + { + 'WAITING FOR TESTING': 'Waiting for testing', + }, + { + Done: 'Done', + }, + ], + }, + reworkTimesSettings: { excludeStates: [], reworkState: 'In Dev' }, +}; diff --git a/frontend/e2e/fixtures/create-new/pipeline-data.csv b/frontend/e2e/fixtures/create-new/pipeline-data.csv index b3bab67630..581b1cf87a 100644 --- a/frontend/e2e/fixtures/create-new/pipeline-data.csv +++ b/frontend/e2e/fixtures/create-new/pipeline-data.csv @@ -1,48 +1,48 @@ -"Organization","Pipeline Name","Pipeline Step","Valid","Build Number","Code Committer","Pipeline Creator","First Code Committed Time In PR","Code Committed Time","PR Created Time","PR Merged Time","Deployment Completed Time","Total Lead Time (HH:mm:ss)","PR Lead Time (HH:mm:ss)","Pipeline Lead Time (HH:mm:ss)","Status","Branch" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4210","guzhongren","guzhongren","2024-01-19T14:58:27Z","2024-01-19T15:02:47Z","2024-01-19T14:59:15Z","2024-01-19T15:02:47Z","2024-01-19T15:27:32.983Z","0:24:45","0:0:0","0:24:45","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4208","Steveay","guzhongren",,"2024-01-19T09:51:14Z",,,"2024-01-19T15:09:15.439Z",,,,"failed","main" -"Thoughtworks-Heartbeat","Heartbeat",":mag: Check Frontend License","false","4204","Steveay",,,"2024-01-19T09:51:14Z",,,"2024-01-19T11:16:12.025Z",,,,"canceled","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4187","sqsq5566",,"2024-01-19T04:01:30Z","2024-01-19T06:18:27Z","2024-01-19T06:07:51Z","2024-01-19T06:18:27Z","2024-01-19T06:37:42.885Z","2:36:12","2:16:57","0:19:15","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4185","neomgb",,"2024-01-18T09:08:32Z","2024-01-19T05:47:23Z","2024-01-19T02:59:59Z","2024-01-19T05:47:24Z","2024-01-19T06:14:32.418Z","21:6:0","20:38:52","0:27:8","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4178","guzhongren","guzhongren",,"2024-01-18T15:51:58Z",,,"2024-01-18T16:56:25.673Z","1:4:27","0:0:0","1:4:27","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4177","guzhongren","guzhongren",,"2024-01-18T15:49:45Z",,,"2024-01-18T16:18:32.089Z",,,,"failed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4176","guzhongren","guzhongren",,"2024-01-18T15:37:47Z",,,"2024-01-18T16:06:41.439Z",,,,"passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4175","guzhongren","guzhongren",,"2024-01-18T15:16:05Z",,,"2024-01-18T15:53:58.280Z","0:37:53","0:0:0","0:37:53","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4174","guzhongren","guzhongren",,"2024-01-18T15:00:53Z",,,"2024-01-18T15:28:06.427Z","0:27:13","0:0:0","0:27:13","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4173","Chao",,"2024-01-18T09:54:35Z","2024-01-18T10:08:17Z","2024-01-18T05:47:24Z","2024-01-18T10:08:17Z","2024-01-18T10:33:46.039Z","0:39:11","0:13:42","0:25:29","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4171","neomgb",,"2024-01-18T02:33:54Z","2024-01-18T09:41:40Z","2024-01-18T02:54:05Z","2024-01-18T09:41:40Z","2024-01-18T10:07:29.676Z","7:33:35","7:7:46","0:25:49","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4166","junbo dai",,"2024-01-18T08:28:22Z","2024-01-18T08:56:52Z","2024-01-18T08:27:11Z","2024-01-18T08:56:52Z","2024-01-18T09:15:44.306Z","0:47:22","0:28:30","0:18:52","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4162","李雪冰",,"2024-01-18T05:45:03Z","2024-01-18T08:28:08Z","2024-01-18T06:34:30Z","2024-01-18T08:28:09Z","2024-01-18T08:52:58.699Z","3:7:55","2:43:6","0:24:49","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4151","yichen.wang","heartbeat-user",,"2024-01-18T05:35:15Z",,,"2024-01-18T05:56:34.575Z","0:21:19","0:0:0","0:21:19","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4148","yichen.wang","heartbeat-user",,"2024-01-17T15:54:45Z",,,"2024-01-18T03:01:41.593Z",,,,"failed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4147","Steveay",,"2024-01-17T07:43:29Z","2024-01-17T10:47:00Z","2024-01-17T09:42:52Z","2024-01-17T10:47:01Z","2024-01-17T11:22:02.967Z","3:38:33","3:3:32","0:35:1","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4146","李雪冰",,"2024-01-17T08:06:19Z","2024-01-17T09:48:38Z","2024-01-17T09:34:27Z","2024-01-17T09:48:38Z","2024-01-17T10:13:39.473Z","2:7:20","1:42:19","0:25:1","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4137","neomgb","heartbeat-user","2024-01-17T03:05:11Z","2024-01-17T06:46:34Z","2024-01-17T04:01:00Z","2024-01-17T06:46:34Z","2024-01-17T07:22:40.087Z","4:17:29","3:41:23","0:36:6","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4133","junbo dai",,"2024-01-17T03:54:13Z","2024-01-17T06:15:16Z","2024-01-17T03:59:41Z","2024-01-17T06:15:16Z","2024-01-17T06:34:03.987Z","2:39:50","2:21:3","0:18:47","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4123","junbo dai",,,"2024-01-17T03:17:10Z",,,"2024-01-17T03:28:28.520Z",,,,"passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4119","sqsq5566",,"2024-01-17T02:26:10Z","2024-01-17T02:55:54Z","2024-01-17T02:31:10Z","2024-01-17T02:55:54Z","2024-01-17T03:14:48.671Z","0:48:38","0:29:44","0:18:54","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4117","junbo dai",,"2024-01-17T02:27:23Z","2024-01-17T02:34:10Z","2024-01-17T02:30:48Z","2024-01-17T02:34:10Z","2024-01-17T02:53:37.896Z","0:26:14","0:6:47","0:19:27","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4114","mjx20045912",,"2024-01-16T08:44:22Z","2024-01-16T16:27:23Z","2024-01-16T08:46:04Z","2024-01-16T16:27:24Z","2024-01-16T16:46:52.588Z","8:2:30","7:43:2","0:19:28","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4109","Steveay",,"2024-01-16T09:35:48Z","2024-01-16T15:24:35Z","2024-01-16T10:42:42Z","2024-01-16T15:24:36Z","2024-01-16T15:44:39.934Z","6:8:51","5:48:48","0:20:3","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4104","sqsq5566",,"2024-01-16T08:55:43Z","2024-01-16T13:49:22Z","2024-01-16T09:33:36Z","2024-01-16T13:49:23Z","2024-01-16T14:10:20.523Z","5:14:37","4:53:40","0:20:57","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4094","xuebing",,"2024-01-16T08:20:20Z","2024-01-16T08:15:29Z","2024-01-16T08:42:05Z","2024-01-16T09:03:42Z","2024-01-16T09:30:16.889Z","1:9:56","0:43:22","0:26:34","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4087","Yunsong",,"2024-01-16T08:42:19Z","2024-01-16T08:42:19Z","2024-01-16T08:43:18Z","2024-01-16T08:43:45Z","2024-01-16T09:02:46.685Z","0:20:27","0:1:26","0:19:1","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4078","GuangbinMa","heartbeat-user","2024-01-16T06:33:44Z","2024-01-16T07:03:48Z","2024-01-16T07:25:34Z","2024-01-16T07:58:33Z","2024-01-16T08:32:28.349Z","1:58:44","1:24:49","0:33:55","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":pipeline: Upload pipeline.yml","false","4075","Yunsong",,,"2024-01-16T07:51:58Z",,,"2024-01-16T07:58:17.589Z",,,,"canceled","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4070","Yunsong",,"2024-01-16T07:06:35Z","2024-01-16T07:06:35Z","2024-01-16T07:11:17Z","2024-01-16T07:31:50Z","2024-01-16T07:50:58.369Z","0:44:23","0:25:15","0:19:8","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4065","Nathan Wang",,"2024-01-16T03:51:04Z","2024-01-16T03:30:16Z","2024-01-10T07:48:39Z","2024-01-16T05:44:17Z","2024-01-16T06:36:44.984Z","2:45:40","1:53:13","0:52:27","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4064","Simon Tal","heartbeat-user","2024-01-16T03:35:20Z","2024-01-16T03:24:08Z","2024-01-16T03:38:26Z","2024-01-16T03:47:27Z","2024-01-16T06:11:26.201Z","2:36:6","0:12:7","2:23:59","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4056","weiran.sun",,"2024-01-16T02:27:37Z","2024-01-16T02:22:01Z","2024-01-16T02:38:02Z","2024-01-16T02:41:35Z","2024-01-16T03:01:19.266Z","0:33:42","0:13:58","0:19:44","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4054","Jianxun.Ma",,,"2024-01-15T02:07:43Z",,,"2024-01-16T02:22:28.775Z",,,,"passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4050","junbo.dai",,"2024-01-16T01:14:24Z","2024-01-15T15:01:57Z","2024-01-16T01:13:59Z","2024-01-16T01:36:23Z","2024-01-16T01:57:13.241Z","0:42:49","0:21:59","0:20:50","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4047","guzhongren","guzhongren",,"2024-01-15T15:41:49Z",,,"2024-01-15T16:10:04.028Z","0:28:15","0:0:0","0:28:15","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy e2e","false","4046","guzhongren","guzhongren",,"2024-01-15T15:18:39Z",,,"2024-01-15T15:40:09.332Z",,,,"passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4045","guzhongren","guzhongren",,"2024-01-15T14:30:35Z",,,"2024-01-15T14:59:21.298Z","0:28:46","0:0:0","0:28:46","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4044","GuangbinMa",,"2024-01-15T09:00:10Z","2024-01-15T09:53:15Z","2024-01-15T09:55:27Z","2024-01-15T10:06:21Z","2024-01-15T10:33:54.045Z","1:33:44","1:6:11","0:27:33","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4034","JiangRu1",,,"2024-01-15T07:20:09Z",,,"2024-01-15T08:49:47.422Z",,,,"failed","main" -"Thoughtworks-Heartbeat","Heartbeat",":mag: Check Backend License","false","4033","Jianxun.Ma","heartbeat-user",,"2024-01-15T07:34:00Z",,,"2024-01-15T08:14:07.027Z",,,,"canceled","main" -"Thoughtworks-Heartbeat","Heartbeat",":pipeline: Upload pipeline.yml","false","4031","Jianxun.Ma","heartbeat-user",,"2024-01-15T07:34:00Z",,,"2024-01-15T07:58:40.203Z",,,,"canceled","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4027","Jianxun.Ma",,,"2024-01-15T07:34:00Z",,,"2024-01-15T08:25:40.681Z",,,,"failed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4018","GuangbinMa",,,"2024-01-15T06:16:33Z",,,"2024-01-15T06:49:24.921Z",,,,"failed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4012","Steveay",,"2024-01-12T08:02:24Z","2024-01-15T03:45:40Z","2024-01-15T01:27:53Z","2024-01-15T03:45:41Z","2024-01-15T04:05:06.398Z","68:2:42","67:43:17","0:19:25","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4001","andrea999",,"2024-01-12T09:06:51Z","2024-01-15T01:37:21Z","2024-01-12T09:23:06Z","2024-01-15T01:37:22Z","2024-01-15T01:57:54.756Z","64:51:3","64:30:31","0:20:32","passed","main" +"Organization","Pipeline Name","Pipeline Step","Valid","Build Number","Code Committer",Build Creator,"First Code Committed Time In PR","PR Created Time","PR Merged Time","No PR Committed Time","Job Start Time","Pipeline Start Time","Pipeline Finish Time","Total Lead Time (HH:mm:ss)","PR Lead Time (HH:mm:ss)","Pipeline Lead Time (HH:mm:ss)","Status","Branch","Revert" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4210",guzhongren,"guzhongren","2024-01-19T14:58:27Z","2024-01-19T14:59:15Z","2024-01-19T15:02:47Z",,"2024-01-19T15:26:59Z","2024-01-19T15:02:47Z","2024-01-19T15:27:32.983Z","0:24:45","0:0:0","0:24:45","passed","main","true" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4208",,"guzhongren",,,,,,,"2024-01-19T15:09:15.439Z",,,,"failed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":mag: Check Frontend License","false","4204","Steveay",,,,,,,,"2024-01-19T11:16:12.025Z",,,,"canceled","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4187","sqsq5566",,"2024-01-19T04:01:30Z","2024-01-19T06:07:51Z","2024-01-19T06:18:27Z",,"2024-01-19T06:37:09Z","2024-01-19T06:18:27Z","2024-01-19T06:37:42.885Z","2:36:12","2:16:57","0:19:15","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4185","neomgb",,"2024-01-18T09:08:32Z","2024-01-19T02:59:59Z","2024-01-19T05:47:24Z",,"2024-01-19T06:14:02Z","2024-01-19T05:47:24Z","2024-01-19T06:14:32.418Z","21:6:0","20:38:52","0:27:8","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4178",guzhongren,"guzhongren",,,,"2024-01-18T15:51:58Z","2024-01-18T16:55:57Z","2024-01-18T15:51:58Z","2024-01-18T16:56:25.673Z","1:4:27","0:0:0","1:4:27","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4177",guzhongren,"guzhongren",,,,,,,"2024-01-18T16:18:32.089Z",,,,"failed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4176",guzhongren,"guzhongren",,,,,,,"2024-01-18T16:06:41.439Z",,,,"passed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4175",guzhongren,"guzhongren",,,,"2024-01-18T15:16:05Z","2024-01-18T15:53:30Z","2024-01-18T15:16:05Z","2024-01-18T15:53:58.280Z","0:37:53","0:0:0","0:37:53","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4174",guzhongren,"guzhongren",,,,"2024-01-18T15:00:53Z","2024-01-18T15:27:37Z","2024-01-18T15:00:53Z","2024-01-18T15:28:06.427Z","0:27:13","0:0:0","0:27:13","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4173","Chao",,"2024-01-18T09:54:35Z","2024-01-18T05:47:24Z","2024-01-18T10:08:17Z",,"2024-01-18T10:33:18Z","2024-01-18T10:08:17Z","2024-01-18T10:33:46.039Z","0:39:11","0:13:42","0:25:29","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4171","neomgb",,"2024-01-18T02:33:54Z","2024-01-18T02:54:05Z","2024-01-18T09:41:40Z",,"2024-01-18T10:07:00Z","2024-01-18T09:41:40Z","2024-01-18T10:07:29.676Z","7:33:35","7:7:46","0:25:49","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4166","junbo dai",,"2024-01-18T08:28:22Z","2024-01-18T08:27:11Z","2024-01-18T08:56:52Z",,"2024-01-18T09:15:09Z","2024-01-18T08:56:52Z","2024-01-18T09:15:44.306Z","0:47:22","0:28:30","0:18:52","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4162","李雪冰",,"2024-01-18T05:45:03Z","2024-01-18T06:34:30Z","2024-01-18T08:28:09Z",,"2024-01-18T08:52:29Z","2024-01-18T08:28:09Z","2024-01-18T08:52:58.699Z","3:7:55","2:43:6","0:24:49","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4151",yichen.wang,"heartbeat-user",,,,"2024-01-18T05:35:15Z","2024-01-18T05:55:59Z","2024-01-18T05:35:15Z","2024-01-18T05:56:34.575Z","0:21:19","0:0:0","0:21:19","passed","main","true" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4148",yichen.wang,"heartbeat-user",,,,,,,"2024-01-18T03:01:41.593Z",,,,"failed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4147","Steveay",,"2024-01-17T07:43:29Z","2024-01-17T09:42:52Z","2024-01-17T10:47:01Z",,"2024-01-17T11:21:20Z","2024-01-17T10:47:01Z","2024-01-17T11:22:02.967Z","3:38:33","3:3:32","0:35:1","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4146","李雪冰",,"2024-01-17T08:06:19Z","2024-01-17T09:34:27Z","2024-01-17T09:48:38Z",,"2024-01-17T10:13:10Z","2024-01-17T09:48:38Z","2024-01-17T10:13:39.473Z","2:7:20","1:42:19","0:25:1","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4137",,"heartbeat-user","2024-01-17T03:05:11Z","2024-01-17T04:01:00Z","2024-01-17T06:46:34Z",,"2024-01-17T07:22:06Z","2024-01-17T06:46:34Z","2024-01-17T07:22:40.087Z","4:17:29","3:41:23","0:36:6","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4133","junbo dai",,"2024-01-17T03:54:13Z","2024-01-17T03:59:41Z","2024-01-17T06:15:16Z",,"2024-01-17T06:33:32Z","2024-01-17T06:15:16Z","2024-01-17T06:34:03.987Z","2:39:50","2:21:3","0:18:47","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4123","junbo dai",,,,,,,,"2024-01-17T03:28:28.520Z",,,,"passed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4119","sqsq5566",,"2024-01-17T02:26:10Z","2024-01-17T02:31:10Z","2024-01-17T02:55:54Z",,"2024-01-17T03:14:17Z","2024-01-17T02:55:54Z","2024-01-17T03:14:48.671Z","0:48:38","0:29:44","0:18:54","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4117","junbo dai",,"2024-01-17T02:27:23Z","2024-01-17T02:30:48Z","2024-01-17T02:34:10Z",,"2024-01-17T02:53:05Z","2024-01-17T02:34:10Z","2024-01-17T02:53:37.896Z","0:26:14","0:6:47","0:19:27","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4114","mjx20045912",,"2024-01-16T08:44:22Z","2024-01-16T08:46:04Z","2024-01-16T16:27:24Z",,"2024-01-16T16:46:22Z","2024-01-16T16:27:24Z","2024-01-16T16:46:52.588Z","8:2:30","7:43:2","0:19:28","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4109","Steveay",,"2024-01-16T09:35:48Z","2024-01-16T10:42:42Z","2024-01-16T15:24:36Z",,"2024-01-16T15:44:09Z","2024-01-16T15:24:36Z","2024-01-16T15:44:39.934Z","6:8:51","5:48:48","0:20:3","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4104","sqsq5566",,"2024-01-16T08:55:43Z","2024-01-16T09:33:36Z","2024-01-16T13:49:23Z",,"2024-01-16T14:09:48Z","2024-01-16T13:49:23Z","2024-01-16T14:10:20.523Z","5:14:37","4:53:40","0:20:57","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4094","xuebing",,"2024-01-16T08:20:20Z","2024-01-16T08:42:05Z","2024-01-16T09:03:42Z",,"2024-01-16T09:29:48Z","2024-01-16T09:03:42Z","2024-01-16T09:30:16.889Z","1:9:56","0:43:22","0:26:34","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4087","Yunsong",,"2024-01-16T08:42:19Z","2024-01-16T08:43:18Z","2024-01-16T08:43:45Z",,"2024-01-16T09:02:16Z","2024-01-16T08:43:45Z","2024-01-16T09:02:46.685Z","0:20:27","0:1:26","0:19:1","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4078",,"heartbeat-user","2024-01-16T06:33:44Z","2024-01-16T07:25:34Z","2024-01-16T07:58:33Z",,"2024-01-16T08:31:57Z","2024-01-16T07:58:33Z","2024-01-16T08:32:28.349Z","1:58:44","1:24:49","0:33:55","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":pipeline: Upload pipeline.yml","false","4075","Yunsong",,,,,,,,"2024-01-16T07:58:17.589Z",,,,"canceled","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4070","Yunsong",,"2024-01-16T07:06:35Z","2024-01-16T07:11:17Z","2024-01-16T07:31:50Z",,"2024-01-16T07:50:18Z","2024-01-16T07:31:50Z","2024-01-16T07:50:58.369Z","0:44:23","0:25:15","0:19:8","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4065","Nathan Wang",,"2024-01-16T03:51:04Z","2024-01-10T07:48:39Z","2024-01-16T05:44:17Z",,"2024-01-16T06:36:15Z","2024-01-16T05:44:17Z","2024-01-16T06:36:44.984Z","2:45:40","1:53:13","0:52:27","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4064",,"heartbeat-user","2024-01-16T03:35:20Z","2024-01-16T03:38:26Z","2024-01-16T03:47:27Z",,"2024-01-16T06:10:58Z","2024-01-16T03:47:27Z","2024-01-16T06:11:26.201Z","2:36:6","0:12:7","2:23:59","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4056","weiran.sun",,"2024-01-16T02:27:37Z","2024-01-16T02:38:02Z","2024-01-16T02:41:35Z",,"2024-01-16T03:00:48Z","2024-01-16T02:41:35Z","2024-01-16T03:01:19.266Z","0:33:42","0:13:58","0:19:44","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4054","Jianxun.Ma",,,,,,,,"2024-01-16T02:22:28.775Z",,,,"passed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4050","junbo.dai",,"2024-01-16T01:14:24Z","2024-01-16T01:13:59Z","2024-01-16T01:36:23Z",,"2024-01-16T01:56:42Z","2024-01-16T01:36:23Z","2024-01-16T01:57:13.241Z","0:42:49","0:21:59","0:20:50","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4047",guzhongren,"guzhongren",,,,"2024-01-15T15:41:49Z","2024-01-15T16:09:35Z","2024-01-15T15:41:49Z","2024-01-15T16:10:04.028Z","0:28:15","0:0:0","0:28:15","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy e2e","false","4046",guzhongren,"guzhongren",,,,,,,"2024-01-15T15:40:09.332Z",,,,"passed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4045",guzhongren,"guzhongren",,,,"2024-01-15T14:30:35Z","2024-01-15T14:58:51Z","2024-01-15T14:30:35Z","2024-01-15T14:59:21.298Z","0:28:46","0:0:0","0:28:46","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4044","GuangbinMa",,"2024-01-15T09:00:10Z","2024-01-15T09:55:27Z","2024-01-15T10:06:21Z",,"2024-01-15T10:33:21Z","2024-01-15T10:06:21Z","2024-01-15T10:33:54.045Z","1:33:44","1:6:11","0:27:33","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4034","JiangRu1",,,,,,,,"2024-01-15T08:49:47.422Z",,,,"failed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":mag: Check Backend License","false","4033",,"heartbeat-user",,,,,,,"2024-01-15T08:14:07.027Z",,,,"canceled","main","" +"Thoughtworks-Heartbeat","Heartbeat",":pipeline: Upload pipeline.yml","false","4031",,"heartbeat-user",,,,,,,"2024-01-15T07:58:40.203Z",,,,"canceled","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4027","Jianxun.Ma",,,,,,,,"2024-01-15T08:25:40.681Z",,,,"failed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4018","GuangbinMa",,,,,,,,"2024-01-15T06:49:24.921Z",,,,"failed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4012","Steveay",,"2024-01-12T08:02:24Z","2024-01-15T01:27:53Z","2024-01-15T03:45:41Z",,"2024-01-15T04:04:34Z","2024-01-15T03:45:41Z","2024-01-15T04:05:06.398Z","68:2:42","67:43:17","0:19:25","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4001","andrea999",,"2024-01-12T09:06:51Z","2024-01-12T09:23:06Z","2024-01-15T01:37:22Z",,"2024-01-15T01:57:23Z","2024-01-15T01:37:22Z","2024-01-15T01:57:54.756Z","64:51:3","64:30:31","0:20:32","passed","main","false" diff --git a/frontend/e2e/fixtures/create-new/report-result.ts b/frontend/e2e/fixtures/create-new/report-result.ts index ed25741be1..f65517c11b 100644 --- a/frontend/e2e/fixtures/create-new/report-result.ts +++ b/frontend/e2e/fixtures/create-new/report-result.ts @@ -12,8 +12,8 @@ export const BOARD_METRICS_RESULT = { export const FLAG_AS_BLOCK_PROJECT_BOARD_METRICS_RESULT = { Velocity: '7.5', Throughput: '5', - AverageCycleTime4SP: '0.50', - AverageCycleTime4Card: '0.75', + AverageCycleTime4SP: '0.55', + AverageCycleTime4Card: '0.83', totalReworkTimes: '3', totalReworkCards: '3', reworkCardsRatio: '0.6000', diff --git a/frontend/e2e/fixtures/cycle-time-by-status/board-data-by-status.csv b/frontend/e2e/fixtures/cycle-time-by-status/board-data-by-status.csv index 29fd87d1ea..ebdbc9c4bb 100644 --- a/frontend/e2e/fixtures/cycle-time-by-status/board-data-by-status.csv +++ b/frontend/e2e/fixtures/cycle-time-by-status/board-data-by-status.csv @@ -1,5 +1,5 @@ -"Issue key","Summary","Issue Type","Status","Status Date","Story Points","assignee","Reporter","Project Key","Project Name","Priority","Parent Summary","Sprint","Labels","Cycle Time","story point","Flagged","Story point estimate","Design","Cycle Time / Story Points","Analysis Days","In Dev Days","Waiting Days","Testing Days","Block Days","Review Days","OriginCycleTime: TO DO","OriginCycleTime: IN PROGRESS","OriginCycleTime: REVIEW","OriginCycleTime: FLAG","OriginCycleTime: ANALYSIS","OriginCycleTime: READY FOR TESTING","OriginCycleTime: DOING" -"ST-5","card5","Task","done","2024-03-29","3.0","Junbo Dai","heartbeat user","ST","status-test","Medium",,,"","1.17","","","3.0","","0.39","0","1.12","0","0","0","0.05","0.78","1.12","0.05","0","0","0","0" -"ST-6","card6","Task","done","2024-03-29","2.0","Chao Wang","heartbeat user","ST","status-test","Medium",,,"","0.98","","","2.0","","0.49","0","0.93","0.05","0","0","0","0.78","0","0","0","0.93","0.05","0" -"ST-4","card4","Task","done","2024-03-29","2.0","heartbeat user","heartbeat user","ST","status-test","Medium",,,"","0.99","","","2.0","","0.49","0","0.99","0","0","0","0","0.78","0","0","0","0","0","0.99" -,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +Issue key,Summary,Issue Type,Status,Status Date,Story Points,assignee,Reporter,Project Key,Project Name,Priority,Parent Summary,Sprint,Labels,Cycle Time,story point,Flagged,Story point estimate,Design,Cycle Time / Story Points,Analysis Days,In Dev Days,Waiting Days,Testing Days,Block Days,Review Days,OriginCycleTime: TO DO,OriginCycleTime: IN PROGRESS,OriginCycleTime: REVIEW,OriginCycleTime: ANALYSIS,OriginCycleTime: READY FOR TESTING,OriginCycleTime: DOING +ST-5,card5,Task,done,2024-03-29,3.0,Junbo Dai,heartbeat user,ST,status-test,Medium,,,"",1.17,"","",3.0,"",0.39,0,1.12,0,0,0,0.05,0.78,1.12,0.05,0,0,0 +ST-6,card6,Task,done,2024-03-29,2.0,Chao Wang,heartbeat user,ST,status-test,Medium,,,"",0.98,"","",2.0,"",0.49,0,0.93,0.05,0,0,0,0.78,0,0,0.93,0.05,0 +ST-4,card4,Task,done,2024-03-29,2.0,heartbeat user,heartbeat user,ST,status-test,Medium,,,"",0.99,"","",2.0,"",0.49,0,0.99,0,0,0,0,0.78,0,0,0,0,0.99 +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, diff --git a/frontend/e2e/fixtures/cycle-time-by-status/cycle-time-by-column-fixture.ts b/frontend/e2e/fixtures/cycle-time-by-status/cycle-time-by-column-fixture.ts index a8103adeee..864ef55cf0 100644 --- a/frontend/e2e/fixtures/cycle-time-by-status/cycle-time-by-column-fixture.ts +++ b/frontend/e2e/fixtures/cycle-time-by-status/cycle-time-by-column-fixture.ts @@ -60,7 +60,7 @@ export const cycleTimeByColumnFixture = { Done: 'Done', }, ], - treatFlagCardAsBlock: true, + treatFlagCardAsBlock: false, }, doneStatus: ['DONE'], classification: [ diff --git a/frontend/e2e/fixtures/import-file/board-data-without-block-column.csv b/frontend/e2e/fixtures/import-file/board-data-without-block-column.csv new file mode 100644 index 0000000000..098a9b4e3d --- /dev/null +++ b/frontend/e2e/fixtures/import-file/board-data-without-block-column.csv @@ -0,0 +1,7 @@ +"Issue key","Summary","Issue Type","Status","Status Date","Story Points","assignee","Reporter","Project Key","Project Name","Priority","Parent Summary","Sprint","Labels","Cycle Time","Cycle Time / Story Points","Analysis Days","In Dev Days","Waiting Days","Testing Days","Block Days","Review Days","OriginCycleTime: TESTING","OriginCycleTime: TO DO","OriginCycleTime: IN PROGRESS","OriginCycleTime: REVIEW","OriginCycleTime: FLAG","Rework: total - In dev","Rework: from Block","Rework: from Review","Rework: from Testing","Rework: from Done" +"TFB-6","test - 1","Task","Done","2024-04-09","1.5","heartbeat user","heartbeat user","TFB","E2E-Test-For-Flag-As-Block","Medium",,,"","0.70","0.47","0","0.70","0","0","0","0","0","0","0.86","0","0.16","1","1","0","0","0" +"TFB-7","test - 2","Task","Done","2024-04-09","2.0","heartbeat user","heartbeat user","TFB","E2E-Test-For-Flag-As-Block","Medium",,,"","0.75","0.38","0","0.73","0","0","0","0.02","0","0","0.84","0.02","0.11","1","1","0","0","0" +"TFB-8","test - 3","Task","Done","2024-04-09","1.0","heartbeat user","heartbeat user","TFB","E2E-Test-For-Flag-As-Block","Medium",,,"","0.70","0.70","0","0.70","0","0","0","0","0","0","0.81","0","0.11","1","1","0","0","0" +"TFB-10","test - 5","Task","Done","2024-04-09","2.0","heartbeat user","heartbeat user","TFB","E2E-Test-For-Flag-As-Block","Medium",,,"","0.81","0.41","0","0.81","0","0","0","0","0","0","0.81","0","0","0","0","0","0","0" +"TFB-9","test - 4","Task","Done","2024-04-09","1.0","heartbeat user","heartbeat user","TFB","E2E-Test-For-Flag-As-Block","Medium",,,"","0.81","0.81","0","0.81","0","0","0","0","0","0","0.81","0","0","0","0","0","0","0" +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, diff --git a/frontend/e2e/fixtures/import-file/board-data.csv b/frontend/e2e/fixtures/import-file/board-data.csv index 13aca7665b..e1910f5730 100644 --- a/frontend/e2e/fixtures/import-file/board-data.csv +++ b/frontend/e2e/fixtures/import-file/board-data.csv @@ -1,4 +1,4 @@ -"Issue key","Summary","Issue Type","Status","Status Date","Story Points","assignee","Reporter","Project Key","Project Name","Priority","Parent Summary","Sprint","Labels","Cycle Time","Story testing-1","Flagged","Fix versions","Partner","Time tracking","Story point estimate","QA","Feature/Operation","Story testing-2","Cycle Time / Story Points","Analysis Days","In Dev Days","Waiting Days","Testing Days","Block Days","Review Days","OriginCycleTime: TODO","OriginCycleTime: TESTING","OriginCycleTime: WAIT FOR TEST","OriginCycleTime: DOING","OriginCycleTime: REVIEW","OriginCycleTime: FLAG","OriginCycleTime: BLOCKED" +"Issue key","Summary","Issue Type","Status","Status Date","Story Points","assignee","Reporter","Project Key","Project Name","Priority","Parent Summary","Sprint","Labels","Cycle Time","Story testing-1","Flagged","Fix versions","Partner","Time tracking","Story point estimate","QA","Feature/Operation","Story testing-2","Cycle Time / Story Points","Analysis Days","In Dev Days","Waiting Days","Testing Days","Block Days","Review Days","OriginCycleTime: TODO","OriginCycleTime: TESTING","OriginCycleTime: WAIT FOR TEST","OriginCycleTime: DOING","OriginCycleTime: REVIEW","OriginCycleTime: BLOCKED" "ADM-735","[backend]identify the source of the error when generate reports encounter exception","Task","Done","2024-01-19","1.0","Yunsong Yang","Yunsong Yang","ADM","Auto Dora Metrics","Medium","Precise on Metrics","Sprint 28","Stream2","7.70","1.0","","","","None","1.0","","","","7.70","0","2.02","1.81","0","0","3.87","3.03","0","1.81","2.02","3.87","0","0" "ADM-708","[Backend] Verify board and obtain board data with new API","Task","Done","2024-01-19","3.0","Weiran Sun","heartbeat user","ADM","Auto Dora Metrics","Medium","easy to use","Sprint 28","Stream1","9.95","1.0","","","","None","3.0","","","","3.32","0","4.00","0.93","1.04","0.98","3.00","7.10","1.04","0.93","4.00","3.00","0","0.98" "ADM-699","[Frontend] Optimize the 4xx&504 error display of report overview","Task","Done","2024-01-18","2.0","heartbeat user","heartbeat user","ADM","Auto Dora Metrics","Medium","Performance Improvement","Sprint 28","Stream2","10.93","1.0","","","","None","2.0","","","","5.46","0","5.14","0.04","0.78","2.01","2.96","10.75","0.78","0.04","5.14","2.96","0","2.01" diff --git a/frontend/e2e/fixtures/import-file/chart-step-data.ts b/frontend/e2e/fixtures/import-file/chart-step-data.ts new file mode 100644 index 0000000000..579c94f213 --- /dev/null +++ b/frontend/e2e/fixtures/import-file/chart-step-data.ts @@ -0,0 +1,30 @@ +export const chartStepData = { + unSelectBranch: 'main', + addNewBranch: ['ADM-669'], + errorDateRange: [ + { + startDate: '2024-09-07T00:00:00.000+08:00', + endDate: '2024-04-08T23:59:59.999+08:00', + }, + ], + noCardDateRange: [ + { + startDate: '2011-04-07T00:00:00.000+08:00', + endDate: '2011-04-08T23:59:59.999+08:00', + }, + { + startDate: '2011-03-07T00:00:00.000+08:00', + endDate: '2011-03-08T23:59:59.999+08:00', + }, + ], + rightDateRange: [ + { + startDate: '2024-01-15T00:00:00.000+08:00', + endDate: '2024-01-16T23:59:59.999+08:00', + }, + { + startDate: '2024-01-17T00:00:00.000+08:00', + endDate: '2024-01-19T23:59:59.999+08:00', + }, + ], +}; diff --git a/frontend/e2e/fixtures/import-file/metric-data.csv b/frontend/e2e/fixtures/import-file/metric-data.csv index 7402eda4b6..4fcc230604 100644 --- a/frontend/e2e/fixtures/import-file/metric-data.csv +++ b/frontend/e2e/fixtures/import-file/metric-data.csv @@ -26,17 +26,17 @@ "Classifications","Story testing-2 / None","100.00" "Classifications","Story testing-1 / 1.0","88.89" "Classifications","Story testing-1 / None","11.11" +"Classifications","Project / Auto Dora Metrics","100.00" "Classifications","Sprint / Sprint 26","11.11" "Classifications","Sprint / Sprint 27","100.00" "Classifications","Sprint / Sprint 28","88.89" -"Classifications","Project / Auto Dora Metrics","100.00" "Classifications","Flagged / None","100.00" "Classifications","Fix versions / None","100.00" "Classifications","Priority / Medium","100.00" "Classifications","Partner / None","100.00" +"Classifications","Time tracking / None","100.00" "Classifications","Labels / Stream1","44.44" "Classifications","Labels / Stream2","55.56" -"Classifications","Time tracking / None","100.00" "Classifications","Story point estimate / 1.0","44.44" "Classifications","Story point estimate / 2.0","22.22" "Classifications","Story point estimate / 3.0","33.33" diff --git a/frontend/e2e/fixtures/import-file/multiple-done-config-file.ts b/frontend/e2e/fixtures/import-file/multiple-done-config-file.ts index 78b8e7ff5a..48a50c1905 100644 --- a/frontend/e2e/fixtures/import-file/multiple-done-config-file.ts +++ b/frontend/e2e/fixtures/import-file/multiple-done-config-file.ts @@ -96,6 +96,7 @@ export const importMultipleDoneProjectFromFile = { pipelineName: 'Heartbeat', step: ':rocket: Deploy prod', branches: ['main'], + isStepEmptyString: false, }, ], reworkTimesSettings: { diff --git a/frontend/e2e/fixtures/import-file/pipeline-data.csv b/frontend/e2e/fixtures/import-file/pipeline-data.csv index b3bab67630..581b1cf87a 100644 --- a/frontend/e2e/fixtures/import-file/pipeline-data.csv +++ b/frontend/e2e/fixtures/import-file/pipeline-data.csv @@ -1,48 +1,48 @@ -"Organization","Pipeline Name","Pipeline Step","Valid","Build Number","Code Committer","Pipeline Creator","First Code Committed Time In PR","Code Committed Time","PR Created Time","PR Merged Time","Deployment Completed Time","Total Lead Time (HH:mm:ss)","PR Lead Time (HH:mm:ss)","Pipeline Lead Time (HH:mm:ss)","Status","Branch" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4210","guzhongren","guzhongren","2024-01-19T14:58:27Z","2024-01-19T15:02:47Z","2024-01-19T14:59:15Z","2024-01-19T15:02:47Z","2024-01-19T15:27:32.983Z","0:24:45","0:0:0","0:24:45","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4208","Steveay","guzhongren",,"2024-01-19T09:51:14Z",,,"2024-01-19T15:09:15.439Z",,,,"failed","main" -"Thoughtworks-Heartbeat","Heartbeat",":mag: Check Frontend License","false","4204","Steveay",,,"2024-01-19T09:51:14Z",,,"2024-01-19T11:16:12.025Z",,,,"canceled","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4187","sqsq5566",,"2024-01-19T04:01:30Z","2024-01-19T06:18:27Z","2024-01-19T06:07:51Z","2024-01-19T06:18:27Z","2024-01-19T06:37:42.885Z","2:36:12","2:16:57","0:19:15","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4185","neomgb",,"2024-01-18T09:08:32Z","2024-01-19T05:47:23Z","2024-01-19T02:59:59Z","2024-01-19T05:47:24Z","2024-01-19T06:14:32.418Z","21:6:0","20:38:52","0:27:8","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4178","guzhongren","guzhongren",,"2024-01-18T15:51:58Z",,,"2024-01-18T16:56:25.673Z","1:4:27","0:0:0","1:4:27","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4177","guzhongren","guzhongren",,"2024-01-18T15:49:45Z",,,"2024-01-18T16:18:32.089Z",,,,"failed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4176","guzhongren","guzhongren",,"2024-01-18T15:37:47Z",,,"2024-01-18T16:06:41.439Z",,,,"passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4175","guzhongren","guzhongren",,"2024-01-18T15:16:05Z",,,"2024-01-18T15:53:58.280Z","0:37:53","0:0:0","0:37:53","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4174","guzhongren","guzhongren",,"2024-01-18T15:00:53Z",,,"2024-01-18T15:28:06.427Z","0:27:13","0:0:0","0:27:13","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4173","Chao",,"2024-01-18T09:54:35Z","2024-01-18T10:08:17Z","2024-01-18T05:47:24Z","2024-01-18T10:08:17Z","2024-01-18T10:33:46.039Z","0:39:11","0:13:42","0:25:29","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4171","neomgb",,"2024-01-18T02:33:54Z","2024-01-18T09:41:40Z","2024-01-18T02:54:05Z","2024-01-18T09:41:40Z","2024-01-18T10:07:29.676Z","7:33:35","7:7:46","0:25:49","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4166","junbo dai",,"2024-01-18T08:28:22Z","2024-01-18T08:56:52Z","2024-01-18T08:27:11Z","2024-01-18T08:56:52Z","2024-01-18T09:15:44.306Z","0:47:22","0:28:30","0:18:52","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4162","李雪冰",,"2024-01-18T05:45:03Z","2024-01-18T08:28:08Z","2024-01-18T06:34:30Z","2024-01-18T08:28:09Z","2024-01-18T08:52:58.699Z","3:7:55","2:43:6","0:24:49","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4151","yichen.wang","heartbeat-user",,"2024-01-18T05:35:15Z",,,"2024-01-18T05:56:34.575Z","0:21:19","0:0:0","0:21:19","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4148","yichen.wang","heartbeat-user",,"2024-01-17T15:54:45Z",,,"2024-01-18T03:01:41.593Z",,,,"failed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4147","Steveay",,"2024-01-17T07:43:29Z","2024-01-17T10:47:00Z","2024-01-17T09:42:52Z","2024-01-17T10:47:01Z","2024-01-17T11:22:02.967Z","3:38:33","3:3:32","0:35:1","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4146","李雪冰",,"2024-01-17T08:06:19Z","2024-01-17T09:48:38Z","2024-01-17T09:34:27Z","2024-01-17T09:48:38Z","2024-01-17T10:13:39.473Z","2:7:20","1:42:19","0:25:1","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4137","neomgb","heartbeat-user","2024-01-17T03:05:11Z","2024-01-17T06:46:34Z","2024-01-17T04:01:00Z","2024-01-17T06:46:34Z","2024-01-17T07:22:40.087Z","4:17:29","3:41:23","0:36:6","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4133","junbo dai",,"2024-01-17T03:54:13Z","2024-01-17T06:15:16Z","2024-01-17T03:59:41Z","2024-01-17T06:15:16Z","2024-01-17T06:34:03.987Z","2:39:50","2:21:3","0:18:47","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4123","junbo dai",,,"2024-01-17T03:17:10Z",,,"2024-01-17T03:28:28.520Z",,,,"passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4119","sqsq5566",,"2024-01-17T02:26:10Z","2024-01-17T02:55:54Z","2024-01-17T02:31:10Z","2024-01-17T02:55:54Z","2024-01-17T03:14:48.671Z","0:48:38","0:29:44","0:18:54","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4117","junbo dai",,"2024-01-17T02:27:23Z","2024-01-17T02:34:10Z","2024-01-17T02:30:48Z","2024-01-17T02:34:10Z","2024-01-17T02:53:37.896Z","0:26:14","0:6:47","0:19:27","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4114","mjx20045912",,"2024-01-16T08:44:22Z","2024-01-16T16:27:23Z","2024-01-16T08:46:04Z","2024-01-16T16:27:24Z","2024-01-16T16:46:52.588Z","8:2:30","7:43:2","0:19:28","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4109","Steveay",,"2024-01-16T09:35:48Z","2024-01-16T15:24:35Z","2024-01-16T10:42:42Z","2024-01-16T15:24:36Z","2024-01-16T15:44:39.934Z","6:8:51","5:48:48","0:20:3","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4104","sqsq5566",,"2024-01-16T08:55:43Z","2024-01-16T13:49:22Z","2024-01-16T09:33:36Z","2024-01-16T13:49:23Z","2024-01-16T14:10:20.523Z","5:14:37","4:53:40","0:20:57","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4094","xuebing",,"2024-01-16T08:20:20Z","2024-01-16T08:15:29Z","2024-01-16T08:42:05Z","2024-01-16T09:03:42Z","2024-01-16T09:30:16.889Z","1:9:56","0:43:22","0:26:34","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4087","Yunsong",,"2024-01-16T08:42:19Z","2024-01-16T08:42:19Z","2024-01-16T08:43:18Z","2024-01-16T08:43:45Z","2024-01-16T09:02:46.685Z","0:20:27","0:1:26","0:19:1","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4078","GuangbinMa","heartbeat-user","2024-01-16T06:33:44Z","2024-01-16T07:03:48Z","2024-01-16T07:25:34Z","2024-01-16T07:58:33Z","2024-01-16T08:32:28.349Z","1:58:44","1:24:49","0:33:55","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":pipeline: Upload pipeline.yml","false","4075","Yunsong",,,"2024-01-16T07:51:58Z",,,"2024-01-16T07:58:17.589Z",,,,"canceled","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4070","Yunsong",,"2024-01-16T07:06:35Z","2024-01-16T07:06:35Z","2024-01-16T07:11:17Z","2024-01-16T07:31:50Z","2024-01-16T07:50:58.369Z","0:44:23","0:25:15","0:19:8","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4065","Nathan Wang",,"2024-01-16T03:51:04Z","2024-01-16T03:30:16Z","2024-01-10T07:48:39Z","2024-01-16T05:44:17Z","2024-01-16T06:36:44.984Z","2:45:40","1:53:13","0:52:27","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4064","Simon Tal","heartbeat-user","2024-01-16T03:35:20Z","2024-01-16T03:24:08Z","2024-01-16T03:38:26Z","2024-01-16T03:47:27Z","2024-01-16T06:11:26.201Z","2:36:6","0:12:7","2:23:59","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4056","weiran.sun",,"2024-01-16T02:27:37Z","2024-01-16T02:22:01Z","2024-01-16T02:38:02Z","2024-01-16T02:41:35Z","2024-01-16T03:01:19.266Z","0:33:42","0:13:58","0:19:44","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4054","Jianxun.Ma",,,"2024-01-15T02:07:43Z",,,"2024-01-16T02:22:28.775Z",,,,"passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4050","junbo.dai",,"2024-01-16T01:14:24Z","2024-01-15T15:01:57Z","2024-01-16T01:13:59Z","2024-01-16T01:36:23Z","2024-01-16T01:57:13.241Z","0:42:49","0:21:59","0:20:50","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4047","guzhongren","guzhongren",,"2024-01-15T15:41:49Z",,,"2024-01-15T16:10:04.028Z","0:28:15","0:0:0","0:28:15","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy e2e","false","4046","guzhongren","guzhongren",,"2024-01-15T15:18:39Z",,,"2024-01-15T15:40:09.332Z",,,,"passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4045","guzhongren","guzhongren",,"2024-01-15T14:30:35Z",,,"2024-01-15T14:59:21.298Z","0:28:46","0:0:0","0:28:46","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4044","GuangbinMa",,"2024-01-15T09:00:10Z","2024-01-15T09:53:15Z","2024-01-15T09:55:27Z","2024-01-15T10:06:21Z","2024-01-15T10:33:54.045Z","1:33:44","1:6:11","0:27:33","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4034","JiangRu1",,,"2024-01-15T07:20:09Z",,,"2024-01-15T08:49:47.422Z",,,,"failed","main" -"Thoughtworks-Heartbeat","Heartbeat",":mag: Check Backend License","false","4033","Jianxun.Ma","heartbeat-user",,"2024-01-15T07:34:00Z",,,"2024-01-15T08:14:07.027Z",,,,"canceled","main" -"Thoughtworks-Heartbeat","Heartbeat",":pipeline: Upload pipeline.yml","false","4031","Jianxun.Ma","heartbeat-user",,"2024-01-15T07:34:00Z",,,"2024-01-15T07:58:40.203Z",,,,"canceled","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4027","Jianxun.Ma",,,"2024-01-15T07:34:00Z",,,"2024-01-15T08:25:40.681Z",,,,"failed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4018","GuangbinMa",,,"2024-01-15T06:16:33Z",,,"2024-01-15T06:49:24.921Z",,,,"failed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4012","Steveay",,"2024-01-12T08:02:24Z","2024-01-15T03:45:40Z","2024-01-15T01:27:53Z","2024-01-15T03:45:41Z","2024-01-15T04:05:06.398Z","68:2:42","67:43:17","0:19:25","passed","main" -"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4001","andrea999",,"2024-01-12T09:06:51Z","2024-01-15T01:37:21Z","2024-01-12T09:23:06Z","2024-01-15T01:37:22Z","2024-01-15T01:57:54.756Z","64:51:3","64:30:31","0:20:32","passed","main" +"Organization","Pipeline Name","Pipeline Step","Valid","Build Number","Code Committer",Build Creator,"First Code Committed Time In PR","PR Created Time","PR Merged Time","No PR Committed Time","Job Start Time","Pipeline Start Time","Pipeline Finish Time","Total Lead Time (HH:mm:ss)","PR Lead Time (HH:mm:ss)","Pipeline Lead Time (HH:mm:ss)","Status","Branch","Revert" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4210",guzhongren,"guzhongren","2024-01-19T14:58:27Z","2024-01-19T14:59:15Z","2024-01-19T15:02:47Z",,"2024-01-19T15:26:59Z","2024-01-19T15:02:47Z","2024-01-19T15:27:32.983Z","0:24:45","0:0:0","0:24:45","passed","main","true" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4208",,"guzhongren",,,,,,,"2024-01-19T15:09:15.439Z",,,,"failed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":mag: Check Frontend License","false","4204","Steveay",,,,,,,,"2024-01-19T11:16:12.025Z",,,,"canceled","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4187","sqsq5566",,"2024-01-19T04:01:30Z","2024-01-19T06:07:51Z","2024-01-19T06:18:27Z",,"2024-01-19T06:37:09Z","2024-01-19T06:18:27Z","2024-01-19T06:37:42.885Z","2:36:12","2:16:57","0:19:15","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4185","neomgb",,"2024-01-18T09:08:32Z","2024-01-19T02:59:59Z","2024-01-19T05:47:24Z",,"2024-01-19T06:14:02Z","2024-01-19T05:47:24Z","2024-01-19T06:14:32.418Z","21:6:0","20:38:52","0:27:8","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4178",guzhongren,"guzhongren",,,,"2024-01-18T15:51:58Z","2024-01-18T16:55:57Z","2024-01-18T15:51:58Z","2024-01-18T16:56:25.673Z","1:4:27","0:0:0","1:4:27","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4177",guzhongren,"guzhongren",,,,,,,"2024-01-18T16:18:32.089Z",,,,"failed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4176",guzhongren,"guzhongren",,,,,,,"2024-01-18T16:06:41.439Z",,,,"passed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4175",guzhongren,"guzhongren",,,,"2024-01-18T15:16:05Z","2024-01-18T15:53:30Z","2024-01-18T15:16:05Z","2024-01-18T15:53:58.280Z","0:37:53","0:0:0","0:37:53","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4174",guzhongren,"guzhongren",,,,"2024-01-18T15:00:53Z","2024-01-18T15:27:37Z","2024-01-18T15:00:53Z","2024-01-18T15:28:06.427Z","0:27:13","0:0:0","0:27:13","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4173","Chao",,"2024-01-18T09:54:35Z","2024-01-18T05:47:24Z","2024-01-18T10:08:17Z",,"2024-01-18T10:33:18Z","2024-01-18T10:08:17Z","2024-01-18T10:33:46.039Z","0:39:11","0:13:42","0:25:29","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4171","neomgb",,"2024-01-18T02:33:54Z","2024-01-18T02:54:05Z","2024-01-18T09:41:40Z",,"2024-01-18T10:07:00Z","2024-01-18T09:41:40Z","2024-01-18T10:07:29.676Z","7:33:35","7:7:46","0:25:49","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4166","junbo dai",,"2024-01-18T08:28:22Z","2024-01-18T08:27:11Z","2024-01-18T08:56:52Z",,"2024-01-18T09:15:09Z","2024-01-18T08:56:52Z","2024-01-18T09:15:44.306Z","0:47:22","0:28:30","0:18:52","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4162","李雪冰",,"2024-01-18T05:45:03Z","2024-01-18T06:34:30Z","2024-01-18T08:28:09Z",,"2024-01-18T08:52:29Z","2024-01-18T08:28:09Z","2024-01-18T08:52:58.699Z","3:7:55","2:43:6","0:24:49","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4151",yichen.wang,"heartbeat-user",,,,"2024-01-18T05:35:15Z","2024-01-18T05:55:59Z","2024-01-18T05:35:15Z","2024-01-18T05:56:34.575Z","0:21:19","0:0:0","0:21:19","passed","main","true" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4148",yichen.wang,"heartbeat-user",,,,,,,"2024-01-18T03:01:41.593Z",,,,"failed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4147","Steveay",,"2024-01-17T07:43:29Z","2024-01-17T09:42:52Z","2024-01-17T10:47:01Z",,"2024-01-17T11:21:20Z","2024-01-17T10:47:01Z","2024-01-17T11:22:02.967Z","3:38:33","3:3:32","0:35:1","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4146","李雪冰",,"2024-01-17T08:06:19Z","2024-01-17T09:34:27Z","2024-01-17T09:48:38Z",,"2024-01-17T10:13:10Z","2024-01-17T09:48:38Z","2024-01-17T10:13:39.473Z","2:7:20","1:42:19","0:25:1","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4137",,"heartbeat-user","2024-01-17T03:05:11Z","2024-01-17T04:01:00Z","2024-01-17T06:46:34Z",,"2024-01-17T07:22:06Z","2024-01-17T06:46:34Z","2024-01-17T07:22:40.087Z","4:17:29","3:41:23","0:36:6","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4133","junbo dai",,"2024-01-17T03:54:13Z","2024-01-17T03:59:41Z","2024-01-17T06:15:16Z",,"2024-01-17T06:33:32Z","2024-01-17T06:15:16Z","2024-01-17T06:34:03.987Z","2:39:50","2:21:3","0:18:47","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4123","junbo dai",,,,,,,,"2024-01-17T03:28:28.520Z",,,,"passed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4119","sqsq5566",,"2024-01-17T02:26:10Z","2024-01-17T02:31:10Z","2024-01-17T02:55:54Z",,"2024-01-17T03:14:17Z","2024-01-17T02:55:54Z","2024-01-17T03:14:48.671Z","0:48:38","0:29:44","0:18:54","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4117","junbo dai",,"2024-01-17T02:27:23Z","2024-01-17T02:30:48Z","2024-01-17T02:34:10Z",,"2024-01-17T02:53:05Z","2024-01-17T02:34:10Z","2024-01-17T02:53:37.896Z","0:26:14","0:6:47","0:19:27","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4114","mjx20045912",,"2024-01-16T08:44:22Z","2024-01-16T08:46:04Z","2024-01-16T16:27:24Z",,"2024-01-16T16:46:22Z","2024-01-16T16:27:24Z","2024-01-16T16:46:52.588Z","8:2:30","7:43:2","0:19:28","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4109","Steveay",,"2024-01-16T09:35:48Z","2024-01-16T10:42:42Z","2024-01-16T15:24:36Z",,"2024-01-16T15:44:09Z","2024-01-16T15:24:36Z","2024-01-16T15:44:39.934Z","6:8:51","5:48:48","0:20:3","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4104","sqsq5566",,"2024-01-16T08:55:43Z","2024-01-16T09:33:36Z","2024-01-16T13:49:23Z",,"2024-01-16T14:09:48Z","2024-01-16T13:49:23Z","2024-01-16T14:10:20.523Z","5:14:37","4:53:40","0:20:57","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4094","xuebing",,"2024-01-16T08:20:20Z","2024-01-16T08:42:05Z","2024-01-16T09:03:42Z",,"2024-01-16T09:29:48Z","2024-01-16T09:03:42Z","2024-01-16T09:30:16.889Z","1:9:56","0:43:22","0:26:34","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4087","Yunsong",,"2024-01-16T08:42:19Z","2024-01-16T08:43:18Z","2024-01-16T08:43:45Z",,"2024-01-16T09:02:16Z","2024-01-16T08:43:45Z","2024-01-16T09:02:46.685Z","0:20:27","0:1:26","0:19:1","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4078",,"heartbeat-user","2024-01-16T06:33:44Z","2024-01-16T07:25:34Z","2024-01-16T07:58:33Z",,"2024-01-16T08:31:57Z","2024-01-16T07:58:33Z","2024-01-16T08:32:28.349Z","1:58:44","1:24:49","0:33:55","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":pipeline: Upload pipeline.yml","false","4075","Yunsong",,,,,,,,"2024-01-16T07:58:17.589Z",,,,"canceled","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4070","Yunsong",,"2024-01-16T07:06:35Z","2024-01-16T07:11:17Z","2024-01-16T07:31:50Z",,"2024-01-16T07:50:18Z","2024-01-16T07:31:50Z","2024-01-16T07:50:58.369Z","0:44:23","0:25:15","0:19:8","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4065","Nathan Wang",,"2024-01-16T03:51:04Z","2024-01-10T07:48:39Z","2024-01-16T05:44:17Z",,"2024-01-16T06:36:15Z","2024-01-16T05:44:17Z","2024-01-16T06:36:44.984Z","2:45:40","1:53:13","0:52:27","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4064",,"heartbeat-user","2024-01-16T03:35:20Z","2024-01-16T03:38:26Z","2024-01-16T03:47:27Z",,"2024-01-16T06:10:58Z","2024-01-16T03:47:27Z","2024-01-16T06:11:26.201Z","2:36:6","0:12:7","2:23:59","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4056","weiran.sun",,"2024-01-16T02:27:37Z","2024-01-16T02:38:02Z","2024-01-16T02:41:35Z",,"2024-01-16T03:00:48Z","2024-01-16T02:41:35Z","2024-01-16T03:01:19.266Z","0:33:42","0:13:58","0:19:44","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4054","Jianxun.Ma",,,,,,,,"2024-01-16T02:22:28.775Z",,,,"passed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4050","junbo.dai",,"2024-01-16T01:14:24Z","2024-01-16T01:13:59Z","2024-01-16T01:36:23Z",,"2024-01-16T01:56:42Z","2024-01-16T01:36:23Z","2024-01-16T01:57:13.241Z","0:42:49","0:21:59","0:20:50","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4047",guzhongren,"guzhongren",,,,"2024-01-15T15:41:49Z","2024-01-15T16:09:35Z","2024-01-15T15:41:49Z","2024-01-15T16:10:04.028Z","0:28:15","0:0:0","0:28:15","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy e2e","false","4046",guzhongren,"guzhongren",,,,,,,"2024-01-15T15:40:09.332Z",,,,"passed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4045",guzhongren,"guzhongren",,,,"2024-01-15T14:30:35Z","2024-01-15T14:58:51Z","2024-01-15T14:30:35Z","2024-01-15T14:59:21.298Z","0:28:46","0:0:0","0:28:46","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4044","GuangbinMa",,"2024-01-15T09:00:10Z","2024-01-15T09:55:27Z","2024-01-15T10:06:21Z",,"2024-01-15T10:33:21Z","2024-01-15T10:06:21Z","2024-01-15T10:33:54.045Z","1:33:44","1:6:11","0:27:33","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4034","JiangRu1",,,,,,,,"2024-01-15T08:49:47.422Z",,,,"failed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":mag: Check Backend License","false","4033",,"heartbeat-user",,,,,,,"2024-01-15T08:14:07.027Z",,,,"canceled","main","" +"Thoughtworks-Heartbeat","Heartbeat",":pipeline: Upload pipeline.yml","false","4031",,"heartbeat-user",,,,,,,"2024-01-15T07:58:40.203Z",,,,"canceled","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4027","Jianxun.Ma",,,,,,,,"2024-01-15T08:25:40.681Z",,,,"failed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Run e2e","false","4018","GuangbinMa",,,,,,,,"2024-01-15T06:49:24.921Z",,,,"failed","main","" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4012","Steveay",,"2024-01-12T08:02:24Z","2024-01-15T01:27:53Z","2024-01-15T03:45:41Z",,"2024-01-15T04:04:34Z","2024-01-15T03:45:41Z","2024-01-15T04:05:06.398Z","68:2:42","67:43:17","0:19:25","passed","main","false" +"Thoughtworks-Heartbeat","Heartbeat",":rocket: Deploy prod","true","4001","andrea999",,"2024-01-12T09:06:51Z","2024-01-12T09:23:06Z","2024-01-15T01:37:22Z",,"2024-01-15T01:57:23Z","2024-01-15T01:37:22Z","2024-01-15T01:57:54.756Z","64:51:3","64:30:31","0:20:32","passed","main","false" diff --git a/frontend/e2e/fixtures/import-file/unhappy-path-file.ts b/frontend/e2e/fixtures/import-file/unhappy-path-file.ts index 5aacf6847c..757f4286bb 100644 --- a/frontend/e2e/fixtures/import-file/unhappy-path-file.ts +++ b/frontend/e2e/fixtures/import-file/unhappy-path-file.ts @@ -86,6 +86,7 @@ export const importInputWrongProjectFromFile = { pipelineName: 'Heartbeat', step: ':rocket: Deploy prod', branches: ['main', 'ADM-747'], + isStepEmptyString: false, }, ], }; diff --git a/frontend/e2e/fixtures/input-files/charting-unhappy-path-config-file.template.json b/frontend/e2e/fixtures/input-files/charting-unhappy-path-config-file.template.json new file mode 100644 index 0000000000..3f340a206e --- /dev/null +++ b/frontend/e2e/fixtures/input-files/charting-unhappy-path-config-file.template.json @@ -0,0 +1,95 @@ +{ + "projectName": "Heartbeat Metrics", + "dateRange": { + "startDate": "2024-02-12T00:00:00.000+08:00", + "endDate": "2024-02-16T23:59:59.999+08:00" + }, + "calendarType": "Calendar with Chinese Holiday", + "metrics": [ + "Velocity", + "Cycle time", + "Classification", + "Rework times", + "Lead time for changes", + "Deployment frequency", + "Change failure rate", + "Mean time to recovery" + ], + "board": { + "type": "Classic Jira", + "boardId": "2", + "email": "heartbeatuser2023@gmail.com", + "site": "dorametrics", + "token": "" + }, + "pipelineTool": { + "type": "BuildKite", + "token": "" + }, + "sourceControl": { + "type": "GitHub", + "token": "" + }, + "crews": ["heartbeat user"], + "assigneeFilter": "lastAssignee", + "pipelineCrews": ["heartbeat-user", "guzhongren", "Unknown"], + "cycleTime": { + "type": "byColumn", + "jiraColumns": [ + { + "TODO": "To do" + }, + { + "Doing": "In Dev" + }, + { + "Blocked": "Block" + }, + { + "Review": "Review" + }, + { + "READY FOR TESTING": "Waiting for testing" + }, + { + "Testing": "Testing" + }, + { + "Done": "Done" + } + ], + "treatFlagCardAsBlock": true + }, + "doneStatus": ["DONE"], + "classification": [ + "issuetype", + "parent", + "customfield_10061", + "customfield_10020", + "project", + "customfield_10021", + "fixVersions", + "priority", + "customfield_10037", + "labels", + "timetracking", + "customfield_10016", + "customfield_10038", + "assignee", + "customfield_10027", + "customfield_10060" + ], + "deployment": [ + { + "id": 0, + "organization": "Thoughtworks-Heartbeat", + "pipelineName": "Heartbeat", + "step": ":rocket: Deploy prod", + "branches": ["main"] + } + ], + "reworkTimesSettings": { + "reworkState": "In Dev", + "excludeStates": [] + } +} diff --git a/frontend/e2e/fixtures/input-files/unhappy-path-config-file.template.json b/frontend/e2e/fixtures/input-files/unhappy-path-config-file.template.json index b3c0348aa2..52056366a2 100644 --- a/frontend/e2e/fixtures/input-files/unhappy-path-config-file.template.json +++ b/frontend/e2e/fixtures/input-files/unhappy-path-config-file.template.json @@ -32,7 +32,27 @@ }, "crews": [], "assigneeFilter": "lastAssignee", - "pipelineCrews": ["heartbeat-user", "guzhongren", "Unknown"], + "pipelineCrews": [ + "Chao", + "GuangbinMa", + "JiangRu1", + "Jianxun.Ma", + "Nathan Wang", + "Steveay", + "Yunsong", + "andrea999", + "guzhongren", + "junbo dai", + "junbo.dai", + "mjx20045912", + "neomgb", + "sqsq5566", + "weiran.sun", + "xuebing", + "yichen.wang", + "李雪冰", + "Unknown" + ], "cycleTime": { "type": "byColumn", "jiraColumns": [ diff --git a/frontend/e2e/pages/metrics/config-step.ts b/frontend/e2e/pages/metrics/config-step.ts index 8fe53851cf..7ab90047a5 100644 --- a/frontend/e2e/pages/metrics/config-step.ts +++ b/frontend/e2e/pages/metrics/config-step.ts @@ -26,6 +26,11 @@ export class ConfigStep { readonly projectNameInput: Locator; readonly regularCalendar: Locator; readonly chineseCalendar: Locator; + readonly basicInfoContainer: Locator; + readonly newTimeRangeButton: Locator; + readonly removeTimeRangeButtons: Locator; + readonly fromDateErrorMessage: Locator; + readonly toDateErrorMessage: Locator; readonly fromDateInput: Locator; readonly fromDateInputButton: Locator; readonly fromDateInputValueSelect: (fromDay: Dayjs) => Locator; @@ -47,11 +52,11 @@ export class ConfigStep { readonly requiredMetricsVelocityOption: Locator; readonly requiredMetricsCycleTimeOption: Locator; readonly requiredMetricsClassificationOption: Locator; - readonly requiredMetricsReworkTimesOption: Locator; readonly requiredMetricsLeadTimeForChangesOption: Locator; readonly requiredMetricsDeploymentFrequencyOption: Locator; readonly requiredMetricsChangeFailureRateOption: Locator; readonly requiredMetricsMeanTimeToRecoveryOption: Locator; + readonly requiredMetricsReworkTimesOption: Locator; readonly boardContainer: Locator; readonly boardTypeSelect: Locator; readonly boardIdInput: Locator; @@ -85,20 +90,25 @@ export class ConfigStep { this.projectNameInput = page.getByLabel('Project name *'); this.regularCalendar = page.getByText('Regular Calendar(Weekend'); this.chineseCalendar = page.getByText('Calendar with Chinese Holiday'); - this.fromDateInput = page.getByRole('textbox', { name: 'From *' }); + this.basicInfoContainer = page.getByLabel('Basic information'); + this.fromDateInput = this.basicInfoContainer.getByRole('textbox', { name: 'From' }); this.fromDateInputButton = page .locator('div') .filter({ hasText: /^From \*$/ }) .getByRole('button', { name: 'Choose date' }); this.fromDateInputValueSelect = (fromDay: Dayjs) => page.getByRole('dialog', { name: 'From *' }).getByRole('gridcell', { name: `${fromDay.date()}` }); - this.toDateInput = page.getByRole('textbox', { name: 'To *' }); + this.toDateInput = this.basicInfoContainer.getByRole('textbox', { name: 'To' }); this.toDateInputButton = page .locator('div') .filter({ hasText: /^To \*$/ }) .getByRole('button', { name: 'Choose date' }); this.toDateInputValueSelect = (toDay: Dayjs) => page.getByRole('dialog', { name: 'To *' }).getByRole('gridcell', { name: `${toDay.date()}` }); + this.newTimeRangeButton = this.basicInfoContainer.getByRole('button', { name: 'Button for adding date range' }); + this.fromDateErrorMessage = this.basicInfoContainer.getByText('Start date is invalid'); + this.toDateErrorMessage = this.basicInfoContainer.getByText('End date is invalid'); + this.removeTimeRangeButtons = this.basicInfoContainer.getByText('Remove'); this.requireDataButton = page.getByRole('button', { name: 'Required Data' }); this.velocityCheckbox = page.getByRole('option', { name: 'Velocity' }).getByRole('checkbox'); @@ -121,6 +131,7 @@ export class ConfigStep { this.requiredMetricsDeploymentFrequencyOption = page.getByRole('option', { name: 'Deployment frequency' }); this.requiredMetricsChangeFailureRateOption = page.getByRole('option', { name: 'Change failure rate' }); this.requiredMetricsMeanTimeToRecoveryOption = page.getByRole('option', { name: 'Mean time to recovery' }); + this.requiredMetricsReworkTimesOption = page.getByRole('option', { name: 'Rework times' }); this.boardContainer = page.getByLabel('Board Config'); this.boardTypeSelect = this.boardContainer.getByLabel('Board *'); @@ -232,15 +243,31 @@ export class ConfigStep { expect(this.requiredDataErrorMessage).toBeTruthy(); } - async typeInDateRange({ startDate, endDate }: { startDate: string; endDate: string }) { - await this.fromDateInput.fill(startDate); - await this.toDateInput.fill(endDate); + async typeInDateRange({ startDate, endDate, number = 0 }: { startDate: string; endDate: string; number?: number }) { + await this.fromDateInput.nth(number).fill(startDate); + await this.toDateInput.nth(number).fill(endDate); } async validateNextButtonNotClickable() { await expect(this.nextButton).toBeDisabled(); } + async validateAddNewTimeRangeButtonNotClickable() { + await expect(this.newTimeRangeButton).toBeDisabled(); + } + + async validateRemoveTimeRangeButtonIsHidden() { + await expect(this.removeTimeRangeButtons.last()).toBeHidden(); + } + + async checkErrorStratTimeMessage() { + await expect(this.fromDateErrorMessage).toBeVisible(); + } + + async checkErrorEndTimeMessage() { + await expect(this.toDateErrorMessage).toBeVisible(); + } + async validateNextButtonClickable() { await expect(this.nextButton).toBeEnabled(); } @@ -252,7 +279,7 @@ export class ConfigStep { } async selectBoardMetricsOnly() { - await this.requiredMetricsLabel.click(); + await this.requiredMetricsLabel.first().click(); await this.velocityCheckbox.click(); await this.classificationCheckbox.click(); await this.cycleTimeCheckbox.click(); @@ -447,6 +474,14 @@ export class ConfigStep { await this.boardVerifyButton.click(); } + async addNewTimeRange() { + await this.newTimeRangeButton.click(); + } + + async RemoveLastNewPipeline() { + await this.removeTimeRangeButtons.last().click(); + } + async checkAllConfigInvalid() { await expect(this.boardTokenErrorMessage).toBeVisible(); await expect(this.pipelineTokenErrorMessage).toBeVisible(); diff --git a/frontend/e2e/pages/metrics/metrics-step.ts b/frontend/e2e/pages/metrics/metrics-step.ts index 8d4b346586..e8836a8277 100644 --- a/frontend/e2e/pages/metrics/metrics-step.ts +++ b/frontend/e2e/pages/metrics/metrics-step.ts @@ -25,20 +25,24 @@ export class MetricsStep { readonly boardByColumnRadioBox: Locator; readonly boardByStatusRadioBox: Locator; readonly boardCycleTimeSelectForTODO: Locator; - readonly boardCycleTimeSelectForTO_DO_BYSTATUS: Locator; + readonly boardCycleTimeSelectForTO_DO: Locator; readonly boardCycleTimeSelectForDoing: Locator; + readonly boardCycleTimeSelectForIN_DEV: Locator; readonly boardCycleTimeSelectForInProgress: Locator; readonly boardCycleTimeSelectForAnalysis: Locator; readonly boardCycleTimeSelectForBlocked: Locator; readonly boardCycleTimeSelectForReview: Locator; readonly boardCycleTimeSelectForREADY: Locator; readonly boardCycleTimeSelectForWAITFORTESTING: Locator; + readonly boardCycleTimeSelectForWAIT_FOR_TESTING: Locator; readonly boardCycleTimeSelectForTesting: Locator; + readonly boardCycleTimeSelectForTESTING: Locator; readonly boardCycleTimeSelectForDone: Locator; readonly boardCycleTimeInputForTODO: Locator; readonly boardCycleTimeInputForDoing: Locator; readonly boardCycleTimeInputForBlocked: Locator; readonly boardCycleTimeInputForReview: Locator; + readonly boardCycleTimeInputForREVIEW: Locator; readonly boardCycleTimeInputForREADY: Locator; readonly boardCycleTimeInputForTesting: Locator; readonly boardCycleTimeInputForDone: Locator; @@ -102,12 +106,15 @@ export class MetricsStep { this.boardCycleTimeSelectForTODO = this.boardCycleTimeSection .getByLabel('Cycle time select for TODO') .getByLabel('Open'); - this.boardCycleTimeSelectForTO_DO_BYSTATUS = this.boardCycleTimeSection + this.boardCycleTimeSelectForTO_DO = this.boardCycleTimeSection .getByLabel('Cycle time select for TO DO') .getByLabel('Open'); this.boardCycleTimeSelectForDoing = this.boardCycleTimeSection .getByLabel('Cycle time select for Doing') .getByLabel('Open'); + this.boardCycleTimeSelectForIN_DEV = this.boardCycleTimeSection + .getByLabel('Cycle time select for IN DEV') + .getByLabel('Open'); this.boardCycleTimeSelectForInProgress = this.boardCycleTimeSection .getByLabel('Cycle time select for IN PROGRESS') .getByLabel('Open'); @@ -126,9 +133,15 @@ export class MetricsStep { this.boardCycleTimeSelectForWAITFORTESTING = this.boardCycleTimeSection .getByLabel('Cycle time select for WAIT FOR TEST') .getByLabel('Open'); + this.boardCycleTimeSelectForWAIT_FOR_TESTING = this.boardCycleTimeSection + .getByLabel('Cycle time select for WAITING FOR TESTING') + .getByLabel('Open'); this.boardCycleTimeSelectForTesting = this.boardCycleTimeSection .getByLabel('Cycle time select for Testing') .getByLabel('Open'); + this.boardCycleTimeSelectForTESTING = this.boardCycleTimeSection + .getByLabel('Cycle time select for TESTING') + .getByLabel('Open'); this.boardCycleTimeSelectForDone = this.boardCycleTimeSection .getByLabel('Cycle time select for Done') .getByLabel('Open'); @@ -144,6 +157,9 @@ export class MetricsStep { this.boardCycleTimeInputForReview = this.boardCycleTimeSection .getByLabel('Cycle time select for Review') .getByRole('combobox'); + this.boardCycleTimeInputForREVIEW = this.boardCycleTimeSection + .getByLabel('Cycle time select for REVIEW') + .getByRole('combobox'); this.boardCycleTimeInputForREADY = this.boardCycleTimeSection .getByLabel('Cycle time select for WAIT FOR TEST') .getByRole('combobox'); @@ -398,7 +414,7 @@ export class MetricsStep { [todoOption, doingOption, blockOption, reviewOption, forReadyOption, testingOption, doneOption]: string[], isByColumn: boolean, ) { - await this.boardCycleTimeSelectForTO_DO_BYSTATUS.click(); + await this.boardCycleTimeSelectForTO_DO.click(); await this.page.getByRole('option', { name: todoOption }).click(); await this.boardCycleTimeSelectForDoing.click(); @@ -428,6 +444,20 @@ export class MetricsStep { await this.page.getByRole('option', { name: doneOption }).click(); } + async selectHeartbeatStateWithoutBlock([todoOption, inDevOption, waitForTestingOption, doneOption]: string[]) { + await this.boardCycleTimeSelectForTO_DO.click(); + await this.page.getByRole('option', { name: todoOption }).click(); + + await this.boardCycleTimeSelectForIN_DEV.click(); + await this.page.getByRole('option', { name: inDevOption }).click(); + + await this.boardCycleTimeSelectForWAIT_FOR_TESTING.click(); + await this.page.getByRole('option', { name: waitForTestingOption }).click(); + + await this.boardCycleTimeSelectForDone.click(); + await this.page.getByRole('option', { name: doneOption }).click(); + } + async checkHeartbeatStateIsSet( [todoOption, doingOption, blockOption, reviewOption, forReadyOption, testingOption, doneOption]: string[], isByColumn: boolean, @@ -527,6 +557,15 @@ export class MetricsStep { await this.page.keyboard.press('Escape'); } + async addBranch(branches: string[]) { + await this.pipelineBranchSelect.click(); + for (const branchName of branches) { + await this.page.getByRole('combobox', { name: 'Branches' }).fill(branchName); + await this.page.getByRole('option', { name: branchName }).getByRole('checkbox').check(); + } + await this.page.keyboard.press('Escape'); + } + async deselectBranch(branch: string) { await this.pipelineDefaultBranchSelectContainer.click(); await this.page.getByRole('option', { name: branch }).getByRole('checkbox').uncheck(); @@ -543,19 +582,33 @@ export class MetricsStep { .getByLabel('Open') .nth(1) .click(); - await expect(this.page.getByRole('option', { name: firstPipelineConfig.pipelineName })).not.toBeEnabled(); } - async RemoveFirstNewPipeline() { - const pipelineList = this.pipelineSettingSection.getByText('Organization *Pipeline Name *Remove'); + async addNewPipelineAndSelectOrgAndName() { + await this.pipelineNewPipelineButton.click(); + await this.pipelineSettingSection.getByText('Organization *Remove').getByLabel('Open').click(); + await this.page.getByRole('option', { name: 'Thoughtworks-Heartbeat' }).click(); + await this.pipelineSettingSection + .getByText('Organization *Pipeline Name *Remove') + .getByLabel('Open') + .nth(1) + .click(); + await this.page.getByRole('option', { name: 'Heartbeat-E2E' }).click(); + } + + async checkPipelineLength(length: number) { + const pipelineLength = await this.pipelineSettingSection.getByText('Organization *').count(); + expect(pipelineLength).toEqual(length); + } - await pipelineList.nth(0).getByRole('button', { name: 'remove' }).click(); + async removePipeline(index: number) { + await this.pipelineSettingSection.getByText('Remove').nth(index).click(); } async checkPipelineFillNoStep(pipelineSettings: typeof metricsStepData.deployment) { const firstPipelineConfig = pipelineSettings[0]; await expect(this.page.getByRole('alert')).toContainText( - 'There is no step during this period for this pipeline! Please change the search time in the Config page!', + 'There is no step during these periods for this pipeline! Please change the search time in the Config page!', ); await expect(this.pipelineOrganizationSelect).toHaveValue(firstPipelineConfig.organization); await expect(this.pipelineNameSelect).toHaveValue(firstPipelineConfig.pipelineName); diff --git a/frontend/e2e/pages/metrics/report-step.ts b/frontend/e2e/pages/metrics/report-step.ts index 904c75f863..936e0963d8 100644 --- a/frontend/e2e/pages/metrics/report-step.ts +++ b/frontend/e2e/pages/metrics/report-step.ts @@ -38,6 +38,7 @@ export class ReportStep { readonly classificationRows: Locator; readonly leadTimeForChangesRows: Locator; readonly devChangeFailureRateRows: Locator; + readonly deploymentFrequencyRows: Locator; readonly devMeanTimeToRecoveryRows: Locator; readonly reworkRows: Locator; @@ -72,10 +73,14 @@ export class ReportStep { this.homeIcon = page.getByLabel('Home'); this.velocityRows = this.page.getByTestId('Velocity').locator('tbody').getByRole('row'); this.cycleTimeRows = this.page.getByTestId('Cycle Time').locator('tbody').getByRole('row'); + this.deploymentFrequencyRows = this.page.getByTestId('Deployment Frequency').locator('tbody').getByRole('row'); this.classificationRows = this.page.getByTestId('Classification').locator('tbody').getByRole('row'); this.leadTimeForChangesRows = this.page.getByTestId('Lead Time For Changes').getByRole('row'); - this.devChangeFailureRateRows = this.page.getByTestId('Dev Change Failure Rate').getByRole('row'); - this.devMeanTimeToRecoveryRows = this.page.getByTestId('Dev Mean Time To Recovery').getByRole('row'); + this.devChangeFailureRateRows = this.page.getByTestId('Dev Change Failure Rate').locator('tbody').getByRole('row'); + this.devMeanTimeToRecoveryRows = this.page + .getByTestId('Dev Mean Time To Recovery') + .locator('tbody') + .getByRole('row'); this.reworkRows = this.page.getByTestId('Rework').getByRole('row'); } combineStrings(arr: string[]): string { @@ -91,23 +96,17 @@ export class ReportStep { } async checkDoraMetricsReportDetails() { - await expect(this.page.getByTestId('Deployment Frequency').getByRole('row').nth(2)).toContainText( - this.combineStrings(['Deployment frequency', '6.60']), - ); + await expect(this.deploymentFrequencyRows.getByRole('cell').nth(0)).toContainText('Heartbeat/ Deploy prod'); + await expect(this.deploymentFrequencyRows.getByRole('cell').nth(1)).toContainText('6.60'); await expect(this.leadTimeForChangesRows.nth(2)).toContainText(this.combineStrings(['PR Lead Time', '6.12'])); await expect(this.leadTimeForChangesRows.nth(3)).toContainText(this.combineStrings(['Pipeline Lead Time', '0.50'])); await expect(this.leadTimeForChangesRows.nth(4)).toContainText(this.combineStrings(['Total Lead Time', '6.62'])); - await expect(this.leadTimeForChangesRows.nth(4)).toContainText(this.combineStrings(['Total Lead Time', '6.62'])); - - await expect(this.devChangeFailureRateRows.nth(2)).toContainText( - this.combineStrings(['Dev change failure rate', '17.50%(7/40)']), - ); - - await expect(this.devMeanTimeToRecoveryRows.nth(2)).toContainText( - this.combineStrings(['Dev mean time to recovery', '1.90']), - ); + await expect(this.devChangeFailureRateRows.getByRole('cell').nth(0)).toContainText('Heartbeat/ Deploy prod'); + await expect(this.devChangeFailureRateRows.getByRole('cell').nth(1)).toContainText('17.50%(7/40)'); + await expect(this.devMeanTimeToRecoveryRows.getByRole('cell').nth(0)).toContainText('Heartbeat/ Deploy prod'); + await expect(this.devMeanTimeToRecoveryRows.getByRole('cell').nth(1)).toContainText('1.90'); } async checkDoraMetricsDetails(projectCreationType: ProjectCreationType) { @@ -230,17 +229,17 @@ export class ReportStep { await expect(this.classificationRows.nth(8)).toContainText(this.combineStrings(['None', '100.00%'])); await expect(this.classificationRows.nth(10)).toContainText(this.combineStrings(['1.0', '88.89%'])); await expect(this.classificationRows.nth(11)).toContainText(this.combineStrings(['None', '11.11%'])); - await expect(this.classificationRows.nth(13)).toContainText(this.combineStrings(['Sprint 26', '11.11%'])); - await expect(this.classificationRows.nth(14)).toContainText(this.combineStrings(['Sprint 27', '100.00%'])); - await expect(this.classificationRows.nth(15)).toContainText(this.combineStrings(['Sprint 28', '88.89%'])); - await expect(this.classificationRows.nth(17)).toContainText(this.combineStrings(['Auto Dora Metrics', '100.00%'])); + await expect(this.classificationRows.nth(13)).toContainText(this.combineStrings(['Auto Dora Metrics', '100.00%'])); + await expect(this.classificationRows.nth(15)).toContainText(this.combineStrings(['Sprint 26', '11.11%'])); + await expect(this.classificationRows.nth(16)).toContainText(this.combineStrings(['Sprint 27', '100.00%'])); + await expect(this.classificationRows.nth(17)).toContainText(this.combineStrings(['Sprint 28', '88.89%'])); await expect(this.classificationRows.nth(19)).toContainText(this.combineStrings(['None', '100.00%'])); await expect(this.classificationRows.nth(21)).toContainText(this.combineStrings(['None', '100.00%'])); await expect(this.classificationRows.nth(23)).toContainText(this.combineStrings(['Medium', '100.00%'])); await expect(this.classificationRows.nth(25)).toContainText(this.combineStrings(['None', '100.00%'])); - await expect(this.classificationRows.nth(27)).toContainText(this.combineStrings(['Stream1', '44.44%'])); - await expect(this.classificationRows.nth(28)).toContainText(this.combineStrings(['Stream2', '55.56%'])); - await expect(this.classificationRows.nth(30)).toContainText(this.combineStrings(['None', '100.00%'])); + await expect(this.classificationRows.nth(27)).toContainText(this.combineStrings(['None', '100.00%'])); + await expect(this.classificationRows.nth(29)).toContainText(this.combineStrings(['Stream1', '44.44%'])); + await expect(this.classificationRows.nth(30)).toContainText(this.combineStrings(['Stream2', '55.56%'])); await expect(this.classificationRows.nth(32)).toContainText(this.combineStrings(['1.0', '44.44%'])); await expect(this.classificationRows.nth(33)).toContainText(this.combineStrings(['2.0', '22.22%'])); await expect(this.classificationRows.nth(34)).toContainText(this.combineStrings(['3.0', '33.33%'])); @@ -356,6 +355,21 @@ export class ReportStep { await this.backButton.click(); } + async checkBoardDownloadDataWithoutBlock(fileName: string) { + await downloadFileAndCheck( + this.page, + this.exportBoardData, + 'board-data-without-block-column.csv', + async (fileDataString) => { + const localCsvFile = fs.readFileSync(path.resolve(__dirname, fileName)); + const localCsv = parse(localCsvFile); + const downloadCsv = parse(fileDataString); + + expect(localCsv).toStrictEqual(downloadCsv); + }, + ); + } + async checkDoraMetrics( prLeadTime: string, pipelineLeadTime: string, diff --git a/frontend/e2e/specs/major-path/create-a-new-project.spec.ts b/frontend/e2e/specs/major-path/create-a-new-project.spec.ts index 018291b331..9784022991 100644 --- a/frontend/e2e/specs/major-path/create-a-new-project.spec.ts +++ b/frontend/e2e/specs/major-path/create-a-new-project.spec.ts @@ -1,3 +1,5 @@ +import { configWithoutBlockColumn as metricsStepWithoutBlockColumnData } from '../../fixtures/create-new/metrics-step'; +import { configWithoutBlockColumn as configWithoutBlockColumnData } from '../../fixtures/create-new/config-step'; import { cycleTimeByStatusFixture } from '../../fixtures/cycle-time-by-status/cycle-time-by-status-fixture'; import { BOARD_METRICS_RESULT, DORA_METRICS_RESULT } from '../../fixtures/create-new/report-result'; import { config as metricsStepData } from '../../fixtures/create-new/metrics-step'; @@ -50,7 +52,6 @@ test('Create a new project', async ({ homePage, configStep, metricsStep, reportS await metricsStep.checkBoardConfigurationVisible(); await metricsStep.checkPipelineConfigurationVisible(); await metricsStep.checkLastAssigneeCrewFilterChecked(); - await metricsStep.checkCycleTimeConsiderCheckboxChecked(); await metricsStep.checkCycleTimeSettingIsByColumn(); await metricsStep.waitForHiddenLoading(); await metricsStep.selectCrews(metricsStepData.crews); @@ -97,3 +98,46 @@ test('Create a new project', async ({ homePage, configStep, metricsStep, reportS await reportStep.checkDoraMetricsDetails(ProjectCreationType.CREATE_A_NEW_PROJECT); await reportStep.checkMetricDownloadData(); }); + +test('Create a new project without block column in boarding mapping', async ({ + homePage, + configStep, + metricsStep, + reportStep, +}) => { + const dateRange = { + startDate: format(configWithoutBlockColumnData.dateRange[0].startDate), + endDate: format(configWithoutBlockColumnData.dateRange[0].endDate), + }; + + await homePage.goto(); + await homePage.createANewProject(); + await configStep.waitForShown(); + await configStep.typeInProjectName(configWithoutBlockColumnData.projectName); + await configStep.selectRegularCalendar(configWithoutBlockColumnData.calendarType); + await configStep.typeInDateRange(dateRange); + await configStep.selectReworkTimesRequiredMetrics(); + await configStep.checkBoardFormVisible(); + await configStep.checkPipelineToolFormInvisible(); + await configStep.checkSourceControlFormInvisible(); + await configStep.fillAndVerifyBoardConfig(configWithoutBlockColumnData.board); + await configStep.validateNextButtonClickable(); + await configStep.goToMetrics(); + + await metricsStep.checkBoardConfigurationVisible(); + await metricsStep.checkPipelineConfigurationInvisible(); + await metricsStep.checkClassificationSettingInvisible(); + await metricsStep.selectCrews(metricsStepWithoutBlockColumnData.crews); + await metricsStep.selectCycleTimeSettingsType(metricsStepWithoutBlockColumnData.cycleTime.type); + await metricsStep.checkCycleTimeConsiderCheckboxChecked(); + await metricsStep.selectHeartbeatStateWithoutBlock( + metricsStepWithoutBlockColumnData.cycleTime.jiraColumns.map( + (jiraToHBSingleMap) => Object.values(jiraToHBSingleMap)[0], + ), + ); + await metricsStep.selectReworkSettings(metricsStepWithoutBlockColumnData.reworkTimesSettings); + + await metricsStep.goToReportPage(); + await reportStep.confirmGeneratedReport(); + await reportStep.checkBoardDownloadDataWithoutBlock('../../fixtures/create-new/board-data-without-block-column.csv'); +}); diff --git a/frontend/e2e/specs/major-path/cycle-time-by-status-test.spec.ts b/frontend/e2e/specs/major-path/cycle-time-by-status-test.spec.ts index 0bb40ef345..d75bf914a5 100644 --- a/frontend/e2e/specs/major-path/cycle-time-by-status-test.spec.ts +++ b/frontend/e2e/specs/major-path/cycle-time-by-status-test.spec.ts @@ -27,7 +27,6 @@ test('Create a new project with cycle time by status', async ({ homePage, config await metricsStep.waitForShown(); await metricsStep.validateNextButtonNotClickable(); await metricsStep.checkLastAssigneeCrewFilterChecked(); - await metricsStep.checkCycleTimeConsiderCheckboxChecked(); await metricsStep.checkCycleTimeSettingIsByColumn(); await metricsStep.waitForHiddenLoading(); await metricsStep.selectCrews(cycleTimeByStatusFixture.crews); @@ -65,7 +64,6 @@ test('Create a new project with cycle time by status', async ({ homePage, config await metricsStep.waitForShown(); await metricsStep.validateNextButtonNotClickable(); await metricsStep.checkLastAssigneeCrewFilterChecked(); - await metricsStep.checkCycleTimeConsiderCheckboxChecked(); await metricsStep.checkCycleTimeSettingIsByColumn(); await metricsStep.waitForHiddenLoading(); await metricsStep.selectCrews(cycleTimeByColumnFixture.crews); diff --git a/frontend/e2e/specs/major-path/import-project-from-file.spec.ts b/frontend/e2e/specs/major-path/import-project-from-file.spec.ts index 16cbf741a6..7f69a111eb 100644 --- a/frontend/e2e/specs/major-path/import-project-from-file.spec.ts +++ b/frontend/e2e/specs/major-path/import-project-from-file.spec.ts @@ -68,15 +68,22 @@ test('Import project from file', async ({ homePage, configStep, metricsStep, rep await reportStep.checkDownloadReports(); }); -test('Import project from flag as block', async ({ homePage, configStep, metricsStep, reportStep }) => { +test('Import project from flag as block and without block column', async ({ + homePage, + configStep, + metricsStep, + reportStep, +}) => { await homePage.goto(); await homePage.importProjectFromFile('../fixtures/input-files/add-flag-as-block-config-file.json'); await configStep.verifyBoardConfig(); await configStep.goToMetrics(); await metricsStep.waitForShown(); + await metricsStep.checkCycleTimeConsiderCheckboxChecked(); await metricsStep.goToReportPage(); + await reportStep.confirmGeneratedReport(); await reportStep.checkBoardMetrics( FLAG_AS_BLOCK_PROJECT_BOARD_METRICS_RESULT.Velocity, FLAG_AS_BLOCK_PROJECT_BOARD_METRICS_RESULT.Throughput, @@ -87,4 +94,5 @@ test('Import project from flag as block', async ({ homePage, configStep, metrics FLAG_AS_BLOCK_PROJECT_BOARD_METRICS_RESULT.reworkCardsRatio, FLAG_AS_BLOCK_PROJECT_BOARD_METRICS_RESULT.throughput, ); + await reportStep.checkBoardDownloadDataWithoutBlock('../../fixtures/import-file/board-data-without-block-column.csv'); }); diff --git a/frontend/e2e/specs/major-path/page-jumps.spec.ts b/frontend/e2e/specs/major-path/page-jumps.spec.ts index b319037ae3..08c4fc1dcb 100644 --- a/frontend/e2e/specs/major-path/page-jumps.spec.ts +++ b/frontend/e2e/specs/major-path/page-jumps.spec.ts @@ -28,7 +28,6 @@ test('Page jump for import', async ({ homePage, configStep, metricsStep, reportS await metricsStep.selectCrews(modifiedMetricsStepData.crews); await metricsStep.selectCycleTimeSettingsType(modifiedMetricsStepData.cycleTime.type); await metricsStep.selectModifiedHeartbeatState(modifiedHbStateData); - await metricsStep.selectCycleTimeConsiderAsBlockCheckbox(); await metricsStep.selectClassifications(modifiedMetricsStepData.classification); await metricsStep.selectReworkSettings(metricsStepData.reworkTimesSettings); await metricsStep.goToReportPage(); @@ -38,7 +37,6 @@ test('Page jump for import', async ({ homePage, configStep, metricsStep, reportS await metricsStep.checkCrews(modifiedMetricsStepData.crews); await metricsStep.checkBoardByStatusRadioBoxChecked(); await metricsStep.checkModifiedHeartbeatState(modifiedHbStateData); - await metricsStep.checkCycleTimeConsiderAsBlockUnchecked(); await metricsStep.checkClassifications(modifiedMetricsStepData.classification); await metricsStep.checkReworkSettings(metricsStepData.reworkTimesSettings); diff --git a/frontend/e2e/specs/side-path/charting-unhappy-path.spec.ts b/frontend/e2e/specs/side-path/charting-unhappy-path.spec.ts new file mode 100644 index 0000000000..5df0156530 --- /dev/null +++ b/frontend/e2e/specs/side-path/charting-unhappy-path.spec.ts @@ -0,0 +1,80 @@ +import { chartStepData } from '../../fixtures/import-file/chart-step-data'; +import { test } from '../../fixtures/test-with-extend-fixtures'; +import { clearTempDir } from '../../utils/clear-temp-dir'; +import { format } from '../../utils/date-time'; + +test.beforeAll(async () => { + await clearTempDir(); +}); + +test('Charting unhappy path on config and metri page', async ({ homePage, configStep, metricsStep }) => { + const rightDateRange_frist = { + startDate: format(chartStepData.rightDateRange[0].startDate), + endDate: format(chartStepData.rightDateRange[0].endDate), + number: 0, + }; + const rightDateRange_second = { + startDate: format(chartStepData.rightDateRange[1].startDate), + endDate: format(chartStepData.rightDateRange[1].endDate), + number: 1, + }; + const errorDateRange = { + startDate: format(chartStepData.errorDateRange[0].startDate), + endDate: format(chartStepData.errorDateRange[0].endDate), + }; + + const noCardDateRange_frist = { + startDate: format(chartStepData.noCardDateRange[0].startDate), + endDate: format(chartStepData.noCardDateRange[0].endDate), + number: 0, + }; + + const noCardDateRange_second = { + startDate: format(chartStepData.noCardDateRange[1].startDate), + endDate: format(chartStepData.noCardDateRange[1].endDate), + number: 1, + }; + await homePage.goto(); + + await homePage.importProjectFromFile('../fixtures/input-files/charting-unhappy-path-config-file.json'); + + await configStep.verifyAllConfig(); + await configStep.addNewTimeRange(); + await configStep.addNewTimeRange(); + await configStep.addNewTimeRange(); + await configStep.addNewTimeRange(); + await configStep.addNewTimeRange(); + await configStep.validateAddNewTimeRangeButtonNotClickable(); + await configStep.validateNextButtonNotClickable(); + + await configStep.RemoveLastNewPipeline(); + await configStep.RemoveLastNewPipeline(); + await configStep.RemoveLastNewPipeline(); + await configStep.RemoveLastNewPipeline(); + await configStep.RemoveLastNewPipeline(); + await configStep.validateRemoveTimeRangeButtonIsHidden(); + + await configStep.typeInDateRange(errorDateRange); + await configStep.checkErrorStratTimeMessage(); + await configStep.checkErrorEndTimeMessage(); + await configStep.validateNextButtonNotClickable(); + + await configStep.typeInDateRange(noCardDateRange_frist); + await configStep.addNewTimeRange(); + await configStep.typeInDateRange(noCardDateRange_second); + await configStep.selectAllRequiredMetrics(); + await configStep.selectBoardMetricsOnly(); + await configStep.goToMetrics(); + await metricsStep.checkBoardNoCard(); + await metricsStep.validateNextButtonNotClickable(); + await metricsStep.goToPreviousStep(); + + await configStep.typeInDateRange(rightDateRange_frist); + await configStep.typeInDateRange(rightDateRange_second); + await configStep.selectAllRequiredMetrics(); + await configStep.goToMetrics(); + await metricsStep.deselectBranch(chartStepData.unSelectBranch); + await metricsStep.addBranch(chartStepData.addNewBranch); + await metricsStep.checkBranchIsInvalid(); + await metricsStep.validateNextButtonNotClickable(); +}); diff --git a/frontend/e2e/specs/side-path/unhappy-path.spec.ts b/frontend/e2e/specs/side-path/unhappy-path.spec.ts index 68f0789a8d..ba00a2d2b3 100644 --- a/frontend/e2e/specs/side-path/unhappy-path.spec.ts +++ b/frontend/e2e/specs/side-path/unhappy-path.spec.ts @@ -56,6 +56,9 @@ test('unhappy path when import file', async ({ homePage, configStep, metricsStep await configStep.goToMetrics(); await metricsStep.checkBoardNoCard(); + await metricsStep.addNewPipelineAndSelectOrgAndName(); + await metricsStep.checkPipelineLength(2); + await metricsStep.removePipeline(1); await metricsStep.checkPipelineFillNoStep(importUnhappyPathProjectFromFile.deployment); await metricsStep.goToPreviousStep(); await configStep.typeInDateRange(dateRange); @@ -72,7 +75,7 @@ test('unhappy path when import file', async ({ homePage, configStep, metricsStep await metricsStep.selectCrews(modifiedCorrectProjectFromFile.crews); await metricsStep.deselectBranch(modifiedCorrectProjectFromFile.deletedBranch); await metricsStep.addNewPipelineAndSelectSamePipeline(importUnhappyPathProjectFromFile.deployment); - await metricsStep.RemoveFirstNewPipeline(); + await metricsStep.removePipeline(1); await metricsStep.selectDoneHeartbeatState(ModifiedhbStateData[6]); await metricsStep.validateNextButtonNotClickable(); await metricsStep.selectDoneHeartbeatState(hbStateData[6]); diff --git a/frontend/jest.config.json b/frontend/jest.config.json index 833a472a39..8c4a848129 100644 --- a/frontend/jest.config.json +++ b/frontend/jest.config.json @@ -5,6 +5,7 @@ ".+\\.svg": "/__tests__/__mocks__/svgTransformer.ts" }, "globalSetup": "./global-setup.ts", + "setupFiles": ["./jest.polyfills.ts"], "setupFilesAfterEnv": ["/__tests__/setupTests.ts"], "testEnvironment": "jsdom", "testRegex": "/__tests__/.*(test|spec)\\.(jsx?|tsx?|ts?)$", @@ -28,5 +29,8 @@ "statements": 100 } }, - "testTimeout": 30000 + "testTimeout": 30000, + "testEnvironmentOptions": { + "customExportConditions": [""] + } } diff --git a/frontend/jest.polyfills.ts b/frontend/jest.polyfills.ts new file mode 100644 index 0000000000..575d8e1c04 --- /dev/null +++ b/frontend/jest.polyfills.ts @@ -0,0 +1,32 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/** + * @note The block below contains polyfills for Node.js globals + * required for Jest to function when running JSDOM tests. + * These HAVE to be require's and HAVE to be in this exact + * order, since "undici" depends on the "TextEncoder" global API. + * + * Consider migrating to a more modern test runner if + * you don't want to deal with this. + */ + +import { ReadableStream } from 'web-streams-polyfill'; +import { TextDecoder, TextEncoder } from 'node:util'; + +Object.defineProperties(globalThis, { + TextDecoder: { value: TextDecoder }, + TextEncoder: { value: TextEncoder }, + ReadableStream: { value: ReadableStream }, +}); + +import { fetch, Headers, FormData, Request, Response } from 'undici'; +import { Blob } from 'node:buffer'; + +Object.defineProperties(globalThis, { + fetch: { value: fetch, writable: true }, + Blob: { value: Blob }, + // File: { value: File }, + Headers: { value: Headers }, + FormData: { value: FormData }, + Request: { value: Request }, + Response: { value: Response }, +}); diff --git a/frontend/package.json b/frontend/package.json index aff0ab0335..a100def25d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,8 +14,8 @@ "audit": "npx audit-ci@^6 --config ./audit-ci.jsonc", "test": "jest", "test:watch": "jest --watchAll", - "coverage": "jest --env=jsdom --watchAll=false --coverage", - "coverage:silent": "jest --env=jsdom --watchAll=false --coverage --silent", + "coverage": "jest --watchAll=false --coverage", + "coverage:silent": "jest --watchAll=false --coverage --silent", "pre-e2e": "./scripts/generate-config-files.sh", "e2e": "pnpm exec playwright test", "e2e:major": "pnpm run pre-e2e && pnpm exec playwright test /major-path --project='Google Chrome'", @@ -45,67 +45,72 @@ }, "dependencies": { "@emotion/react": "^11.11.4", - "@emotion/styled": "^11.11.0", - "@fontsource/roboto": "^5.0.12", - "@mui/icons-material": "^5.15.14", - "@mui/material": "^5.15.14", - "@mui/x-date-pickers": "^7.0.0", - "@reduxjs/toolkit": "^2.2.2", + "@emotion/styled": "^11.11.5", + "@fontsource/roboto": "^5.0.13", + "@hookform/resolvers": "^3.3.4", + "@mui/icons-material": "^5.15.16", + "@mui/material": "^5.15.16", + "@mui/x-date-pickers": "^7.3.2", + "@reduxjs/toolkit": "^2.2.3", "axios": "^1.6.8", - "dayjs": "^1.11.10", + "dayjs": "^1.11.11", "lodash": "^4.17.21", "lodash.camelcase": "^4.3.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", "react-error-boundary": "^4.0.13", - "react-redux": "^9.0.0", - "react-router-dom": "^6.22.3", - "typescript": "^5.4.2", - "vite": "^5.2.2", - "vite-plugin-pwa": "^0.19.5" + "react-hook-form": "^7.51.3", + "react-redux": "^9.1.2", + "react-router-dom": "^6.23.0", + "typescript": "^5.4.5", + "vite": "^5.2.11", + "vite-plugin-pwa": "^0.20.0", + "yup": "^1.4.0" }, "devDependencies": { - "@dotenvx/dotenvx": "^0.27.0", - "@playwright/test": "^1.42.1", - "@testing-library/jest-dom": "^6.4.2", - "@testing-library/react": "^14.2.2", + "@dotenvx/dotenvx": "^0.37.1", + "@playwright/test": "^1.43.1", + "@testing-library/jest-dom": "^6.4.3", + "@testing-library/react": "^15.0.6", "@testing-library/user-event": "^14.5.2", "@types/jest": "^29.5.12", "@types/lodash": "^4.17.0", "@types/lodash.camelcase": "^4.3.9", - "@types/node": "^20.11.30", + "@types/node": "^20.12.8", "@types/node-fetch": "^2.6.11", - "@types/react": "^18.2.67", - "@types/react-dom": "^18.2.22", + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.0", "@types/react-redux": "^7.1.33", - "@typescript-eslint/eslint-plugin": "^7.3.1", - "@typescript-eslint/parser": "^7.3.1", + "@typescript-eslint/eslint-plugin": "^7.8.0", + "@typescript-eslint/parser": "^7.8.0", "@vitejs/plugin-react-swc": "^3.6.0", "audit-ci": "^6.6.1", - "autoprefixer": "^10.4.18", + "autoprefixer": "^10.4.19", "csv-parse": "^5.5.5", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.29.1", - "eslint-plugin-n": "^16.6.2", + "eslint-plugin-n": "^17.4.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react": "^7.34.1", - "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-hooks": "^4.6.2", "execa": "^8.0.1", "husky": "^9.0.11", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "license-compliance": "^2.0.1", + "license-compliance": "^3.0.0", "lint-staged": "^15.2.2", - "msw": "^1.3.3", + "msw": "^2.2.14", "node-fetch": "^3.3.2", "prettier": "3.2.5", - "prettier-plugin-sort-imports": "^1.8.4", + "prettier-plugin-sort-imports": "^1.8.5", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", - "tsc-files": "^1.1.4" + "tsc-files": "^1.1.4", + "undici": "^6.15.0", + "web-streams-polyfill": "^4.0.0" }, "engines": { "node": ">=16.18.0" diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts index 08b3e4c619..17a48128a7 100644 --- a/frontend/playwright.config.ts +++ b/frontend/playwright.config.ts @@ -26,7 +26,7 @@ export default defineConfig({ /* Retry on CI only */ retries: process.env.CI ? 1 : 0, /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, + workers: process.env.CI ? '80%' : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ outputDir: './e2e/test-results', reporter: [['html', { open: process.env.CI ? 'never' : 'on-failure', outputFolder: './e2e/reports/html' }], ['list']], @@ -35,6 +35,7 @@ export default defineConfig({ /* Base URL to use in actions like `await page.goto('/')`. */ baseURL: process.env.APP_ORIGIN, viewport: VIEWPORT_DEFAULT, + timezoneId: 'Asia/Shanghai', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on', @@ -53,17 +54,17 @@ export default defineConfig({ use: { ...devices['Desktop Safari'] }, }, - // { - // name: 'firefox', - // use: { ...devices['Desktop Firefox'] }, - // }, - - /* Test against Tablet viewports. */ { - name: 'Tablet', - use: devices['iPad landscape'], + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, }, + /* Test against Tablet viewports. */ + // { + // name: 'Tablet', + // use: devices['iPad landscape'], + // }, + /* Test against branded browsers. */ { name: 'Microsoft Edge', diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 15c12aa7db..e2e9253f12 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -7,31 +7,34 @@ settings: dependencies: '@emotion/react': specifier: ^11.11.4 - version: 11.11.4(@types/react@18.2.67)(react@18.2.0) + version: 11.11.4(@types/react@18.3.1)(react@18.3.1) '@emotion/styled': - specifier: ^11.11.0 - version: 11.11.0(@emotion/react@11.11.4)(@types/react@18.2.67)(react@18.2.0) + specifier: ^11.11.5 + version: 11.11.5(@emotion/react@11.11.4)(@types/react@18.3.1)(react@18.3.1) '@fontsource/roboto': - specifier: ^5.0.12 - version: 5.0.12 + specifier: ^5.0.13 + version: 5.0.13 + '@hookform/resolvers': + specifier: ^3.3.4 + version: 3.3.4(react-hook-form@7.51.3) '@mui/icons-material': - specifier: ^5.15.14 - version: 5.15.14(@mui/material@5.15.14)(@types/react@18.2.67)(react@18.2.0) + specifier: ^5.15.16 + version: 5.15.16(@mui/material@5.15.16)(@types/react@18.3.1)(react@18.3.1) '@mui/material': - specifier: ^5.15.14 - version: 5.15.14(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0) + specifier: ^5.15.16 + version: 5.15.16(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1) '@mui/x-date-pickers': - specifier: ^7.0.0 - version: 7.0.0(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(@mui/material@5.15.14)(@types/react@18.2.67)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0) + specifier: ^7.3.2 + version: 7.3.2(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@mui/material@5.15.16)(@types/react@18.3.1)(dayjs@1.11.11)(react-dom@18.3.1)(react@18.3.1) '@reduxjs/toolkit': - specifier: ^2.2.2 - version: 2.2.2(react-redux@9.1.0)(react@18.2.0) + specifier: ^2.2.3 + version: 2.2.3(react-redux@9.1.2)(react@18.3.1) axios: specifier: ^1.6.8 version: 1.6.8 dayjs: - specifier: ^1.11.10 - version: 1.11.10 + specifier: ^1.11.11 + version: 1.11.11 lodash: specifier: ^4.17.21 version: 4.17.21 @@ -39,46 +42,52 @@ dependencies: specifier: ^4.3.0 version: 4.3.0 react: - specifier: ^18.2.0 - version: 18.2.0 + specifier: ^18.3.1 + version: 18.3.1 react-dom: - specifier: ^18.2.0 - version: 18.2.0(react@18.2.0) + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) react-error-boundary: specifier: ^4.0.13 - version: 4.0.13(react@18.2.0) + version: 4.0.13(react@18.3.1) + react-hook-form: + specifier: ^7.51.3 + version: 7.51.3(react@18.3.1) react-redux: - specifier: ^9.0.0 - version: 9.1.0(@types/react@18.2.67)(react@18.2.0)(redux@5.0.1) + specifier: ^9.1.2 + version: 9.1.2(@types/react@18.3.1)(react@18.3.1)(redux@5.0.1) react-router-dom: - specifier: ^6.22.3 - version: 6.22.3(react-dom@18.2.0)(react@18.2.0) + specifier: ^6.23.0 + version: 6.23.0(react-dom@18.3.1)(react@18.3.1) typescript: - specifier: ^5.4.2 - version: 5.4.2 + specifier: ^5.4.5 + version: 5.4.5 vite: - specifier: ^5.2.2 - version: 5.2.6(@types/node@20.11.30) + specifier: ^5.2.11 + version: 5.2.11(@types/node@20.12.8) vite-plugin-pwa: - specifier: ^0.19.5 - version: 0.19.5(vite@5.2.6)(workbox-build@7.0.0)(workbox-window@7.0.0) + specifier: ^0.20.0 + version: 0.20.0(vite@5.2.11)(workbox-build@7.1.0)(workbox-window@7.1.0) + yup: + specifier: ^1.4.0 + version: 1.4.0 devDependencies: '@dotenvx/dotenvx': - specifier: ^0.27.0 - version: 0.27.0 + specifier: ^0.37.1 + version: 0.37.1 '@playwright/test': - specifier: ^1.42.1 - version: 1.42.1 + specifier: ^1.43.1 + version: 1.43.1 '@testing-library/jest-dom': - specifier: ^6.4.2 - version: 6.4.2(@types/jest@29.5.12)(jest@29.7.0) + specifier: ^6.4.3 + version: 6.4.5(@types/jest@29.5.12)(jest@29.7.0) '@testing-library/react': - specifier: ^14.2.2 - version: 14.2.2(react-dom@18.2.0)(react@18.2.0) + specifier: ^15.0.6 + version: 15.0.6(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1) '@testing-library/user-event': specifier: ^14.5.2 - version: 14.5.2(@testing-library/dom@9.3.4) + version: 14.5.2(@testing-library/dom@10.1.0) '@types/jest': specifier: ^29.5.12 version: 29.5.12 @@ -89,35 +98,35 @@ devDependencies: specifier: ^4.3.9 version: 4.3.9 '@types/node': - specifier: ^20.11.30 - version: 20.11.30 + specifier: ^20.12.8 + version: 20.12.8 '@types/node-fetch': specifier: ^2.6.11 version: 2.6.11 '@types/react': - specifier: ^18.2.67 - version: 18.2.67 + specifier: ^18.3.1 + version: 18.3.1 '@types/react-dom': - specifier: ^18.2.22 - version: 18.2.22 + specifier: ^18.3.0 + version: 18.3.0 '@types/react-redux': specifier: ^7.1.33 version: 7.1.33 '@typescript-eslint/eslint-plugin': - specifier: ^7.3.1 - version: 7.4.0(@typescript-eslint/parser@7.4.0)(eslint@8.57.0)(typescript@5.4.2) + specifier: ^7.8.0 + version: 7.8.0(@typescript-eslint/parser@7.8.0)(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/parser': - specifier: ^7.3.1 - version: 7.4.0(eslint@8.57.0)(typescript@5.4.2) + specifier: ^7.8.0 + version: 7.8.0(eslint@8.57.0)(typescript@5.4.5) '@vitejs/plugin-react-swc': specifier: ^3.6.0 - version: 3.6.0(vite@5.2.6) + version: 3.6.0(vite@5.2.11) audit-ci: specifier: ^6.6.1 version: 6.6.1 autoprefixer: - specifier: ^10.4.18 - version: 10.4.18(postcss@8.4.38) + specifier: ^10.4.19 + version: 10.4.19(postcss@8.4.38) csv-parse: specifier: ^5.5.5 version: 5.5.5 @@ -129,10 +138,10 @@ devDependencies: version: 9.1.0(eslint@8.57.0) eslint-plugin-import: specifier: ^2.29.1 - version: 2.29.1(@typescript-eslint/parser@7.4.0)(eslint@8.57.0) + version: 2.29.1(@typescript-eslint/parser@7.8.0)(eslint@8.57.0) eslint-plugin-n: - specifier: ^16.6.2 - version: 16.6.2(eslint@8.57.0) + specifier: ^17.4.0 + version: 17.4.0(eslint@8.57.0) eslint-plugin-prettier: specifier: ^5.1.3 version: 5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5) @@ -143,8 +152,8 @@ devDependencies: specifier: ^7.34.1 version: 7.34.1(eslint@8.57.0) eslint-plugin-react-hooks: - specifier: ^4.6.0 - version: 4.6.0(eslint@8.57.0) + specifier: ^4.6.2 + version: 4.6.2(eslint@8.57.0) execa: specifier: ^8.0.1 version: 8.0.1 @@ -156,19 +165,19 @@ devDependencies: version: 3.0.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.30)(ts-node@10.9.2) + version: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2) jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0 license-compliance: - specifier: ^2.0.1 - version: 2.0.1(typescript@5.4.2) + specifier: ^3.0.0 + version: 3.0.0(typescript@5.4.5) lint-staged: specifier: ^15.2.2 version: 15.2.2 msw: - specifier: ^1.3.3 - version: 1.3.3(typescript@5.4.2) + specifier: ^2.2.14 + version: 2.2.14(typescript@5.4.5) node-fetch: specifier: ^3.3.2 version: 3.3.2 @@ -176,17 +185,23 @@ devDependencies: specifier: 3.2.5 version: 3.2.5 prettier-plugin-sort-imports: - specifier: ^1.8.4 - version: 1.8.4(typescript@5.4.2) + specifier: ^1.8.5 + version: 1.8.5(typescript@5.4.5) ts-jest: specifier: ^29.1.2 - version: 29.1.2(@babel/core@7.24.3)(jest@29.7.0)(typescript@5.4.2) + version: 29.1.2(@babel/core@7.24.3)(jest@29.7.0)(typescript@5.4.5) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.11.30)(typescript@5.4.2) + version: 10.9.2(@types/node@20.12.8)(typescript@5.4.5) tsc-files: specifier: ^1.1.4 - version: 1.1.4(typescript@5.4.2) + version: 1.1.4(typescript@5.4.5) + undici: + specifier: ^6.15.0 + version: 6.15.0 + web-streams-polyfill: + specifier: ^4.0.0 + version: 4.0.0 packages: @@ -1541,13 +1556,13 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.1 + dev: false /@babel/runtime@7.24.4: resolution: {integrity: sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.1 - dev: false /@babel/template@7.24.0: resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} @@ -1586,6 +1601,18 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true + /@bundled-es-modules/cookie@2.0.0: + resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==} + dependencies: + cookie: 0.5.0 + dev: true + + /@bundled-es-modules/statuses@1.0.1: + resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} + dependencies: + statuses: 2.0.1 + dev: true + /@colors/colors@1.6.0: resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} @@ -1606,41 +1633,29 @@ packages: kuler: 2.0.0 dev: true - /@dotenvx/dotenvx@0.27.0: - resolution: {integrity: sha512-zB01t8/4HxERp2qqz3CHniBUm6LtWK/8U6snewLY1zxeletpBhDKo5rgHslOoKPeRVWKLshSvHYNTEEXVlzFRQ==} + /@dotenvx/dotenvx@0.37.1: + resolution: {integrity: sha512-CN8A4cxuIQ5eRIaWK6qeRAYXPtt5Mo5XHw6o9kP+VWYlTg8UY3k426tCxcgrMRTI/GKMyu8wryHng+thUFCNYg==} hasBin: true dependencies: - '@inquirer/prompts': 3.3.2 + '@inquirer/confirm': 2.0.17 arch: 2.2.0 - boxen: 5.1.2 chalk: 4.1.2 commander: 11.1.0 conf: 10.2.0 - configstore: 5.0.1 + diff: 5.2.0 dotenv: 16.4.5 dotenv-expand: 11.0.6 execa: 5.1.1 glob: 10.3.10 - got: 11.8.6 - has-yarn: 2.1.0 ignore: 5.3.1 - import-lazy: 2.1.0 - is-ci: 2.0.0 - is-installed-globally: 0.4.0 - is-npm: 5.0.0 is-wsl: 2.2.0 - is-yarn-global: 0.3.0 object-treeify: 1.1.33 open: 8.4.2 ora: 5.4.1 - package-json: 7.0.0 - pupa: 2.1.1 semver: 7.6.0 - semver-diff: 3.1.1 undici: 5.28.4 which: 4.0.0 winston: 3.12.0 - xdg-basedir: 4.0.0 xxhashjs: 0.2.2 dev: true @@ -1684,7 +1699,7 @@ packages: resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} dev: false - /@emotion/react@11.11.4(@types/react@18.2.67)(react@18.2.0): + /@emotion/react@11.11.4(@types/react@18.3.1)(react@18.3.1): resolution: {integrity: sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==} peerDependencies: '@types/react': '*' @@ -1697,12 +1712,12 @@ packages: '@emotion/babel-plugin': 11.11.0 '@emotion/cache': 11.11.0 '@emotion/serialize': 1.1.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) + '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.3.1) '@emotion/utils': 1.2.1 '@emotion/weak-memoize': 0.3.1 - '@types/react': 18.2.67 + '@types/react': 18.3.1 hoist-non-react-statics: 3.3.2 - react: 18.2.0 + react: 18.3.1 dev: false /@emotion/serialize@1.1.3: @@ -1715,12 +1730,22 @@ packages: csstype: 3.1.3 dev: false + /@emotion/serialize@1.1.4: + resolution: {integrity: sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==} + dependencies: + '@emotion/hash': 0.9.1 + '@emotion/memoize': 0.8.1 + '@emotion/unitless': 0.8.1 + '@emotion/utils': 1.2.1 + csstype: 3.1.3 + dev: false + /@emotion/sheet@1.2.2: resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==} dev: false - /@emotion/styled@11.11.0(@emotion/react@11.11.4)(@types/react@18.2.67)(react@18.2.0): - resolution: {integrity: sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==} + /@emotion/styled@11.11.5(@emotion/react@11.11.4)(@types/react@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==} peerDependencies: '@emotion/react': ^11.0.0-rc.0 '@types/react': '*' @@ -1729,27 +1754,27 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 '@emotion/babel-plugin': 11.11.0 '@emotion/is-prop-valid': 1.2.2 - '@emotion/react': 11.11.4(@types/react@18.2.67)(react@18.2.0) - '@emotion/serialize': 1.1.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) + '@emotion/react': 11.11.4(@types/react@18.3.1)(react@18.3.1) + '@emotion/serialize': 1.1.4 + '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.3.1) '@emotion/utils': 1.2.1 - '@types/react': 18.2.67 - react: 18.2.0 + '@types/react': 18.3.1 + react: 18.3.1 dev: false /@emotion/unitless@0.8.1: resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} dev: false - /@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.2.0): + /@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.3.1): resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==} peerDependencies: react: '>=16.8.0' dependencies: - react: 18.2.0 + react: 18.3.1 dev: false /@emotion/utils@1.2.1: @@ -1999,23 +2024,23 @@ packages: '@floating-ui/utils': 0.2.1 dev: false - /@floating-ui/react-dom@2.0.8(react-dom@18.2.0)(react@18.2.0): + /@floating-ui/react-dom@2.0.8(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' dependencies: '@floating-ui/dom': 1.6.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: false /@floating-ui/utils@0.2.1: resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==} dev: false - /@fontsource/roboto@5.0.12: - resolution: {integrity: sha512-x0o17jvgoSSbS9OZnUX2+xJmVRvVCfeaYJjkS7w62iN7CuJWtMf5vJj8LqgC7ibqIkitOHVW+XssRjgrcHn62g==} + /@fontsource/roboto@5.0.13: + resolution: {integrity: sha512-j61DHjsdUCKMXSdNLTOxcG701FWnF0jcqNNQi2iPCDxU8seN/sMxeh62dC++UiagCWq9ghTypX+Pcy7kX+QOeQ==} dev: false /@hapi/hoek@9.3.0: @@ -2028,6 +2053,14 @@ packages: '@hapi/hoek': 9.3.0 dev: true + /@hookform/resolvers@3.3.4(react-hook-form@7.51.3): + resolution: {integrity: sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==} + peerDependencies: + react-hook-form: ^7.0.0 + dependencies: + react-hook-form: 7.51.3(react@18.3.1) + dev: false + /@humanwhocodes/config-array@0.11.14: resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} @@ -2048,24 +2081,21 @@ packages: resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} dev: true - /@inquirer/checkbox@1.5.2: - resolution: {integrity: sha512-CifrkgQjDkUkWexmgYYNyB5603HhTHI91vLFeQXh6qrTKiCMVASol01Rs1cv6LP/A2WccZSRlJKZhbaBIs/9ZA==} + /@inquirer/confirm@2.0.17: + resolution: {integrity: sha512-EqzhGryzmGpy2aJf6LxJVhndxYmFs+m8cxXzf8nejb1DE3sabf6mUgBcp4J0jAUEiAcYzqmkqRr7LPFh/WdnXA==} engines: {node: '>=14.18.0'} dependencies: '@inquirer/core': 6.0.0 '@inquirer/type': 1.2.1 - ansi-escapes: 4.3.2 chalk: 4.1.2 - figures: 3.2.0 dev: true - /@inquirer/confirm@2.0.17: - resolution: {integrity: sha512-EqzhGryzmGpy2aJf6LxJVhndxYmFs+m8cxXzf8nejb1DE3sabf6mUgBcp4J0jAUEiAcYzqmkqRr7LPFh/WdnXA==} - engines: {node: '>=14.18.0'} + /@inquirer/confirm@3.1.6: + resolution: {integrity: sha512-Mj4TU29g6Uy+37UtpA8UpEOI2icBfpCwSW1QDtfx60wRhUy90s/kHPif2OXSSvuwDQT1lhAYRWUfkNf9Tecxvg==} + engines: {node: '>=18'} dependencies: - '@inquirer/core': 6.0.0 - '@inquirer/type': 1.2.1 - chalk: 4.1.2 + '@inquirer/core': 8.1.0 + '@inquirer/type': 1.3.1 dev: true /@inquirer/core@6.0.0: @@ -2074,7 +2104,7 @@ packages: dependencies: '@inquirer/type': 1.2.1 '@types/mute-stream': 0.0.4 - '@types/node': 20.11.30 + '@types/node': 20.12.8 '@types/wrap-ansi': 3.0.0 ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -2088,78 +2118,28 @@ packages: wrap-ansi: 6.2.0 dev: true - /@inquirer/editor@1.2.15: - resolution: {integrity: sha512-gQ77Ls09x5vKLVNMH9q/7xvYPT6sIs5f7URksw+a2iJZ0j48tVS6crLqm2ugG33tgXHIwiEqkytY60Zyh5GkJQ==} - engines: {node: '>=14.18.0'} - dependencies: - '@inquirer/core': 6.0.0 - '@inquirer/type': 1.2.1 - chalk: 4.1.2 - external-editor: 3.1.0 - dev: true - - /@inquirer/expand@1.1.16: - resolution: {integrity: sha512-TGLU9egcuo+s7PxphKUCnJnpCIVY32/EwPCLLuu+gTvYiD8hZgx8Z2niNQD36sa6xcfpdLY6xXDBiL/+g1r2XQ==} - engines: {node: '>=14.18.0'} - dependencies: - '@inquirer/core': 6.0.0 - '@inquirer/type': 1.2.1 - chalk: 4.1.2 - figures: 3.2.0 - dev: true - - /@inquirer/input@1.2.16: - resolution: {integrity: sha512-Ou0LaSWvj1ni+egnyQ+NBtfM1885UwhRCMtsRt2bBO47DoC1dwtCa+ZUNgrxlnCHHF0IXsbQHYtIIjFGAavI4g==} - engines: {node: '>=14.18.0'} - dependencies: - '@inquirer/core': 6.0.0 - '@inquirer/type': 1.2.1 - chalk: 4.1.2 - dev: true - - /@inquirer/password@1.1.16: - resolution: {integrity: sha512-aZYZVHLUXZ2gbBot+i+zOJrks1WaiI95lvZCn1sKfcw6MtSSlYC8uDX8sTzQvAsQ8epHoP84UNvAIT0KVGOGqw==} - engines: {node: '>=14.18.0'} + /@inquirer/core@8.1.0: + resolution: {integrity: sha512-kfx0SU9nWgGe1f03ao/uXc85SFH1v2w3vQVH7QDGjKxdtJz+7vPitFtG++BTyJMYyYgH8MpXigutcXJeiQwVRw==} + engines: {node: '>=18'} dependencies: - '@inquirer/core': 6.0.0 - '@inquirer/type': 1.2.1 + '@inquirer/figures': 1.0.1 + '@inquirer/type': 1.3.1 + '@types/mute-stream': 0.0.4 + '@types/node': 20.12.8 + '@types/wrap-ansi': 3.0.0 ansi-escapes: 4.3.2 chalk: 4.1.2 + cli-spinners: 2.9.2 + cli-width: 4.1.0 + mute-stream: 1.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 dev: true - /@inquirer/prompts@3.3.2: - resolution: {integrity: sha512-k52mOMRvTUejrqyF1h8Z07chC+sbaoaUYzzr1KrJXyj7yaX7Nrh0a9vktv8TuocRwIJOQMaj5oZEmkspEcJFYQ==} - engines: {node: '>=14.18.0'} - dependencies: - '@inquirer/checkbox': 1.5.2 - '@inquirer/confirm': 2.0.17 - '@inquirer/core': 6.0.0 - '@inquirer/editor': 1.2.15 - '@inquirer/expand': 1.1.16 - '@inquirer/input': 1.2.16 - '@inquirer/password': 1.1.16 - '@inquirer/rawlist': 1.2.16 - '@inquirer/select': 1.3.3 - dev: true - - /@inquirer/rawlist@1.2.16: - resolution: {integrity: sha512-pZ6TRg2qMwZAOZAV6TvghCtkr53dGnK29GMNQ3vMZXSNguvGqtOVc4j/h1T8kqGJFagjyfBZhUPGwNS55O5qPQ==} - engines: {node: '>=14.18.0'} - dependencies: - '@inquirer/core': 6.0.0 - '@inquirer/type': 1.2.1 - chalk: 4.1.2 - dev: true - - /@inquirer/select@1.3.3: - resolution: {integrity: sha512-RzlRISXWqIKEf83FDC9ZtJ3JvuK1l7aGpretf41BCWYrvla2wU8W8MTRNMiPrPJ+1SIqrRC1nZdZ60hD9hRXLg==} - engines: {node: '>=14.18.0'} - dependencies: - '@inquirer/core': 6.0.0 - '@inquirer/type': 1.2.1 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - figures: 3.2.0 + /@inquirer/figures@1.0.1: + resolution: {integrity: sha512-mtup3wVKia3ZwULPHcbs4Mor8Voi+iIXEWD7wCNbIO6lYR62oPCTQyrddi5OMYVXHzeCSoneZwJuS8sBvlEwDw==} + engines: {node: '>=18'} dev: true /@inquirer/type@1.2.1: @@ -2167,6 +2147,11 @@ packages: engines: {node: '>=18'} dev: true + /@inquirer/type@1.3.1: + resolution: {integrity: sha512-Pe3PFccjPVJV1vtlfVvm9OnlbxqdnP5QcscFEFEnK5quChf1ufZtM0r8mR5ToWHMxZOh0s8o/qp9ANGRTo/DAw==} + engines: {node: '>=18'} + dev: true + /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -2200,7 +2185,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.11.30 + '@types/node': 20.12.8 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -2221,14 +2206,14 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.30 + '@types/node': 20.12.8 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.11.30)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -2256,7 +2241,7 @@ packages: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.30 + '@types/node': 20.12.8 jest-mock: 29.7.0 dev: true @@ -2283,7 +2268,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.11.30 + '@types/node': 20.12.8 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -2316,7 +2301,7 @@ packages: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 20.11.30 + '@types/node': 20.12.8 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -2404,7 +2389,7 @@ packages: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.11.30 + '@types/node': 20.12.8 '@types/yargs': 17.0.32 chalk: 4.1.2 dev: true @@ -2448,31 +2433,24 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true - /@mswjs/cookies@0.2.2: - resolution: {integrity: sha512-mlN83YSrcFgk7Dm1Mys40DLssI1KdJji2CMKN8eOlBqsTADYzj2+jWzsANsUTFbxDMWPD5e9bfA1RGqBpS3O1g==} - engines: {node: '>=14'} - dependencies: - '@types/set-cookie-parser': 2.4.7 - set-cookie-parser: 2.6.0 + /@mswjs/cookies@1.1.0: + resolution: {integrity: sha512-0ZcCVQxifZmhwNBoQIrystCb+2sWBY2Zw8lpfJBPCHGCA/HWqehITeCRVIv4VMy8MPlaHo2w2pTHFV2pFfqKPw==} + engines: {node: '>=18'} dev: true - /@mswjs/interceptors@0.17.10: - resolution: {integrity: sha512-N8x7eSLGcmUFNWZRxT1vsHvypzIRgQYdG0rJey/rZCy6zT/30qDt8Joj7FxzGNLSwXbeZqJOMqDurp7ra4hgbw==} - engines: {node: '>=14'} + /@mswjs/interceptors@0.26.15: + resolution: {integrity: sha512-HM47Lu1YFmnYHKMBynFfjCp0U/yRskHj/8QEJW0CBEPOlw8Gkmjfll+S9b8M7V5CNDw2/ciRxjjnWeaCiblSIQ==} + engines: {node: '>=18'} dependencies: - '@open-draft/until': 1.0.3 - '@types/debug': 4.1.12 - '@xmldom/xmldom': 0.8.10 - debug: 4.3.4 - headers-polyfill: 3.2.5 + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 outvariant: 1.4.2 - strict-event-emitter: 0.2.8 - web-encoding: 1.1.5 - transitivePeerDependencies: - - supports-color + strict-event-emitter: 0.5.1 dev: true - /@mui/base@5.0.0-beta.40(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0): + /@mui/base@5.0.0-beta.40(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==} engines: {node: '>=12.0.0'} peerDependencies: @@ -2483,24 +2461,24 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.24.1 - '@floating-ui/react-dom': 2.0.8(react-dom@18.2.0)(react@18.2.0) - '@mui/types': 7.2.14(@types/react@18.2.67) - '@mui/utils': 5.15.14(@types/react@18.2.67)(react@18.2.0) + '@babel/runtime': 7.24.4 + '@floating-ui/react-dom': 2.0.8(react-dom@18.3.1)(react@18.3.1) + '@mui/types': 7.2.14(@types/react@18.3.1) + '@mui/utils': 5.15.14(@types/react@18.3.1)(react@18.3.1) '@popperjs/core': 2.11.8 - '@types/react': 18.2.67 + '@types/react': 18.3.1 clsx: 2.1.0 prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: false - /@mui/core-downloads-tracker@5.15.14: - resolution: {integrity: sha512-on75VMd0XqZfaQW+9pGjSNiqW+ghc5E2ZSLRBXwcXl/C4YzjfyjrLPhrEpKnR9Uym9KXBvxrhoHfPcczYHweyA==} + /@mui/core-downloads-tracker@5.15.16: + resolution: {integrity: sha512-PTIbMJs5C/vYMfyJNW8ArOezh4eyHkg2pTeA7bBxh2kLP1Uzs0Nm+krXWbWGJPwTWjM8EhnDrr4aCF26+2oleg==} dev: false - /@mui/icons-material@5.15.14(@mui/material@5.15.14)(@types/react@18.2.67)(react@18.2.0): - resolution: {integrity: sha512-vj/51k7MdFmt+XVw94sl30SCvGx6+wJLsNYjZRgxhS6y3UtnWnypMOsm3Kmg8TN+P0dqwsjy4/fX7B1HufJIhw==} + /@mui/icons-material@5.15.16(@mui/material@5.15.16)(@types/react@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-s8vYbyACzTNZRKv+20fCfVXJwJqNcVotns2EKnu1wmAga6wv2LAo5kB1d5yqQqZlMFtp34EJvRXf7cy8X0tJVA==} engines: {node: '>=12.0.0'} peerDependencies: '@mui/material': ^5.0.0 @@ -2510,14 +2488,14 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.24.1 - '@mui/material': 5.15.14(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.67 - react: 18.2.0 + '@babel/runtime': 7.24.4 + '@mui/material': 5.15.16(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1) + '@types/react': 18.3.1 + react: 18.3.1 dev: false - /@mui/material@5.15.14(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-kEbRw6fASdQ1SQ7LVdWR5OlWV3y7Y54ZxkLzd6LV5tmz+NpO3MJKZXSfgR0LHMP7meKsPiMm4AuzV0pXDpk/BQ==} + /@mui/material@5.15.16(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-ery2hFReewko9gpDBqOr2VmXwQG9ifXofPhGzIx09/b9JqCQC/06kZXZDGGrOTpIddK9HlIf4yrS+G70jPAzUQ==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.5.0 @@ -2533,26 +2511,26 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.24.1 - '@emotion/react': 11.11.4(@types/react@18.2.67)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.4)(@types/react@18.2.67)(react@18.2.0) - '@mui/base': 5.0.0-beta.40(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0) - '@mui/core-downloads-tracker': 5.15.14 - '@mui/system': 5.15.14(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(@types/react@18.2.67)(react@18.2.0) - '@mui/types': 7.2.14(@types/react@18.2.67) - '@mui/utils': 5.15.14(@types/react@18.2.67)(react@18.2.0) - '@types/react': 18.2.67 + '@babel/runtime': 7.24.4 + '@emotion/react': 11.11.4(@types/react@18.3.1)(react@18.3.1) + '@emotion/styled': 11.11.5(@emotion/react@11.11.4)(@types/react@18.3.1)(react@18.3.1) + '@mui/base': 5.0.0-beta.40(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1) + '@mui/core-downloads-tracker': 5.15.16 + '@mui/system': 5.15.15(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.3.1)(react@18.3.1) + '@mui/types': 7.2.14(@types/react@18.3.1) + '@mui/utils': 5.15.14(@types/react@18.3.1)(react@18.3.1) + '@types/react': 18.3.1 '@types/react-transition-group': 4.4.10 clsx: 2.1.0 csstype: 3.1.3 prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) react-is: 18.2.0 - react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) + react-transition-group: 4.4.5(react-dom@18.3.1)(react@18.3.1) dev: false - /@mui/private-theming@5.15.14(@types/react@18.2.67)(react@18.2.0): + /@mui/private-theming@5.15.14(@types/react@18.3.1)(react@18.3.1): resolution: {integrity: sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==} engines: {node: '>=12.0.0'} peerDependencies: @@ -2562,14 +2540,14 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.24.1 - '@mui/utils': 5.15.14(@types/react@18.2.67)(react@18.2.0) - '@types/react': 18.2.67 + '@babel/runtime': 7.24.4 + '@mui/utils': 5.15.14(@types/react@18.3.1)(react@18.3.1) + '@types/react': 18.3.1 prop-types: 15.8.1 - react: 18.2.0 + react: 18.3.1 dev: false - /@mui/styled-engine@5.15.14(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0): + /@mui/styled-engine@5.15.14(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(react@18.3.1): resolution: {integrity: sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==} engines: {node: '>=12.0.0'} peerDependencies: @@ -2582,16 +2560,16 @@ packages: '@emotion/styled': optional: true dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 '@emotion/cache': 11.11.0 - '@emotion/react': 11.11.4(@types/react@18.2.67)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.4)(@types/react@18.2.67)(react@18.2.0) + '@emotion/react': 11.11.4(@types/react@18.3.1)(react@18.3.1) + '@emotion/styled': 11.11.5(@emotion/react@11.11.4)(@types/react@18.3.1)(react@18.3.1) csstype: 3.1.3 prop-types: 15.8.1 - react: 18.2.0 + react: 18.3.1 dev: false - /@mui/system@5.15.14(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(@types/react@18.2.67)(react@18.2.0): + /@mui/system@5.15.14(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.3.1)(react@18.3.1): resolution: {integrity: sha512-auXLXzUaCSSOLqJXmsAaq7P96VPRXg2Rrz6OHNV7lr+kB8lobUF+/N84Vd9C4G/wvCXYPs5TYuuGBRhcGbiBGg==} engines: {node: '>=12.0.0'} peerDependencies: @@ -2607,21 +2585,51 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.24.1 - '@emotion/react': 11.11.4(@types/react@18.2.67)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.4)(@types/react@18.2.67)(react@18.2.0) - '@mui/private-theming': 5.15.14(@types/react@18.2.67)(react@18.2.0) - '@mui/styled-engine': 5.15.14(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(react@18.2.0) - '@mui/types': 7.2.14(@types/react@18.2.67) - '@mui/utils': 5.15.14(@types/react@18.2.67)(react@18.2.0) - '@types/react': 18.2.67 + '@babel/runtime': 7.24.4 + '@emotion/react': 11.11.4(@types/react@18.3.1)(react@18.3.1) + '@emotion/styled': 11.11.5(@emotion/react@11.11.4)(@types/react@18.3.1)(react@18.3.1) + '@mui/private-theming': 5.15.14(@types/react@18.3.1)(react@18.3.1) + '@mui/styled-engine': 5.15.14(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(react@18.3.1) + '@mui/types': 7.2.14(@types/react@18.3.1) + '@mui/utils': 5.15.14(@types/react@18.3.1)(react@18.3.1) + '@types/react': 18.3.1 + clsx: 2.1.0 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + dev: false + + /@mui/system@5.15.15(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.4 + '@emotion/react': 11.11.4(@types/react@18.3.1)(react@18.3.1) + '@emotion/styled': 11.11.5(@emotion/react@11.11.4)(@types/react@18.3.1)(react@18.3.1) + '@mui/private-theming': 5.15.14(@types/react@18.3.1)(react@18.3.1) + '@mui/styled-engine': 5.15.14(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(react@18.3.1) + '@mui/types': 7.2.14(@types/react@18.3.1) + '@mui/utils': 5.15.14(@types/react@18.3.1)(react@18.3.1) + '@types/react': 18.3.1 clsx: 2.1.0 csstype: 3.1.3 prop-types: 15.8.1 - react: 18.2.0 + react: 18.3.1 dev: false - /@mui/types@7.2.14(@types/react@18.2.67): + /@mui/types@7.2.14(@types/react@18.3.1): resolution: {integrity: sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 @@ -2629,10 +2637,10 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 18.2.67 + '@types/react': 18.3.1 dev: false - /@mui/utils@5.15.14(@types/react@18.2.67)(react@18.2.0): + /@mui/utils@5.15.14(@types/react@18.3.1)(react@18.3.1): resolution: {integrity: sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==} engines: {node: '>=12.0.0'} peerDependencies: @@ -2642,16 +2650,16 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 '@types/prop-types': 15.7.11 - '@types/react': 18.2.67 + '@types/react': 18.3.1 prop-types: 15.8.1 - react: 18.2.0 + react: 18.3.1 react-is: 18.2.0 dev: false - /@mui/x-date-pickers@7.0.0(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(@mui/material@5.15.14)(@types/react@18.2.67)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-/9mp4O2WMixHOso63DBoZVfJVYGrzOHF5voheV2tYQ4XqDdTKp2AdWS3oh8PGwrsvCzqkvb3quzTqhKoEsJUwA==} + /@mui/x-date-pickers@7.3.2(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@mui/material@5.15.16)(@types/react@18.3.1)(dayjs@1.11.11)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-i7JaDs1eXSZWyJihfszUHVV0t/C2HvtdMv5tHwv3E3enMx5Hup1vkJ64vZAH2fgGrTHQH8mjxvVsmI6jhDXIUg==} engines: {node: '>=14.0.0'} peerDependencies: '@emotion/react': ^11.9.0 @@ -2686,20 +2694,20 @@ packages: moment-jalaali: optional: true dependencies: - '@babel/runtime': 7.24.1 - '@emotion/react': 11.11.4(@types/react@18.2.67)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.4)(@types/react@18.2.67)(react@18.2.0) - '@mui/base': 5.0.0-beta.40(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0) - '@mui/material': 5.15.14(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0) - '@mui/system': 5.15.14(@emotion/react@11.11.4)(@emotion/styled@11.11.0)(@types/react@18.2.67)(react@18.2.0) - '@mui/utils': 5.15.14(@types/react@18.2.67)(react@18.2.0) + '@babel/runtime': 7.24.4 + '@emotion/react': 11.11.4(@types/react@18.3.1)(react@18.3.1) + '@emotion/styled': 11.11.5(@emotion/react@11.11.4)(@types/react@18.3.1)(react@18.3.1) + '@mui/base': 5.0.0-beta.40(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1) + '@mui/material': 5.15.16(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1) + '@mui/system': 5.15.14(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.3.1)(react@18.3.1) + '@mui/utils': 5.15.14(@types/react@18.3.1)(react@18.3.1) '@types/react-transition-group': 4.4.10 clsx: 2.1.0 - dayjs: 1.11.10 + dayjs: 1.11.11 prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-transition-group: 4.4.5(react-dom@18.3.1)(react@18.3.1) transitivePeerDependencies: - '@types/react' dev: false @@ -2722,8 +2730,19 @@ packages: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - /@open-draft/until@1.0.3: - resolution: {integrity: sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==} + /@open-draft/deferred-promise@2.2.0: + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + dev: true + + /@open-draft/logger@0.3.0: + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.2 + dev: true + + /@open-draft/until@2.1.0: + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} dev: true /@pkgjs/parseargs@0.11.0: @@ -2738,20 +2757,20 @@ packages: engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} dev: true - /@playwright/test@1.42.1: - resolution: {integrity: sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==} + /@playwright/test@1.43.1: + resolution: {integrity: sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==} engines: {node: '>=16'} hasBin: true dependencies: - playwright: 1.42.1 + playwright: 1.43.1 dev: true /@popperjs/core@2.11.8: resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} dev: false - /@reduxjs/toolkit@2.2.2(react-redux@9.1.0)(react@18.2.0): - resolution: {integrity: sha512-454GZrEx3G6QSYwIx9ROaso1HR6sTH8qyZBe3KEsdWVGU3ayV8jYCwdaEJV3vl9V6+pi3GRl+7Xl7AeDna6qwQ==} + /@reduxjs/toolkit@2.2.3(react-redux@9.1.2)(react@18.3.1): + resolution: {integrity: sha512-76dll9EnJXg4EVcI5YNxZA/9hSAmZsFqzMmNRHvIlzw2WS/twfcVX3ysYrWGJMClwEmChQFC4yRq74tn6fdzRA==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 @@ -2762,15 +2781,15 @@ packages: optional: true dependencies: immer: 10.0.4 - react: 18.2.0 - react-redux: 9.1.0(@types/react@18.2.67)(react@18.2.0)(redux@5.0.1) + react: 18.3.1 + react-redux: 9.1.2(@types/react@18.3.1)(react@18.3.1)(redux@5.0.1) redux: 5.0.1 redux-thunk: 3.1.0(redux@5.0.1) reselect: 5.1.0 dev: false - /@remix-run/router@1.15.3: - resolution: {integrity: sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==} + /@remix-run/router@1.16.0: + resolution: {integrity: sha512-Quz1KOffeEf/zwkCBM3kBtH4ZoZ+pT3xIXBG4PPW/XFtDP7EGhtTiC2+gpL9GnR7+Qdet5Oa6cYSvwKYg6kN9Q==} engines: {node: '>=14.0.0'} dev: false @@ -2791,16 +2810,19 @@ packages: rollup: 2.79.1 dev: false - /@rollup/plugin-node-resolve@11.2.1(rollup@2.79.1): - resolution: {integrity: sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==} - engines: {node: '>= 10.0.0'} + /@rollup/plugin-node-resolve@15.2.3(rollup@2.79.1): + resolution: {integrity: sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==} + engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^1.20.0||^2.0.0 + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true dependencies: - '@rollup/pluginutils': 3.1.0(rollup@2.79.1) - '@types/resolve': 1.17.1 - builtin-modules: 3.3.0 + '@rollup/pluginutils': 5.1.0(rollup@2.79.1) + '@types/resolve': 1.20.2 deepmerge: 4.3.1 + is-builtin-module: 3.2.1 is-module: 1.0.0 resolve: 1.22.8 rollup: 2.79.1 @@ -2816,6 +2838,21 @@ packages: rollup: 2.79.1 dev: false + /@rollup/plugin-terser@0.4.4(rollup@2.79.1): + resolution: {integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + rollup: 2.79.1 + serialize-javascript: 6.0.2 + smob: 1.5.0 + terser: 5.30.3 + dev: false + /@rollup/pluginutils@3.1.0(rollup@2.79.1): resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} engines: {node: '>= 8.0.0'} @@ -2828,6 +2865,21 @@ packages: rollup: 2.79.1 dev: false + /@rollup/pluginutils@5.1.0(rollup@2.79.1): + resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.5 + estree-walker: 2.0.2 + picomatch: 2.3.1 + rollup: 2.79.1 + dev: false + /@rollup/rollup-android-arm-eabi@4.14.0: resolution: {integrity: sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w==} cpu: [arm] @@ -2951,11 +3003,6 @@ packages: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true - /@sindresorhus/is@4.6.0: - resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} - engines: {node: '>=10'} - dev: true - /@sinonjs/commons@3.0.1: resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} dependencies: @@ -2971,7 +3018,7 @@ packages: /@surma/rollup-plugin-off-main-thread@2.2.3: resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} dependencies: - ejs: 3.1.9 + ejs: 3.1.10 json5: 2.2.3 magic-string: 0.25.9 string.prototype.matchall: 4.0.11 @@ -3102,29 +3149,22 @@ packages: '@swc/counter': 0.1.3 dev: true - /@szmarczak/http-timer@4.0.6: - resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} - engines: {node: '>=10'} - dependencies: - defer-to-connect: 2.0.1 - dev: true - - /@testing-library/dom@9.3.4: - resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==} - engines: {node: '>=14'} + /@testing-library/dom@10.1.0: + resolution: {integrity: sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA==} + engines: {node: '>=18'} dependencies: '@babel/code-frame': 7.24.2 - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 '@types/aria-query': 5.0.4 - aria-query: 5.1.3 + aria-query: 5.3.0 chalk: 4.1.2 dom-accessibility-api: 0.5.16 lz-string: 1.5.0 pretty-format: 27.5.1 dev: true - /@testing-library/jest-dom@6.4.2(@types/jest@29.5.12)(jest@29.7.0): - resolution: {integrity: sha512-CzqH0AFymEMG48CpzXFriYYkOjk6ZGPCLMhW9e9jg3KMCn5OfJecF8GtGW7yGfR/IgCe3SX8BSwjdzI6BBbZLw==} + /@testing-library/jest-dom@6.4.5(@types/jest@29.5.12)(jest@29.7.0): + resolution: {integrity: sha512-AguB9yvTXmCnySBP1lWjfNNUwpbElsaQ567lt2VdGqAdHtpieLgjmcVyv1q7PMIvLbgpDdkWV5Ydv3FEejyp2A==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} peerDependencies: '@jest/globals': '>= 28' @@ -3145,38 +3185,43 @@ packages: optional: true dependencies: '@adobe/css-tools': 4.3.3 - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 '@types/jest': 29.5.12 aria-query: 5.3.0 chalk: 3.0.0 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 - jest: 29.7.0(@types/node@20.11.30)(ts-node@10.9.2) + jest: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2) lodash: 4.17.21 redent: 3.0.0 dev: true - /@testing-library/react@14.2.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-SOUuM2ysCvjUWBXTNfQ/ztmnKDmqaiPV3SvoIuyxMUca45rbSWWAT/qB8CUs/JQ/ux/8JFs9DNdFQ3f6jH3crA==} - engines: {node: '>=14'} + /@testing-library/react@15.0.6(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-UlbazRtEpQClFOiYp+1BapMT+xyqWMnE+hh9tn5DQ6gmlE7AIZWcGpzZukmDZuFk3By01oiqOf8lRedLS4k6xQ==} + engines: {node: '>=18'} peerDependencies: + '@types/react': ^18.0.0 react: ^18.0.0 react-dom: ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true dependencies: - '@babel/runtime': 7.24.1 - '@testing-library/dom': 9.3.4 - '@types/react-dom': 18.2.22 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@babel/runtime': 7.24.4 + '@testing-library/dom': 10.1.0 + '@types/react': 18.3.1 + '@types/react-dom': 18.3.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: true - /@testing-library/user-event@14.5.2(@testing-library/dom@9.3.4): + /@testing-library/user-event@14.5.2(@testing-library/dom@10.1.0): resolution: {integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==} engines: {node: '>=12', npm: '>=6'} peerDependencies: '@testing-library/dom': '>=7.21.4' dependencies: - '@testing-library/dom': 9.3.4 + '@testing-library/dom': 10.1.0 dev: true /@tootallnate/once@2.0.0: @@ -3233,23 +3278,8 @@ packages: '@babel/types': 7.24.0 dev: true - /@types/cacheable-request@6.0.3: - resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} - dependencies: - '@types/http-cache-semantics': 4.0.4 - '@types/keyv': 3.1.4 - '@types/node': 20.11.30 - '@types/responselike': 1.0.3 - dev: true - - /@types/cookie@0.4.1: - resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} - dev: true - - /@types/debug@4.1.12: - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - dependencies: - '@types/ms': 0.7.34 + /@types/cookie@0.6.0: + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} dev: true /@types/estree@0.0.39: @@ -3262,20 +3292,16 @@ packages: /@types/graceful-fs@4.1.9: resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.8 dev: true /@types/hoist-non-react-statics@3.3.5: resolution: {integrity: sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==} dependencies: - '@types/react': 18.2.67 + '@types/react': 18.3.1 hoist-non-react-statics: 3.3.2 dev: true - /@types/http-cache-semantics@4.0.4: - resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} - dev: true - /@types/istanbul-lib-coverage@2.0.6: resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} dev: true @@ -3299,14 +3325,10 @@ packages: pretty-format: 29.7.0 dev: true - /@types/js-levenshtein@1.1.3: - resolution: {integrity: sha512-jd+Q+sD20Qfu9e2aEXogiO3vpOC1PYJOUdyN9gvs4Qrvkg4wF43L5OhqrPeokdv8TL0/mXoYfpkcoGZMNN2pkQ==} - dev: true - /@types/jsdom@20.0.1: resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.8 '@types/tough-cookie': 4.0.5 parse5: 7.1.2 dev: true @@ -3319,12 +3341,6 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true - /@types/keyv@3.1.4: - resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} - dependencies: - '@types/node': 20.11.30 - dev: true - /@types/lodash.camelcase@4.3.9: resolution: {integrity: sha512-ys9/hGBfsKxzmFI8hckII40V0ASQ83UM2pxfQRghHAwekhH4/jWtjz/3/9YDy7ZpUd/H0k2STSqmPR28dnj7Zg==} dependencies: @@ -3335,33 +3351,23 @@ packages: resolution: {integrity: sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==} dev: true - /@types/ms@0.7.34: - resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - dev: true - /@types/mute-stream@0.0.4: resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.8 dev: true /@types/node-fetch@2.6.11: resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.8 form-data: 4.0.0 dev: true - /@types/node@20.11.30: - resolution: {integrity: sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==} - dependencies: - undici-types: 5.26.5 - - /@types/node@20.12.3: - resolution: {integrity: sha512-sD+ia2ubTeWrOu+YMF+MTAB7E+O7qsMqAbMfW7DG3K1URwhZ5hN1pLlRVGbf4wDFzSfikL05M17EyorS86jShw==} + /@types/node@20.12.8: + resolution: {integrity: sha512-NU0rJLJnshZWdE/097cdCBbyW1h4hEg0xpovcoAQYHl8dnEyp/NAOiE45pvc+Bd1Dt+2r94v2eGFpQJ4R7g+2w==} dependencies: undici-types: 5.26.5 - dev: false /@types/parse-json@4.0.2: resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -3370,17 +3376,17 @@ packages: /@types/prop-types@15.7.11: resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==} - /@types/react-dom@18.2.22: - resolution: {integrity: sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ==} + /@types/react-dom@18.3.0: + resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} dependencies: - '@types/react': 18.2.67 + '@types/react': 18.3.1 dev: true /@types/react-redux@7.1.33: resolution: {integrity: sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==} dependencies: '@types/hoist-non-react-statics': 3.3.5 - '@types/react': 18.2.67 + '@types/react': 18.3.1 hoist-non-react-statics: 3.3.2 redux: 4.2.1 dev: true @@ -3388,45 +3394,31 @@ packages: /@types/react-transition-group@4.4.10: resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==} dependencies: - '@types/react': 18.2.67 + '@types/react': 18.3.1 dev: false - /@types/react@18.2.67: - resolution: {integrity: sha512-vkIE2vTIMHQ/xL0rgmuoECBCkZFZeHr49HeWSc24AptMbNRo7pwSBvj73rlJJs9fGKj0koS+V7kQB1jHS0uCgw==} + /@types/react@18.3.1: + resolution: {integrity: sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==} dependencies: '@types/prop-types': 15.7.11 - '@types/scheduler': 0.16.8 csstype: 3.1.3 - /@types/resolve@1.17.1: - resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} - dependencies: - '@types/node': 20.12.3 + /@types/resolve@1.20.2: + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} dev: false - /@types/responselike@1.0.3: - resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} - dependencies: - '@types/node': 20.11.30 - dev: true - - /@types/scheduler@0.16.8: - resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==} - /@types/semver@7.5.8: resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} dev: true - /@types/set-cookie-parser@2.4.7: - resolution: {integrity: sha512-+ge/loa0oTozxip6zmhRIk8Z/boU51wl9Q6QdLZcokIGMzY5lFXYy/x7Htj2HTC6/KZP1hUbZ1ekx8DYXICvWg==} - dependencies: - '@types/node': 20.11.30 - dev: true - /@types/stack-utils@2.0.3: resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} dev: true + /@types/statuses@2.0.5: + resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} + dev: true + /@types/tough-cookie@4.0.5: resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} dev: true @@ -3457,8 +3449,8 @@ packages: '@types/yargs-parser': 21.0.3 dev: true - /@typescript-eslint/eslint-plugin@7.4.0(@typescript-eslint/parser@7.4.0)(eslint@8.57.0)(typescript@5.4.2): - resolution: {integrity: sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==} + /@typescript-eslint/eslint-plugin@7.8.0(@typescript-eslint/parser@7.8.0)(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: '@typescript-eslint/parser': ^7.0.0 @@ -3469,25 +3461,25 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 7.4.0(eslint@8.57.0)(typescript@5.4.2) - '@typescript-eslint/scope-manager': 7.4.0 - '@typescript-eslint/type-utils': 7.4.0(eslint@8.57.0)(typescript@5.4.2) - '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.4.2) - '@typescript-eslint/visitor-keys': 7.4.0 + '@typescript-eslint/parser': 7.8.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/scope-manager': 7.8.0 + '@typescript-eslint/type-utils': 7.8.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/utils': 7.8.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.8.0 debug: 4.3.4 eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@5.4.2) - typescript: 5.4.2 + ts-api-utils: 1.3.0(typescript@5.4.5) + typescript: 5.4.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@7.4.0(eslint@8.57.0)(typescript@5.4.2): - resolution: {integrity: sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==} + /@typescript-eslint/parser@7.8.0(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -3496,27 +3488,27 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 7.4.0 - '@typescript-eslint/types': 7.4.0 - '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.2) - '@typescript-eslint/visitor-keys': 7.4.0 + '@typescript-eslint/scope-manager': 7.8.0 + '@typescript-eslint/types': 7.8.0 + '@typescript-eslint/typescript-estree': 7.8.0(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.8.0 debug: 4.3.4 eslint: 8.57.0 - typescript: 5.4.2 + typescript: 5.4.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager@7.4.0: - resolution: {integrity: sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==} + /@typescript-eslint/scope-manager@7.8.0: + resolution: {integrity: sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==} engines: {node: ^18.18.0 || >=20.0.0} dependencies: - '@typescript-eslint/types': 7.4.0 - '@typescript-eslint/visitor-keys': 7.4.0 + '@typescript-eslint/types': 7.8.0 + '@typescript-eslint/visitor-keys': 7.8.0 dev: true - /@typescript-eslint/type-utils@7.4.0(eslint@8.57.0)(typescript@5.4.2): - resolution: {integrity: sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==} + /@typescript-eslint/type-utils@7.8.0(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -3525,23 +3517,23 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.2) - '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.4.2) + '@typescript-eslint/typescript-estree': 7.8.0(typescript@5.4.5) + '@typescript-eslint/utils': 7.8.0(eslint@8.57.0)(typescript@5.4.5) debug: 4.3.4 eslint: 8.57.0 - ts-api-utils: 1.3.0(typescript@5.4.2) - typescript: 5.4.2 + ts-api-utils: 1.3.0(typescript@5.4.5) + typescript: 5.4.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types@7.4.0: - resolution: {integrity: sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==} + /@typescript-eslint/types@7.8.0: + resolution: {integrity: sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==} engines: {node: ^18.18.0 || >=20.0.0} dev: true - /@typescript-eslint/typescript-estree@7.4.0(typescript@5.4.2): - resolution: {integrity: sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==} + /@typescript-eslint/typescript-estree@7.8.0(typescript@5.4.5): + resolution: {integrity: sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: typescript: '*' @@ -3549,21 +3541,21 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 7.4.0 - '@typescript-eslint/visitor-keys': 7.4.0 + '@typescript-eslint/types': 7.8.0 + '@typescript-eslint/visitor-keys': 7.8.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 - minimatch: 9.0.3 + minimatch: 9.0.4 semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@5.4.2) - typescript: 5.4.2 + ts-api-utils: 1.3.0(typescript@5.4.5) + typescript: 5.4.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@7.4.0(eslint@8.57.0)(typescript@5.4.2): - resolution: {integrity: sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==} + /@typescript-eslint/utils@7.8.0(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -3571,9 +3563,9 @@ packages: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 7.4.0 - '@typescript-eslint/types': 7.4.0 - '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.2) + '@typescript-eslint/scope-manager': 7.8.0 + '@typescript-eslint/types': 7.8.0 + '@typescript-eslint/typescript-estree': 7.8.0(typescript@5.4.5) eslint: 8.57.0 semver: 7.6.0 transitivePeerDependencies: @@ -3581,11 +3573,11 @@ packages: - typescript dev: true - /@typescript-eslint/visitor-keys@7.4.0: - resolution: {integrity: sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==} + /@typescript-eslint/visitor-keys@7.8.0: + resolution: {integrity: sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==} engines: {node: ^18.18.0 || >=20.0.0} dependencies: - '@typescript-eslint/types': 7.4.0 + '@typescript-eslint/types': 7.8.0 eslint-visitor-keys: 3.4.3 dev: true @@ -3593,28 +3585,17 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true - /@vitejs/plugin-react-swc@3.6.0(vite@5.2.6): + /@vitejs/plugin-react-swc@3.6.0(vite@5.2.11): resolution: {integrity: sha512-XFRbsGgpGxGzEV5i5+vRiro1bwcIaZDIdBRP16qwm+jP68ue/S8FJTBEgOeojtVDYrbSua3XFp71kC8VJE6v+g==} peerDependencies: vite: ^4 || ^5 dependencies: '@swc/core': 1.4.8 - vite: 5.2.6(@types/node@20.11.30) + vite: 5.2.11(@types/node@20.12.8) transitivePeerDependencies: - '@swc/helpers' dev: true - /@xmldom/xmldom@0.8.10: - resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} - engines: {node: '>=10.0.0'} - dev: true - - /@zxing/text-encoding@0.9.0: - resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==} - requiresBuild: true - dev: true - optional: true - /JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -3690,12 +3671,6 @@ packages: require-from-string: 2.0.2 uri-js: 4.4.1 - /ansi-align@3.0.1: - resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} - dependencies: - string-width: 4.2.3 - dev: true - /ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -3768,12 +3743,6 @@ packages: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true - /aria-query@5.1.3: - resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} - dependencies: - deep-equal: 2.2.3 - dev: true - /aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} dependencies: @@ -3915,8 +3884,8 @@ packages: yargs: 17.7.2 dev: true - /autoprefixer@10.4.18(postcss@8.4.38): - resolution: {integrity: sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==} + /autoprefixer@10.4.19(postcss@8.4.38): + resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: @@ -4071,11 +4040,6 @@ packages: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: true - /binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - dev: true - /bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} dependencies: @@ -4084,20 +4048,6 @@ packages: readable-stream: 3.6.2 dev: true - /boxen@5.1.2: - resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} - engines: {node: '>=10'} - dependencies: - ansi-align: 3.0.1 - camelcase: 6.3.0 - chalk: 4.1.2 - cli-boxes: 2.2.1 - string-width: 4.2.3 - type-fest: 0.20.2 - widest-line: 3.1.0 - wrap-ansi: 7.0.0 - dev: true - /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -4151,34 +4101,11 @@ packages: /builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} + dev: false - /builtins@5.0.1: - resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} - dependencies: - semver: 7.6.0 - dev: true - - /cacheable-lookup@5.0.4: - resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} - engines: {node: '>=10.6.0'} - dev: true - - /cacheable-request@7.0.4: - resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} - engines: {node: '>=8'} - dependencies: - clone-response: 1.0.3 - get-stream: 5.2.0 - http-cache-semantics: 4.1.1 - keyv: 4.5.4 - lowercase-keys: 2.0.0 - normalize-url: 6.1.0 - responselike: 2.0.1 - dev: true - - /call-bind@1.0.7: - resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} - engines: {node: '>= 0.4'} + /call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} dependencies: es-define-property: 1.0.0 es-errors: 1.3.0 @@ -4236,29 +4163,6 @@ packages: engines: {node: '>=10'} dev: true - /chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - dev: true - - /chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.3 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - dev: true - - /ci-info@2.0.0: - resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} - dev: true - /ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} @@ -4268,11 +4172,6 @@ packages: resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} dev: true - /cli-boxes@2.2.1: - resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} - engines: {node: '>=6'} - dev: true - /cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -4300,11 +4199,6 @@ packages: string-width: 7.1.0 dev: true - /cli-width@3.0.0: - resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} - engines: {node: '>= 10'} - dev: true - /cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} @@ -4319,12 +4213,6 @@ packages: wrap-ansi: 7.0.0 dev: true - /clone-response@1.0.3: - resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} - dependencies: - mimic-response: 1.0.1 - dev: true - /clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} @@ -4397,6 +4285,11 @@ packages: engines: {node: '>=16'} dev: true + /commander@12.0.0: + resolution: {integrity: sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==} + engines: {node: '>=18'} + dev: true + /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} dev: false @@ -4407,7 +4300,7 @@ packages: dev: false /concat-map@0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} /conf@10.2.0: resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==} @@ -4425,18 +4318,6 @@ packages: semver: 7.6.0 dev: true - /configstore@5.0.1: - resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==} - engines: {node: '>=8'} - dependencies: - dot-prop: 5.3.0 - graceful-fs: 4.2.11 - make-dir: 3.1.0 - unique-string: 2.0.0 - write-file-atomic: 3.0.3 - xdg-basedir: 4.0.0 - dev: true - /convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} dev: false @@ -4444,8 +4325,8 @@ packages: /convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - /cookie@0.4.2: - resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + /cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} dev: true @@ -4466,8 +4347,8 @@ packages: yaml: 1.10.2 dev: false - /cosmiconfig@8.3.6(typescript@5.4.2): - resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + /cosmiconfig@9.0.0(typescript@5.4.5): + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} peerDependencies: typescript: '>=4.9.5' @@ -4475,14 +4356,14 @@ packages: typescript: optional: true dependencies: + env-paths: 2.2.1 import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 - path-type: 4.0.0 - typescript: 5.4.2 + typescript: 5.4.5 dev: true - /create-jest@29.7.0(@types/node@20.11.30)(ts-node@10.9.2): + /create-jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2): resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -4491,7 +4372,7 @@ packages: chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.11.30)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -4517,6 +4398,7 @@ packages: /crypto-random-string@2.0.0: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} + dev: false /css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} @@ -4586,8 +4468,8 @@ packages: es-errors: 1.3.0 is-data-view: 1.0.1 - /dayjs@1.11.10: - resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} + /dayjs@1.11.11: + resolution: {integrity: sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==} dev: false /debounce-fn@4.0.0: @@ -4623,13 +4505,6 @@ packages: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} dev: true - /decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - dependencies: - mimic-response: 3.1.0 - dev: true - /dedent@1.5.1: resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==} peerDependencies: @@ -4639,35 +4514,6 @@ packages: optional: true dev: true - /deep-equal@2.2.3: - resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} - engines: {node: '>= 0.4'} - dependencies: - array-buffer-byte-length: 1.0.1 - call-bind: 1.0.7 - es-get-iterator: 1.1.3 - get-intrinsic: 1.2.4 - is-arguments: 1.1.1 - is-array-buffer: 3.0.4 - is-date-object: 1.0.5 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.3 - isarray: 2.0.5 - object-is: 1.1.6 - object-keys: 1.1.1 - object.assign: 4.1.5 - regexp.prototype.flags: 1.5.2 - side-channel: 1.0.6 - which-boxed-primitive: 1.0.2 - which-collection: 1.0.2 - which-typed-array: 1.1.15 - dev: true - - /deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - dev: true - /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true @@ -4682,11 +4528,6 @@ packages: clone: 1.0.4 dev: true - /defer-to-connect@2.0.1: - resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} - engines: {node: '>=10'} - dev: true - /define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -4732,6 +4573,11 @@ packages: engines: {node: '>=0.3.1'} dev: true + /diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + dev: true + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -4764,7 +4610,7 @@ packages: /dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 csstype: 3.1.3 dev: false @@ -4776,13 +4622,6 @@ packages: webidl-conversions: 7.0.0 dev: true - /dot-prop@5.3.0: - resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} - engines: {node: '>=8'} - dependencies: - is-obj: 2.0.0 - dev: true - /dot-prop@6.0.1: resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} engines: {node: '>=10'} @@ -4810,8 +4649,8 @@ packages: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true - /ejs@3.1.9: - resolution: {integrity: sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==} + /ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} engines: {node: '>=0.10.0'} hasBin: true dependencies: @@ -4842,10 +4681,12 @@ packages: resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} dev: true - /end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + /enhanced-resolve@5.16.0: + resolution: {integrity: sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==} + engines: {node: '>=10.13.0'} dependencies: - once: 1.4.0 + graceful-fs: 4.2.11 + tapable: 2.2.1 dev: true /entities@4.5.0: @@ -4970,20 +4811,6 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - /es-get-iterator@1.1.3: - resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 - is-arguments: 1.1.1 - is-map: 2.0.3 - is-set: 2.0.3 - is-string: 1.0.7 - isarray: 2.0.5 - stop-iteration-iterator: 1.0.0 - dev: true - /es-iterator-helpers@1.0.18: resolution: {integrity: sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA==} engines: {node: '>= 0.4'} @@ -5066,11 +4893,6 @@ packages: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} - /escape-goat@2.1.1: - resolution: {integrity: sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==} - engines: {node: '>=8'} - dev: true - /escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -5124,7 +4946,7 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.1(@typescript-eslint/parser@7.4.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): + /eslint-module-utils@2.8.1(@typescript-eslint/parser@7.8.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} engines: {node: '>=4'} peerDependencies: @@ -5145,7 +4967,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 7.4.0(eslint@8.57.0)(typescript@5.4.2) + '@typescript-eslint/parser': 7.8.0(eslint@8.57.0)(typescript@5.4.5) debug: 3.2.7 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 @@ -5165,7 +4987,7 @@ packages: eslint-compat-utils: 0.1.2(eslint@8.57.0) dev: true - /eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.4.0)(eslint@8.57.0): + /eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.8.0)(eslint@8.57.0): resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} engines: {node: '>=4'} peerDependencies: @@ -5175,7 +4997,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 7.4.0(eslint@8.57.0)(typescript@5.4.2) + '@typescript-eslint/parser': 7.8.0(eslint@8.57.0)(typescript@5.4.5) array-includes: 3.1.7 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 @@ -5184,7 +5006,7 @@ packages: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.4.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.8.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -5200,23 +5022,20 @@ packages: - supports-color dev: true - /eslint-plugin-n@16.6.2(eslint@8.57.0): - resolution: {integrity: sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==} - engines: {node: '>=16.0.0'} + /eslint-plugin-n@17.4.0(eslint@8.57.0): + resolution: {integrity: sha512-RtgGgNpYxECwE9dFr+D66RtbN0B8r/fY6ZF8EVsmK2YnZxE8/n9LNQhgnkL9z37UFZjYVmvMuC32qu7fQBsLVQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: '>=7.0.0' + eslint: '>=8.23.0' dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - builtins: 5.0.1 + enhanced-resolve: 5.16.0 eslint: 8.57.0 eslint-plugin-es-x: 7.5.0(eslint@8.57.0) get-tsconfig: 4.7.3 - globals: 13.24.0 + globals: 15.1.0 ignore: 5.3.1 - is-builtin-module: 3.2.1 - is-core-module: 2.13.1 - minimatch: 3.1.2 - resolve: 1.22.8 + minimatch: 9.0.3 semver: 7.6.0 dev: true @@ -5250,8 +5069,8 @@ packages: eslint: 8.57.0 dev: true - /eslint-plugin-react-hooks@4.6.0(eslint@8.57.0): - resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} + /eslint-plugin-react-hooks@4.6.2(eslint@8.57.0): + resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} engines: {node: '>=10'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 @@ -5384,6 +5203,10 @@ packages: resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} dev: false + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: false + /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -5404,11 +5227,6 @@ packages: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} dev: true - /events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - dev: true - /execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -5455,15 +5273,6 @@ packages: jest-util: 29.7.0 dev: true - /external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} - dependencies: - chardet: 0.7.0 - iconv-lite: 0.4.24 - tmp: 0.0.33 - dev: true - /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -5703,13 +5512,6 @@ packages: engines: {node: '>=8.0.0'} dev: true - /get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} - dependencies: - pump: 3.0.0 - dev: true - /get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -5769,13 +5571,6 @@ packages: once: 1.4.0 path-is-absolute: 1.0.1 - /global-dirs@3.0.1: - resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} - engines: {node: '>=10'} - dependencies: - ini: 2.0.0 - dev: true - /globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -5787,6 +5582,11 @@ packages: type-fest: 0.20.2 dev: true + /globals@15.1.0: + resolution: {integrity: sha512-926gJqg+4mkxwYKiFvoomM4J0kWESfk3qfTvRL2/oc/tK/eTDBbrfcKnSa2KtfdxB5onoL7D3A3qIHQFpd4+UA==} + engines: {node: '>=18'} + dev: true + /globalthis@1.0.3: resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} engines: {node: '>= 0.4'} @@ -5810,23 +5610,6 @@ packages: dependencies: get-intrinsic: 1.2.4 - /got@11.8.6: - resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} - engines: {node: '>=10.19.0'} - dependencies: - '@sindresorhus/is': 4.6.0 - '@szmarczak/http-timer': 4.0.6 - '@types/cacheable-request': 6.0.3 - '@types/responselike': 1.0.3 - cacheable-lookup: 5.0.4 - cacheable-request: 7.0.4 - decompress-response: 6.0.0 - http2-wrapper: 1.0.3 - lowercase-keys: 2.0.0 - p-cancelable: 2.1.1 - responselike: 2.0.1 - dev: true - /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -5873,19 +5656,14 @@ packages: dependencies: has-symbols: 1.0.3 - /has-yarn@2.1.0: - resolution: {integrity: sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==} - engines: {node: '>=8'} - dev: true - /hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 - /headers-polyfill@3.2.5: - resolution: {integrity: sha512-tUCGvt191vNSQgttSyJoibR+VO+I6+iCHIUdhzEMJKE+EAL8BwCN7fUOZlY4ofOelNHsK+gEjxB/B+9N3EWtdA==} + /headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} dev: true /hoist-non-react-statics@3.3.2: @@ -5904,10 +5682,6 @@ packages: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} dev: true - /http-cache-semantics@4.1.1: - resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} - dev: true - /http-proxy-agent@5.0.0: resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} engines: {node: '>= 6'} @@ -5919,14 +5693,6 @@ packages: - supports-color dev: true - /http2-wrapper@1.0.3: - resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} - engines: {node: '>=10.19.0'} - dependencies: - quick-lru: 5.1.1 - resolve-alpn: 1.2.1 - dev: true - /https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -5953,13 +5719,6 @@ packages: hasBin: true dev: true - /iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} - dependencies: - safer-buffer: 2.1.2 - dev: true - /iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -5998,11 +5757,6 @@ packages: parent-module: 1.0.1 resolve-from: 4.0.0 - /import-lazy@2.1.0: - resolution: {integrity: sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==} - engines: {node: '>=4'} - dev: true - /import-local@3.1.0: resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} engines: {node: '>=8'} @@ -6031,36 +5785,6 @@ packages: /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - /ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - dev: true - - /ini@2.0.0: - resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} - engines: {node: '>=10'} - dev: true - - /inquirer@8.2.6: - resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} - engines: {node: '>=12.0.0'} - dependencies: - ansi-escapes: 4.3.2 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-width: 3.0.0 - external-editor: 3.1.0 - figures: 3.2.0 - lodash: 4.17.21 - mute-stream: 0.0.8 - ora: 5.4.1 - run-async: 2.4.1 - rxjs: 7.8.1 - string-width: 4.2.3 - strip-ansi: 6.0.1 - through: 2.3.8 - wrap-ansi: 6.2.0 - dev: true - /internal-slot@1.0.7: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} @@ -6069,14 +5793,6 @@ packages: hasown: 2.0.2 side-channel: 1.0.6 - /is-arguments@1.1.1: - resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - has-tostringtag: 1.0.2 - dev: true - /is-array-buffer@3.0.4: resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} engines: {node: '>= 0.4'} @@ -6103,13 +5819,6 @@ packages: dependencies: has-bigints: 1.0.2 - /is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - dependencies: - binary-extensions: 2.3.0 - dev: true - /is-boolean-object@1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} engines: {node: '>= 0.4'} @@ -6122,19 +5831,12 @@ packages: engines: {node: '>=6'} dependencies: builtin-modules: 3.3.0 - dev: true + dev: false /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - /is-ci@2.0.0: - resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==} - hasBin: true - dependencies: - ci-info: 2.0.0 - dev: true - /is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} dependencies: @@ -6203,14 +5905,6 @@ packages: dependencies: is-extglob: 2.1.1 - /is-installed-globally@0.4.0: - resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} - engines: {node: '>=10'} - dependencies: - global-dirs: 3.0.1 - is-path-inside: 3.0.3 - dev: true - /is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} @@ -6233,11 +5927,6 @@ packages: resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} dev: true - /is-npm@5.0.0: - resolution: {integrity: sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==} - engines: {node: '>=10'} - dev: true - /is-number-object@1.0.7: resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} engines: {node: '>= 0.4'} @@ -6317,10 +6006,6 @@ packages: dependencies: which-typed-array: 1.1.15 - /is-typedarray@1.0.0: - resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - dev: true - /is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} @@ -6351,10 +6036,6 @@ packages: is-docker: 2.2.1 dev: true - /is-yarn-global@0.3.0: - resolution: {integrity: sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==} - dev: true - /isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -6473,7 +6154,7 @@ packages: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.30 + '@types/node': 20.12.8 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.1 @@ -6494,7 +6175,7 @@ packages: - supports-color dev: true - /jest-cli@29.7.0(@types/node@20.11.30)(ts-node@10.9.2): + /jest-cli@29.7.0(@types/node@20.12.8)(ts-node@10.9.2): resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -6508,10 +6189,10 @@ packages: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.11.30)(ts-node@10.9.2) + create-jest: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.11.30)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -6522,7 +6203,7 @@ packages: - ts-node dev: true - /jest-config@29.7.0(@types/node@20.11.30)(ts-node@10.9.2): + /jest-config@29.7.0(@types/node@20.12.8)(ts-node@10.9.2): resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -6537,7 +6218,7 @@ packages: '@babel/core': 7.24.3 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.30 + '@types/node': 20.12.8 babel-jest: 29.7.0(@babel/core@7.24.3) chalk: 4.1.2 ci-info: 3.9.0 @@ -6557,7 +6238,7 @@ packages: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.9.2(@types/node@20.11.30)(typescript@5.4.2) + ts-node: 10.9.2(@types/node@20.12.8)(typescript@5.4.5) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -6604,7 +6285,7 @@ packages: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 '@types/jsdom': 20.0.1 - '@types/node': 20.11.30 + '@types/node': 20.12.8 jest-mock: 29.7.0 jest-util: 29.7.0 jsdom: 20.0.3 @@ -6621,7 +6302,7 @@ packages: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.30 + '@types/node': 20.12.8 jest-mock: 29.7.0 jest-util: 29.7.0 dev: true @@ -6637,7 +6318,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.11.30 + '@types/node': 20.12.8 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -6688,7 +6369,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.11.30 + '@types/node': 20.12.8 jest-util: 29.7.0 dev: true @@ -6743,7 +6424,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.30 + '@types/node': 20.12.8 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -6774,7 +6455,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.30 + '@types/node': 20.12.8 chalk: 4.1.2 cjs-module-lexer: 1.2.3 collect-v8-coverage: 1.0.2 @@ -6826,7 +6507,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.11.30 + '@types/node': 20.12.8 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -6851,7 +6532,7 @@ packages: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.30 + '@types/node': 20.12.8 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -6859,26 +6540,17 @@ packages: string-length: 4.0.2 dev: true - /jest-worker@26.6.2: - resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} - engines: {node: '>= 10.13.0'} - dependencies: - '@types/node': 20.12.3 - merge-stream: 2.0.0 - supports-color: 7.2.0 - dev: false - /jest-worker@29.7.0: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.8 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true - /jest@29.7.0(@types/node@20.11.30)(ts-node@10.9.2): + /jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2): resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -6891,7 +6563,7 @@ packages: '@jest/core': 29.7.0(ts-node@10.9.2) '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@20.11.30)(ts-node@10.9.2) + jest-cli: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -6903,8 +6575,8 @@ packages: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} dev: true - /joi@17.11.0: - resolution: {integrity: sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==} + /joi@17.12.3: + resolution: {integrity: sha512-2RRziagf555owrm9IRVtdKynOBeITiDpuZqIpgwqXShPncPKNiRQoiGsl/T8SQdq+8ugRzH2LqY67irr2y/d+g==} dependencies: '@hapi/hoek': 9.3.0 '@hapi/topo': 5.1.0 @@ -6913,11 +6585,6 @@ packages: '@sideway/pinpoint': 2.0.0 dev: true - /js-levenshtein@1.1.6: - resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} - engines: {node: '>=0.10.0'} - dev: true - /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -7080,17 +6747,17 @@ packages: type-check: 0.4.0 dev: true - /license-compliance@2.0.1(typescript@5.4.2): - resolution: {integrity: sha512-c6w74uKnDgvbW3opy8NFMeX1pgWsHl8dLOu7Bc85s9eurXYCzF5x+Sj7gvZJIjtsYu3vQ7aYYpjc23ru+4onPA==} - engines: {node: '>=14.17.0'} + /license-compliance@3.0.0(typescript@5.4.5): + resolution: {integrity: sha512-0kXEr7JSdP+jPSTSEnAiyGvpOoFnkiVXqmTFhXx22+tCay7shTN1mVM7Z+p2F3YNeIhx0tmADglrp5ddWGyHnQ==} + engines: {node: '>=18.20.1'} hasBin: true dependencies: chalk: 4.1.2 - commander: 11.1.0 - cosmiconfig: 8.3.6(typescript@5.4.2) + commander: 12.0.0 + cosmiconfig: 9.0.0(typescript@5.4.5) debug: 4.3.4 - joi: 17.11.0 - spdx-expression-parse: 3.0.1 + joi: 17.12.3 + spdx-expression-parse: 4.0.0 spdx-satisfies: 5.0.1 tslib: 2.6.2 xmlbuilder: 15.1.1 @@ -7220,11 +6887,6 @@ packages: dependencies: js-tokens: 4.0.0 - /lowercase-keys@2.0.0: - resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} - engines: {node: '>=8'} - dev: true - /lru-cache@10.2.0: resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} engines: {node: 14 || >=16.14} @@ -7253,13 +6915,6 @@ packages: sourcemap-codec: 1.4.8 dev: false - /make-dir@3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} - dependencies: - semver: 6.3.1 - dev: true - /make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -7283,6 +6938,7 @@ packages: /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} @@ -7320,16 +6976,6 @@ packages: engines: {node: '>=12'} dev: true - /mimic-response@1.0.1: - resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} - engines: {node: '>=4'} - dev: true - - /mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - dev: true - /min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -7354,6 +7000,13 @@ packages: brace-expansion: 2.0.1 dev: true + /minimatch@9.0.4: + resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} dev: true @@ -7370,44 +7023,35 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: true - /msw@1.3.3(typescript@5.4.2): - resolution: {integrity: sha512-CiPyRFiYJCXYyH/vwxT7m+sa4VZHuUH6cGwRBj0kaTjBGpsk4EnL47YzhoA859htVCF2vzqZuOsomIUlFqg9GQ==} - engines: {node: '>=14'} + /msw@2.2.14(typescript@5.4.5): + resolution: {integrity: sha512-64i8rNCa1xzDK8ZYsTrVMli05D687jty8+Th+PU5VTbJ2/4P7fkQFVyDQ6ZFT5FrNR8z2BHhbY47fKNvfHrumA==} + engines: {node: '>=18'} hasBin: true requiresBuild: true peerDependencies: - typescript: '>= 4.4.x' + typescript: '>= 4.7.x' peerDependenciesMeta: typescript: optional: true dependencies: - '@mswjs/cookies': 0.2.2 - '@mswjs/interceptors': 0.17.10 - '@open-draft/until': 1.0.3 - '@types/cookie': 0.4.1 - '@types/js-levenshtein': 1.1.3 + '@bundled-es-modules/cookie': 2.0.0 + '@bundled-es-modules/statuses': 1.0.1 + '@inquirer/confirm': 3.1.6 + '@mswjs/cookies': 1.1.0 + '@mswjs/interceptors': 0.26.15 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.5 chalk: 4.1.2 - chokidar: 3.6.0 - cookie: 0.4.2 graphql: 16.8.1 - headers-polyfill: 3.2.5 - inquirer: 8.2.6 + headers-polyfill: 4.0.3 is-node-process: 1.2.0 - js-levenshtein: 1.1.6 - node-fetch: 2.7.0 outvariant: 1.4.2 path-to-regexp: 6.2.1 - strict-event-emitter: 0.4.6 - type-fest: 2.19.0 - typescript: 5.4.2 + strict-event-emitter: 0.5.1 + type-fest: 4.18.1 + typescript: 5.4.5 yargs: 17.7.2 - transitivePeerDependencies: - - encoding - - supports-color - dev: true - - /mute-stream@0.0.8: - resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} dev: true /mute-stream@1.0.0: @@ -7429,18 +7073,6 @@ packages: engines: {node: '>=10.5.0'} dev: true - /node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - dependencies: - whatwg-url: 5.0.0 - dev: true - /node-fetch@3.3.2: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -7467,11 +7099,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /normalize-url@6.1.0: - resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} - engines: {node: '>=10'} - dev: true - /npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} @@ -7497,14 +7124,6 @@ packages: /object-inspect@1.13.1: resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} - /object-is@1.1.6: - resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - dev: true - /object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -7628,20 +7247,10 @@ packages: wcwidth: 1.0.1 dev: true - /os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - dev: true - /outvariant@1.4.2: resolution: {integrity: sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==} dev: true - /p-cancelable@2.1.1: - resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} - engines: {node: '>=8'} - dev: true - /p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -7682,16 +7291,6 @@ packages: engines: {node: '>=6'} dev: true - /package-json@7.0.0: - resolution: {integrity: sha512-CHJqc94AA8YfSLHGQT3DbvSIuE12NLFekpM4n7LRrAd3dOJtA911+4xe9q6nC3/jcKraq7nNS9VxgtT0KC+diA==} - engines: {node: '>=12'} - dependencies: - got: 11.8.6 - registry-auth-token: 4.2.2 - registry-url: 5.1.0 - semver: 7.6.0 - dev: true - /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -7794,18 +7393,18 @@ packages: find-up: 3.0.0 dev: true - /playwright-core@1.42.1: - resolution: {integrity: sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==} + /playwright-core@1.43.1: + resolution: {integrity: sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==} engines: {node: '>=16'} hasBin: true dev: true - /playwright@1.42.1: - resolution: {integrity: sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==} + /playwright@1.43.1: + resolution: {integrity: sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==} engines: {node: '>=16'} hasBin: true dependencies: - playwright-core: 1.42.1 + playwright-core: 1.43.1 optionalDependencies: fsevents: 2.3.2 dev: true @@ -7838,13 +7437,13 @@ packages: fast-diff: 1.3.0 dev: true - /prettier-plugin-sort-imports@1.8.4(typescript@5.4.2): - resolution: {integrity: sha512-3Y5TK68TXdP+ViIzRNp4bvjjjPZ0MULL96ImBVTwWtKiIOIcuBIzFmtfPEzOVHaX0tJa3MGChrzmJAsyObvPbA==} + /prettier-plugin-sort-imports@1.8.5(typescript@5.4.5): + resolution: {integrity: sha512-PkizzuO2S8h3kJeWHytnMZXqvv/fD6g+en/dhv4y5QjoiMm1wq3FWzFiFT7c/BilX95l0ZIqJTlMsXYs8z/WQQ==} peerDependencies: typescript: '>4.0.0' dependencies: prettier: 3.2.5 - typescript: 5.4.2 + typescript: 5.4.5 dev: true /prettier@3.2.5: @@ -7896,6 +7495,10 @@ packages: object-assign: 4.1.1 react-is: 16.13.1 + /property-expr@2.0.6: + resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} + dev: false + /proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: false @@ -7904,24 +7507,10 @@ packages: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} dev: true - /pump@3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} - dependencies: - end-of-stream: 1.4.4 - once: 1.4.0 - dev: true - /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - /pupa@2.1.1: - resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==} - engines: {node: '>=8'} - dependencies: - escape-goat: 2.1.1 - dev: true - /pure-rand@6.0.4: resolution: {integrity: sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==} dev: true @@ -7933,43 +7522,37 @@ packages: /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - /quick-lru@5.1.1: - resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} - engines: {node: '>=10'} - dev: true - /randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: safe-buffer: 5.2.1 dev: false - /rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true - dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 - dev: true - - /react-dom@18.2.0(react@18.2.0): - resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + /react-dom@18.3.1(react@18.3.1): + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: - react: ^18.2.0 + react: ^18.3.1 dependencies: loose-envify: 1.4.0 - react: 18.2.0 - scheduler: 0.23.0 + react: 18.3.1 + scheduler: 0.23.2 - /react-error-boundary@4.0.13(react@18.2.0): + /react-error-boundary@4.0.13(react@18.3.1): resolution: {integrity: sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ==} peerDependencies: react: '>=16.13.1' dependencies: '@babel/runtime': 7.24.1 - react: 18.2.0 + react: 18.3.1 + dev: false + + /react-hook-form@7.51.3(react@18.3.1): + resolution: {integrity: sha512-cvJ/wbHdhYx8aviSWh28w9ImjmVsb5Y05n1+FW786vEZQJV5STNM0pW6ujS+oiBecb0ARBxJFyAnXj9+GHXACQ==} + engines: {node: '>=12.22.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + dependencies: + react: 18.3.1 dev: false /react-is@16.13.1: @@ -7982,67 +7565,64 @@ packages: /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - /react-redux@9.1.0(@types/react@18.2.67)(react@18.2.0)(redux@5.0.1): - resolution: {integrity: sha512-6qoDzIO+gbrza8h3hjMA9aq4nwVFCKFtY2iLxCtVT38Swyy2C/dJCGBXHeHLtx6qlg/8qzc2MrhOeduf5K32wQ==} + /react-redux@9.1.2(@types/react@18.3.1)(react@18.3.1)(redux@5.0.1): + resolution: {integrity: sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==} peerDependencies: '@types/react': ^18.2.25 react: ^18.0 - react-native: '>=0.69' redux: ^5.0.0 peerDependenciesMeta: '@types/react': optional: true - react-native: - optional: true redux: optional: true dependencies: - '@types/react': 18.2.67 + '@types/react': 18.3.1 '@types/use-sync-external-store': 0.0.3 - react: 18.2.0 + react: 18.3.1 redux: 5.0.1 - use-sync-external-store: 1.2.0(react@18.2.0) + use-sync-external-store: 1.2.0(react@18.3.1) dev: false - /react-router-dom@6.22.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==} + /react-router-dom@6.23.0(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-Q9YaSYvubwgbal2c9DJKfx6hTNoBp3iJDsl+Duva/DwxoJH+OTXkxGpql4iUK2sla/8z4RpjAm6EWx1qUDuopQ==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' dependencies: - '@remix-run/router': 1.15.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-router: 6.22.3(react@18.2.0) + '@remix-run/router': 1.16.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 6.23.0(react@18.3.1) dev: false - /react-router@6.22.3(react@18.2.0): - resolution: {integrity: sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==} + /react-router@6.23.0(react@18.3.1): + resolution: {integrity: sha512-wPMZ8S2TuPadH0sF5irFGjkNLIcRvOSaEe7v+JER8508dyJumm6XZB1u5kztlX0RVq6AzRVndzqcUh6sFIauzA==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' dependencies: - '@remix-run/router': 1.15.3 - react: 18.2.0 + '@remix-run/router': 1.16.0 + react: 18.3.1 dev: false - /react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): + /react-transition-group@4.4.5(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} peerDependencies: react: '>=16.6.0' react-dom: '>=16.6.0' dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: false - /react@18.2.0: - resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + /react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} dependencies: loose-envify: 1.4.0 @@ -8056,13 +7636,6 @@ packages: util-deprecate: 1.0.2 dev: true - /readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - dependencies: - picomatch: 2.3.1 - dev: true - /readline-transform@1.0.0: resolution: {integrity: sha512-7KA6+N9IGat52d83dvxnApAWN+MtVb1MiVuMR/cf1O4kYsJG+g/Aav0AHcHKsb6StinayfPLne0+fMX2sOzAKg==} engines: {node: '>=6'} @@ -8087,7 +7660,7 @@ packages: /redux@4.2.1: resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 dev: true /redux@5.0.1: @@ -8148,20 +7721,6 @@ packages: unicode-match-property-value-ecmascript: 2.1.0 dev: false - /registry-auth-token@4.2.2: - resolution: {integrity: sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==} - engines: {node: '>=6.0.0'} - dependencies: - rc: 1.2.8 - dev: true - - /registry-url@5.1.0: - resolution: {integrity: sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==} - engines: {node: '>=8'} - dependencies: - rc: 1.2.8 - dev: true - /regjsparser@0.9.1: resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} hasBin: true @@ -8186,10 +7745,6 @@ packages: resolution: {integrity: sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==} dev: false - /resolve-alpn@1.2.1: - resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} - dev: true - /resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} @@ -8232,12 +7787,6 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true - /responselike@2.0.1: - resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} - dependencies: - lowercase-keys: 2.0.0 - dev: true - /restore-cursor@3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} @@ -8269,19 +7818,6 @@ packages: glob: 7.2.3 dev: true - /rollup-plugin-terser@7.0.2(rollup@2.79.1): - resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==} - deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser - peerDependencies: - rollup: ^2.0.0 - dependencies: - '@babel/code-frame': 7.24.2 - jest-worker: 26.6.2 - rollup: 2.79.1 - serialize-javascript: 4.0.0 - terser: 5.30.3 - dev: false - /rollup@2.79.1: resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} engines: {node: '>=10.0.0'} @@ -8314,11 +7850,6 @@ packages: '@rollup/rollup-win32-x64-msvc': 4.14.0 fsevents: 2.3.3 - /run-async@2.4.1: - resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} - engines: {node: '>=0.12.0'} - dev: true - /run-async@3.0.0: resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} engines: {node: '>=0.12.0'} @@ -8329,12 +7860,6 @@ packages: dependencies: queue-microtask: 1.2.3 - /rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} - dependencies: - tslib: 2.6.2 - dev: true - /safe-array-concat@1.1.2: resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} engines: {node: '>=0.4'} @@ -8371,18 +7896,11 @@ packages: xmlchars: 2.2.0 dev: true - /scheduler@0.23.0: - resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} + /scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} dependencies: loose-envify: 1.4.0 - /semver-diff@3.1.1: - resolution: {integrity: sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==} - engines: {node: '>=8'} - dependencies: - semver: 6.3.1 - dev: true - /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -8395,16 +7913,12 @@ packages: lru-cache: 6.0.0 dev: true - /serialize-javascript@4.0.0: - resolution: {integrity: sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==} + /serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} dependencies: randombytes: 2.1.0 dev: false - /set-cookie-parser@2.6.0: - resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} - dev: true - /set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -8486,6 +8000,10 @@ packages: is-fullwidth-code-point: 5.0.0 dev: true + /smob@1.5.0: + resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} + dev: false + /source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} @@ -8544,6 +8062,13 @@ packages: spdx-license-ids: 3.0.17 dev: true + /spdx-expression-parse@4.0.0: + resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.17 + dev: true + /spdx-license-ids@3.0.17: resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==} dev: true @@ -8581,11 +8106,9 @@ packages: escape-string-regexp: 2.0.0 dev: true - /stop-iteration-iterator@1.0.0: - resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} - engines: {node: '>= 0.4'} - dependencies: - internal-slot: 1.0.7 + /statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} dev: true /stream-combiner@0.2.2: @@ -8595,14 +8118,8 @@ packages: through: 2.3.8 dev: true - /strict-event-emitter@0.2.8: - resolution: {integrity: sha512-KDf/ujU8Zud3YaLtMCcTI4xkZlZVIYxTLr+XIULexP+77EEVWixeXroLUXQXiVtH4XH2W7jr/3PT1v3zBuvc3A==} - dependencies: - events: 3.3.0 - dev: true - - /strict-event-emitter@0.4.6: - resolution: {integrity: sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==} + /strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} dev: true /string-argv@0.3.2: @@ -8746,11 +8263,6 @@ packages: min-indent: 1.0.1 dev: true - /strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - dev: true - /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -8795,6 +8307,11 @@ packages: tslib: 2.6.2 dev: true + /tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + dev: true + /temp-dir@2.0.0: resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} engines: {node: '>=8'} @@ -8842,12 +8359,9 @@ packages: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true - /tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} - dependencies: - os-tmpdir: 1.0.2 - dev: true + /tiny-case@1.0.3: + resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} + dev: false /tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -8863,6 +8377,10 @@ packages: dependencies: is-number: 7.0.0 + /toposort@2.0.2: + resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} + dev: false + /tough-cookie@4.1.3: resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==} engines: {node: '>=6'} @@ -8873,10 +8391,6 @@ packages: url-parse: 1.5.10 dev: true - /tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - dev: true - /tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} dependencies: @@ -8895,16 +8409,16 @@ packages: engines: {node: '>= 14.0.0'} dev: true - /ts-api-utils@1.3.0(typescript@5.4.2): + /ts-api-utils@1.3.0(typescript@5.4.5): resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} peerDependencies: typescript: '>=4.2.0' dependencies: - typescript: 5.4.2 + typescript: 5.4.5 dev: true - /ts-jest@29.1.2(@babel/core@7.24.3)(jest@29.7.0)(typescript@5.4.2): + /ts-jest@29.1.2(@babel/core@7.24.3)(jest@29.7.0)(typescript@5.4.5): resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true @@ -8928,17 +8442,17 @@ packages: '@babel/core': 7.24.3 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.11.30)(ts-node@10.9.2) + jest: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.6.0 - typescript: 5.4.2 + typescript: 5.4.5 yargs-parser: 21.1.1 dev: true - /ts-node@10.9.2(@types/node@20.11.30)(typescript@5.4.2): + /ts-node@10.9.2(@types/node@20.12.8)(typescript@5.4.5): resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true peerDependencies: @@ -8957,25 +8471,25 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.11.30 + '@types/node': 20.12.8 acorn: 8.11.3 acorn-walk: 8.3.2 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.4.2 + typescript: 5.4.5 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 dev: true - /tsc-files@1.1.4(typescript@5.4.2): + /tsc-files@1.1.4(typescript@5.4.5): resolution: {integrity: sha512-RePsRsOLru3BPpnf237y1Xe1oCGta8rmSYzM76kYo5tLGsv5R2r3s64yapYorGTPuuLyfS9NVbh9ydzmvNie2w==} hasBin: true peerDependencies: typescript: '>=3' dependencies: - typescript: 5.4.2 + typescript: 5.4.5 dev: true /tsconfig-paths@3.15.0: @@ -9021,13 +8535,18 @@ packages: /type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} - dev: true + dev: false /type-fest@3.13.1: resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} engines: {node: '>=14.16'} dev: true + /type-fest@4.18.1: + resolution: {integrity: sha512-qXhgeNsX15bM63h5aapNFcQid9jRF/l3ojDoDFmekDQEUufZ9U4ErVt6SjDxnHp48Ltrw616R8yNc3giJ3KvVQ==} + engines: {node: '>=16'} + dev: true + /typed-array-buffer@1.0.2: resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} engines: {node: '>= 0.4'} @@ -9068,14 +8587,8 @@ packages: is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 - /typedarray-to-buffer@3.1.5: - resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} - dependencies: - is-typedarray: 1.0.0 - dev: true - - /typescript@5.4.2: - resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==} + /typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} engines: {node: '>=14.17'} hasBin: true @@ -9097,6 +8610,11 @@ packages: '@fastify/busboy': 2.1.1 dev: true + /undici@6.15.0: + resolution: {integrity: sha512-VviMt2tlMg1BvQ0FKXxrz1eJuyrcISrL2sPfBf7ZskX/FCEc/7LeThQaoygsMJpNqrATWQIsRVx+1Dpe4jaYuQ==} + engines: {node: '>=18.17'} + dev: true + /unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} engines: {node: '>=4'} @@ -9125,6 +8643,7 @@ packages: engines: {node: '>=8'} dependencies: crypto-random-string: 2.0.0 + dev: false /universalify@0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} @@ -9163,28 +8682,18 @@ packages: requires-port: 1.0.0 dev: true - /use-sync-external-store@1.2.0(react@18.2.0): + /use-sync-external-store@1.2.0(react@18.3.1): resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - react: 18.2.0 + react: 18.3.1 dev: false /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: true - /util@0.12.5: - resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} - dependencies: - inherits: 2.0.4 - is-arguments: 1.1.1 - is-generator-function: 1.0.10 - is-typed-array: 1.1.13 - which-typed-array: 1.1.15 - dev: true - /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true @@ -9198,14 +8707,14 @@ packages: convert-source-map: 2.0.0 dev: true - /vite-plugin-pwa@0.19.5(vite@5.2.6)(workbox-build@7.0.0)(workbox-window@7.0.0): - resolution: {integrity: sha512-3xJEc2Gmq6SBf730UAV1N2/MqOm+MiyvaLToSTglg+pH9b9qm666yPVxrBBlcOhGoJJWjJpu+Z9tROKek2CODg==} + /vite-plugin-pwa@0.20.0(vite@5.2.11)(workbox-build@7.1.0)(workbox-window@7.1.0): + resolution: {integrity: sha512-/kDZyqF8KqoXRpMUQtR5Atri/7BWayW8Gp7Kz/4bfstsV6zSFTxjREbXZYL7zSuRL40HGA+o2hvUAFRmC+bL7g==} engines: {node: '>=16.0.0'} peerDependencies: '@vite-pwa/assets-generator': ^0.2.4 vite: ^3.1.0 || ^4.0.0 || ^5.0.0 - workbox-build: ^7.0.0 - workbox-window: ^7.0.0 + workbox-build: ^7.1.0 + workbox-window: ^7.1.0 peerDependenciesMeta: '@vite-pwa/assets-generator': optional: true @@ -9213,15 +8722,15 @@ packages: debug: 4.3.4 fast-glob: 3.3.2 pretty-bytes: 6.1.1 - vite: 5.2.6(@types/node@20.11.30) - workbox-build: 7.0.0 - workbox-window: 7.0.0 + vite: 5.2.11(@types/node@20.12.8) + workbox-build: 7.1.0 + workbox-window: 7.1.0 transitivePeerDependencies: - supports-color dev: false - /vite@5.2.6(@types/node@20.11.30): - resolution: {integrity: sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==} + /vite@5.2.11(@types/node@20.12.8): + resolution: {integrity: sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -9248,7 +8757,7 @@ packages: terser: optional: true dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.8 esbuild: 0.20.2 postcss: 8.4.38 rollup: 4.14.0 @@ -9274,21 +8783,14 @@ packages: defaults: 1.0.4 dev: true - /web-encoding@1.1.5: - resolution: {integrity: sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==} - dependencies: - util: 0.12.5 - optionalDependencies: - '@zxing/text-encoding': 0.9.0 - dev: true - /web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} dev: true - /webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + /web-streams-polyfill@4.0.0: + resolution: {integrity: sha512-0zJXHRAYEjM2tUfZ2DiSOHAa2aw1tisnnhU3ufD57R8iefL+DcdJyRBRyJpG+NUimDgbTI/lH+gAE1PAvV3Cgw==} + engines: {node: '>= 8'} dev: true /webidl-conversions@4.0.2: @@ -9320,13 +8822,6 @@ packages: webidl-conversions: 7.0.0 dev: true - /whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - dev: true - /whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} dependencies: @@ -9398,13 +8893,6 @@ packages: isexe: 3.1.1 dev: true - /widest-line@3.1.0: - resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} - engines: {node: '>=8'} - dependencies: - string-width: 4.2.3 - dev: true - /winston-transport@4.7.0: resolution: {integrity: sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==} engines: {node: '>= 12.0.0'} @@ -9431,21 +8919,21 @@ packages: winston-transport: 4.7.0 dev: true - /workbox-background-sync@7.0.0: - resolution: {integrity: sha512-S+m1+84gjdueM+jIKZ+I0Lx0BDHkk5Nu6a3kTVxP4fdj3gKouRNmhO8H290ybnJTOPfBDtTMXSQA/QLTvr7PeA==} + /workbox-background-sync@7.1.0: + resolution: {integrity: sha512-rMbgrzueVWDFcEq1610YyDW71z0oAXLfdRHRQcKw4SGihkfOK0JUEvqWHFwA6rJ+6TClnMIn7KQI5PNN1XQXwQ==} dependencies: idb: 7.1.1 - workbox-core: 7.0.0 + workbox-core: 7.1.0 dev: false - /workbox-broadcast-update@7.0.0: - resolution: {integrity: sha512-oUuh4jzZrLySOo0tC0WoKiSg90bVAcnE98uW7F8GFiSOXnhogfNDGZelPJa+6KpGBO5+Qelv04Hqx2UD+BJqNQ==} + /workbox-broadcast-update@7.1.0: + resolution: {integrity: sha512-O36hIfhjej/c5ar95pO67k1GQw0/bw5tKP7CERNgK+JdxBANQhDmIuOXZTNvwb2IHBx9hj2kxvcDyRIh5nzOgQ==} dependencies: - workbox-core: 7.0.0 + workbox-core: 7.1.0 dev: false - /workbox-build@7.0.0: - resolution: {integrity: sha512-CttE7WCYW9sZC+nUYhQg3WzzGPr4IHmrPnjKiu3AMXsiNQKx+l4hHl63WTrnicLmKEKHScWDH8xsGBdrYgtBzg==} + /workbox-build@7.1.0: + resolution: {integrity: sha512-F6R94XAxjB2j4ETMkP1EXKfjECOtDmyvt0vz3BzgWJMI68TNSXIVNkgatwUKBlPGOfy9n2F/4voYRNAhEvPJNg==} engines: {node: '>=16.0.0'} dependencies: '@apideck/better-ajv-errors': 0.3.6(ajv@8.12.0) @@ -9453,8 +8941,9 @@ packages: '@babel/preset-env': 7.24.4(@babel/core@7.24.4) '@babel/runtime': 7.24.4 '@rollup/plugin-babel': 5.3.1(@babel/core@7.24.4)(rollup@2.79.1) - '@rollup/plugin-node-resolve': 11.2.1(rollup@2.79.1) + '@rollup/plugin-node-resolve': 15.2.3(rollup@2.79.1) '@rollup/plugin-replace': 2.4.2(rollup@2.79.1) + '@rollup/plugin-terser': 0.4.4(rollup@2.79.1) '@surma/rollup-plugin-off-main-thread': 2.2.3 ajv: 8.12.0 common-tags: 1.8.2 @@ -9464,118 +8953,116 @@ packages: lodash: 4.17.21 pretty-bytes: 5.6.0 rollup: 2.79.1 - rollup-plugin-terser: 7.0.2(rollup@2.79.1) source-map: 0.8.0-beta.0 stringify-object: 3.3.0 strip-comments: 2.0.1 tempy: 0.6.0 upath: 1.2.0 - workbox-background-sync: 7.0.0 - workbox-broadcast-update: 7.0.0 - workbox-cacheable-response: 7.0.0 - workbox-core: 7.0.0 - workbox-expiration: 7.0.0 - workbox-google-analytics: 7.0.0 - workbox-navigation-preload: 7.0.0 - workbox-precaching: 7.0.0 - workbox-range-requests: 7.0.0 - workbox-recipes: 7.0.0 - workbox-routing: 7.0.0 - workbox-strategies: 7.0.0 - workbox-streams: 7.0.0 - workbox-sw: 7.0.0 - workbox-window: 7.0.0 + workbox-background-sync: 7.1.0 + workbox-broadcast-update: 7.1.0 + workbox-cacheable-response: 7.1.0 + workbox-core: 7.1.0 + workbox-expiration: 7.1.0 + workbox-google-analytics: 7.1.0 + workbox-navigation-preload: 7.1.0 + workbox-precaching: 7.1.0 + workbox-range-requests: 7.1.0 + workbox-recipes: 7.1.0 + workbox-routing: 7.1.0 + workbox-strategies: 7.1.0 + workbox-streams: 7.1.0 + workbox-sw: 7.1.0 + workbox-window: 7.1.0 transitivePeerDependencies: - '@types/babel__core' - supports-color dev: false - /workbox-cacheable-response@7.0.0: - resolution: {integrity: sha512-0lrtyGHn/LH8kKAJVOQfSu3/80WDc9Ma8ng0p2i/5HuUndGttH+mGMSvOskjOdFImLs2XZIimErp7tSOPmu/6g==} + /workbox-cacheable-response@7.1.0: + resolution: {integrity: sha512-iwsLBll8Hvua3xCuBB9h92+/e0wdsmSVgR2ZlvcfjepZWwhd3osumQB3x9o7flj+FehtWM2VHbZn8UJeBXXo6Q==} dependencies: - workbox-core: 7.0.0 + workbox-core: 7.1.0 dev: false - /workbox-core@7.0.0: - resolution: {integrity: sha512-81JkAAZtfVP8darBpfRTovHg8DGAVrKFgHpOArZbdFd78VqHr5Iw65f2guwjE2NlCFbPFDoez3D3/6ZvhI/rwQ==} + /workbox-core@7.1.0: + resolution: {integrity: sha512-5KB4KOY8rtL31nEF7BfvU7FMzKT4B5TkbYa2tzkS+Peqj0gayMT9SytSFtNzlrvMaWgv6y/yvP9C0IbpFjV30Q==} dev: false - /workbox-expiration@7.0.0: - resolution: {integrity: sha512-MLK+fogW+pC3IWU9SFE+FRStvDVutwJMR5if1g7oBJx3qwmO69BNoJQVaMXq41R0gg3MzxVfwOGKx3i9P6sOLQ==} + /workbox-expiration@7.1.0: + resolution: {integrity: sha512-m5DcMY+A63rJlPTbbBNtpJ20i3enkyOtSgYfv/l8h+D6YbbNiA0zKEkCUaMsdDlxggla1oOfRkyqTvl5Ni5KQQ==} dependencies: idb: 7.1.1 - workbox-core: 7.0.0 + workbox-core: 7.1.0 dev: false - /workbox-google-analytics@7.0.0: - resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==} - deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained + /workbox-google-analytics@7.1.0: + resolution: {integrity: sha512-FvE53kBQHfVTcZyczeBVRexhh7JTkyQ8HAvbVY6mXd2n2A7Oyz/9fIwnY406ZcDhvE4NFfKGjW56N4gBiqkrew==} dependencies: - workbox-background-sync: 7.0.0 - workbox-core: 7.0.0 - workbox-routing: 7.0.0 - workbox-strategies: 7.0.0 + workbox-background-sync: 7.1.0 + workbox-core: 7.1.0 + workbox-routing: 7.1.0 + workbox-strategies: 7.1.0 dev: false - /workbox-navigation-preload@7.0.0: - resolution: {integrity: sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA==} + /workbox-navigation-preload@7.1.0: + resolution: {integrity: sha512-4wyAbo0vNI/X0uWNJhCMKxnPanNyhybsReMGN9QUpaePLTiDpKxPqFxl4oUmBNddPwIXug01eTSLVIFXimRG/A==} dependencies: - workbox-core: 7.0.0 + workbox-core: 7.1.0 dev: false - /workbox-precaching@7.0.0: - resolution: {integrity: sha512-EC0vol623LJqTJo1mkhD9DZmMP604vHqni3EohhQVwhJlTgyKyOkMrZNy5/QHfOby+39xqC01gv4LjOm4HSfnA==} + /workbox-precaching@7.1.0: + resolution: {integrity: sha512-LyxzQts+UEpgtmfnolo0hHdNjoB7EoRWcF7EDslt+lQGd0lW4iTvvSe3v5JiIckQSB5KTW5xiCqjFviRKPj1zA==} dependencies: - workbox-core: 7.0.0 - workbox-routing: 7.0.0 - workbox-strategies: 7.0.0 + workbox-core: 7.1.0 + workbox-routing: 7.1.0 + workbox-strategies: 7.1.0 dev: false - /workbox-range-requests@7.0.0: - resolution: {integrity: sha512-SxAzoVl9j/zRU9OT5+IQs7pbJBOUOlriB8Gn9YMvi38BNZRbM+RvkujHMo8FOe9IWrqqwYgDFBfv6sk76I1yaQ==} + /workbox-range-requests@7.1.0: + resolution: {integrity: sha512-m7+O4EHolNs5yb/79CrnwPR/g/PRzMFYEdo01LqwixVnc/sbzNSvKz0d04OE3aMRel1CwAAZQheRsqGDwATgPQ==} dependencies: - workbox-core: 7.0.0 + workbox-core: 7.1.0 dev: false - /workbox-recipes@7.0.0: - resolution: {integrity: sha512-DntcK9wuG3rYQOONWC0PejxYYIDHyWWZB/ueTbOUDQgefaeIj1kJ7pdP3LZV2lfrj8XXXBWt+JDRSw1lLLOnww==} + /workbox-recipes@7.1.0: + resolution: {integrity: sha512-NRrk4ycFN9BHXJB6WrKiRX3W3w75YNrNrzSX9cEZgFB5ubeGoO8s/SDmOYVrFYp9HMw6sh1Pm3eAY/1gVS8YLg==} dependencies: - workbox-cacheable-response: 7.0.0 - workbox-core: 7.0.0 - workbox-expiration: 7.0.0 - workbox-precaching: 7.0.0 - workbox-routing: 7.0.0 - workbox-strategies: 7.0.0 + workbox-cacheable-response: 7.1.0 + workbox-core: 7.1.0 + workbox-expiration: 7.1.0 + workbox-precaching: 7.1.0 + workbox-routing: 7.1.0 + workbox-strategies: 7.1.0 dev: false - /workbox-routing@7.0.0: - resolution: {integrity: sha512-8YxLr3xvqidnbVeGyRGkaV4YdlKkn5qZ1LfEePW3dq+ydE73hUUJJuLmGEykW3fMX8x8mNdL0XrWgotcuZjIvA==} + /workbox-routing@7.1.0: + resolution: {integrity: sha512-oOYk+kLriUY2QyHkIilxUlVcFqwduLJB7oRZIENbqPGeBP/3TWHYNNdmGNhz1dvKuw7aqvJ7CQxn27/jprlTdg==} dependencies: - workbox-core: 7.0.0 + workbox-core: 7.1.0 dev: false - /workbox-strategies@7.0.0: - resolution: {integrity: sha512-dg3qJU7tR/Gcd/XXOOo7x9QoCI9nk74JopaJaYAQ+ugLi57gPsXycVdBnYbayVj34m6Y8ppPwIuecrzkpBVwbA==} + /workbox-strategies@7.1.0: + resolution: {integrity: sha512-/UracPiGhUNehGjRm/tLUQ+9PtWmCbRufWtV0tNrALuf+HZ4F7cmObSEK+E4/Bx1p8Syx2tM+pkIrvtyetdlew==} dependencies: - workbox-core: 7.0.0 + workbox-core: 7.1.0 dev: false - /workbox-streams@7.0.0: - resolution: {integrity: sha512-moVsh+5to//l6IERWceYKGiftc+prNnqOp2sgALJJFbnNVpTXzKISlTIsrWY+ogMqt+x1oMazIdHj25kBSq/HQ==} + /workbox-streams@7.1.0: + resolution: {integrity: sha512-WyHAVxRXBMfysM8ORwiZnI98wvGWTVAq/lOyBjf00pXFvG0mNaVz4Ji+u+fKa/mf1i2SnTfikoYKto4ihHeS6w==} dependencies: - workbox-core: 7.0.0 - workbox-routing: 7.0.0 + workbox-core: 7.1.0 + workbox-routing: 7.1.0 dev: false - /workbox-sw@7.0.0: - resolution: {integrity: sha512-SWfEouQfjRiZ7GNABzHUKUyj8pCoe+RwjfOIajcx6J5mtgKkN+t8UToHnpaJL5UVVOf5YhJh+OHhbVNIHe+LVA==} + /workbox-sw@7.1.0: + resolution: {integrity: sha512-Hml/9+/njUXBglv3dtZ9WBKHI235AQJyLBV1G7EFmh4/mUdSQuXui80RtjDeVRrXnm/6QWgRUEHG3/YBVbxtsA==} dev: false - /workbox-window@7.0.0: - resolution: {integrity: sha512-j7P/bsAWE/a7sxqTzXo3P2ALb1reTfZdvVp6OJ/uLr/C2kZAMvjeWGm8V4htQhor7DOvYg0sSbFN2+flT5U0qA==} + /workbox-window@7.1.0: + resolution: {integrity: sha512-ZHeROyqR+AS5UPzholQRDttLFqGMwP0Np8MKWAdyxsDETxq3qOAyXvqessc3GniohG6e0mAqSQyKOHmT8zPF7g==} dependencies: '@types/trusted-types': 2.0.7 - workbox-core: 7.0.0 + workbox-core: 7.1.0 dev: false /wrap-ansi@6.2.0: @@ -9617,15 +9104,6 @@ packages: /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - /write-file-atomic@3.0.3: - resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} - dependencies: - imurmurhash: 0.1.4 - is-typedarray: 1.0.0 - signal-exit: 3.0.7 - typedarray-to-buffer: 3.1.5 - dev: true - /write-file-atomic@4.0.2: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -9647,11 +9125,6 @@ packages: optional: true dev: true - /xdg-basedir@4.0.0: - resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} - engines: {node: '>=8'} - dev: true - /xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} @@ -9721,3 +9194,12 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + /yup@1.4.0: + resolution: {integrity: sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==} + dependencies: + property-expr: 2.0.6 + tiny-case: 1.0.3 + toposort: 2.0.2 + type-fest: 2.19.0 + dev: false diff --git a/frontend/scripts/generate-config-files.sh b/frontend/scripts/generate-config-files.sh index 9469cbfcfa..f81c237cb5 100755 --- a/frontend/scripts/generate-config-files.sh +++ b/frontend/scripts/generate-config-files.sh @@ -44,3 +44,11 @@ sed -i -e "s//${E2E_TOKEN_JIRA/%????/1234}/g" "${import_file_nam sed -i -e "s//${E2E_TOKEN_BUILD_KITE/%????/1234}/g" "${import_file_name}" sed -i -e "s//${E2E_TOKEN_GITHUB/%????/1234}/g" "${import_file_name}" echo "Successfully generate ${import_file_name}" + +import_file_name='./e2e/fixtures/input-files/charting-unhappy-path-config-file.json' +echo "Start to generate ${import_file_name}" +cat ./e2e/fixtures/input-files/charting-unhappy-path-config-file.template.json > "${import_file_name}" +sed -i -e "s//${E2E_TOKEN_JIRA}/g" "${import_file_name}" +sed -i -e "s//${E2E_TOKEN_BUILD_KITE}/g" "${import_file_name}" +sed -i -e "s//${E2E_TOKEN_GITHUB}/g" "${import_file_name}" +echo "Successfully generate ${import_file_name}" diff --git a/frontend/src/clients/HttpClient.ts b/frontend/src/clients/HttpClient.ts index 8be0eaf00e..81d9f65389 100644 --- a/frontend/src/clients/HttpClient.ts +++ b/frontend/src/clients/HttpClient.ts @@ -1,3 +1,4 @@ +/* istanbul ignore file */ // to cover #29 import { AXIOS_NETWORK_ERROR_CODES, AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; import { InternalServerError } from '@src/errors/InternalServerError'; import { UnauthorizedError } from '@src/errors/UnauthorizedError'; @@ -24,6 +25,8 @@ export class HttpClient { const { code, response } = error; if (AXIOS_NETWORK_ERROR_CODES.some((predefinedCode) => predefinedCode === code)) { throw new TimeoutError(error?.message, AXIOS_REQUEST_ERROR_CODE.TIMEOUT); + // Can't find any solution to cover below line due to upgrading the msw from v1 to v2 + /* istanbul ignore branch */ } else if (response && response.status && response.status > 0) { const { status, data, statusText } = response; const errorMessage = data?.hintInfo ?? statusText; @@ -45,6 +48,8 @@ export class HttpClient { throw new UnknownError(); } } else { + // Can't find any solution to cover below line due to upgrading the msw from v1 to v2 + /* istanbul ignore next */ throw new UnknownError(); } }, diff --git a/frontend/src/clients/MetricsClient.ts b/frontend/src/clients/MetricsClient.ts index 902f7f50d1..a7ba261885 100644 --- a/frontend/src/clients/MetricsClient.ts +++ b/frontend/src/clients/MetricsClient.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@src/clients/HttpClient'; import { HttpStatusCode } from 'axios'; -export interface getStepsParams { +export interface IStepsParams { pipelineName: string; repository: string; orgName: string; @@ -9,6 +9,13 @@ export interface getStepsParams { endTime: number; } +export interface IStepsRes { + response: string[]; + haveStep: boolean; + branches: string[]; + pipelineCrews: string[]; +} + export class MetricsClient extends HttpClient { steps: string[] = []; haveStep = true; @@ -16,12 +23,12 @@ export class MetricsClient extends HttpClient { pipelineCrews: string[] = []; getSteps = async ( - params: getStepsParams, + params: IStepsParams, organizationId: string, buildId: string, pipelineType: string, token: string, - ) => { + ): Promise => { this.steps = []; this.haveStep = true; const result = await this.axiosInstance.get( diff --git a/frontend/src/clients/board/dto/request.ts b/frontend/src/clients/board/dto/request.ts index 9d04365139..69eb24c4bd 100644 --- a/frontend/src/clients/board/dto/request.ts +++ b/frontend/src/clients/board/dto/request.ts @@ -1,3 +1,5 @@ +import { DateRangeList } from '@src/context/config/configSlice'; + export interface BoardRequestDTO { token: string; type: string; @@ -16,3 +18,8 @@ export interface BoardInfoRequestDTO { boardId: string; projectKey: string; } + +export interface BoardInfoConfigDTO extends BoardRequestDTO { + dateRanges: DateRangeList | null; + projectKey: string; +} diff --git a/frontend/src/clients/pipeline/PipelineToolClient.ts b/frontend/src/clients/pipeline/PipelineToolClient.ts index ee74a0affc..a272da0470 100644 --- a/frontend/src/clients/pipeline/PipelineToolClient.ts +++ b/frontend/src/clients/pipeline/PipelineToolClient.ts @@ -3,6 +3,7 @@ import { PIPELINE_TOOL_GET_INFO_ERROR_CASE_TEXT_MAPPING, PIPELINE_TOOL_GET_INFO_ERROR_MESSAGE, UNKNOWN_ERROR_TITLE, + PIPELINE_CONFIG_TITLE, } from '@src/constants/resources'; import { IPipelineVerifyRequestDTO, PipelineInfoRequestDTO } from '@src/clients/pipeline/dto/request'; import { IPipelineInfoResponseDTO } from '@src/clients/pipeline/dto/response'; @@ -64,7 +65,14 @@ export class PipelineToolClient extends HttpClient { if (isAppError(e)) { const exception = e as IAppError; result.code = exception.code; - result.errorTitle = PIPELINE_TOOL_GET_INFO_ERROR_CASE_TEXT_MAPPING[`${exception.code}`] || UNKNOWN_ERROR_TITLE; + if ( + (exception.code as number) >= HttpStatusCode.BadRequest && + (exception.code as number) < HttpStatusCode.InternalServerError + ) { + result.errorTitle = PIPELINE_CONFIG_TITLE; + } else { + result.errorTitle = UNKNOWN_ERROR_TITLE; + } } result.errorMessage = PIPELINE_TOOL_GET_INFO_ERROR_MESSAGE; diff --git a/frontend/src/clients/pipeline/dto/request.ts b/frontend/src/clients/pipeline/dto/request.ts index 78c249ff58..f67497cb9e 100644 --- a/frontend/src/clients/pipeline/dto/request.ts +++ b/frontend/src/clients/pipeline/dto/request.ts @@ -6,8 +6,6 @@ export interface IPipelineVerifyRequestDTO { export interface PipelineInfoRequestDTO { type: string; token: string; - startTime: string | number | null; - endTime: string | number | null; } export interface PipelineRequestDTO { diff --git a/frontend/src/clients/pipeline/dto/response.ts b/frontend/src/clients/pipeline/dto/response.ts index f15061508c..8125bda16a 100644 --- a/frontend/src/clients/pipeline/dto/response.ts +++ b/frontend/src/clients/pipeline/dto/response.ts @@ -1,5 +1,5 @@ -import { pipeline } from '@src/context/config/pipelineTool/verifyResponseSlice'; +import { IPipeline } from '@src/context/config/pipelineTool/verifyResponseSlice'; export interface IPipelineInfoResponseDTO { - pipelineList: pipeline[]; + pipelineList: IPipeline[]; } diff --git a/frontend/src/clients/report/CSVClient.ts b/frontend/src/clients/report/CSVClient.ts index fccaf92c7f..9d786ce8bb 100644 --- a/frontend/src/clients/report/CSVClient.ts +++ b/frontend/src/clients/report/CSVClient.ts @@ -9,7 +9,13 @@ export class CSVClient extends HttpClient { exportCSVData = async (params: CSVReportRequestDTO) => { await this.axiosInstance - .get(`/reports/${params.dataType}/${params.csvTimeStamp}`, { responseType: 'blob' }) + .get(`/reports/${params.dataType}/${params.csvTimeStamp}`, { + params: { + startTime: this.parseCollectionDateToHumanDate(params.startDate), + endTime: this.parseCollectionDateToHumanDate(params.endDate), + }, + responseType: 'blob', + }) .then((res) => { const exportedFilename = `${params.dataType}-${this.parseCollectionDateToHumanDate( params.startDate, diff --git a/frontend/src/clients/report/ReportClient.ts b/frontend/src/clients/report/ReportClient.ts index 7d7127fc30..a7dfd7579c 100644 --- a/frontend/src/clients/report/ReportClient.ts +++ b/frontend/src/clients/report/ReportClient.ts @@ -2,6 +2,11 @@ import { ReportCallbackResponse, ReportResponseDTO } from '@src/clients/report/d import { ReportRequestDTO } from '@src/clients/report/dto/request'; import { HttpClient } from '@src/clients/HttpClient'; +export interface IPollingRes { + status: number; + response: ReportResponseDTO; +} + export class ReportClient extends HttpClient { status = 0; reportCallbackResponse: ReportCallbackResponse = { @@ -31,7 +36,6 @@ export class ReportClient extends HttpClient { reworkState: 'Done', fromAnalysis: 0, fromInDev: 0, - fromFlag: 0, fromBlock: 0, fromWaitingForTesting: 0, fromTesting: 0, @@ -95,12 +99,10 @@ export class ReportClient extends HttpClient { .catch((e) => { throw e; }); - return { - response: this.reportCallbackResponse, - }; + return this.reportCallbackResponse; }; - polling = async (url: string) => { + polling = async (url: string): Promise => { await this.axiosInstance .get(url) .then((res) => { diff --git a/frontend/src/clients/report/dto/response.ts b/frontend/src/clients/report/dto/response.ts index 4219fae83c..535a33404d 100644 --- a/frontend/src/clients/report/dto/response.ts +++ b/frontend/src/clients/report/dto/response.ts @@ -49,7 +49,6 @@ export interface ReworkTimeResponse { fromAnalysis: number | null; fromInDev: number | null; fromBlock: number | null; - fromFlag: number | null; fromWaitingForTesting: number | null; fromTesting: number | null; fromReview: number | null; @@ -167,9 +166,9 @@ export interface ReportResponse { cycleTimeList?: ReportDataWithTwoColumns[] | null; reworkList?: ReportDataWithTwoColumns[] | null; classification?: ReportDataWithThreeColumns[] | null; - deploymentFrequencyList?: ReportDataWithThreeColumns[] | null; - devMeanTimeToRecoveryList?: ReportDataWithThreeColumns[] | null; + deploymentFrequencyList?: ReportDataWithTwoColumns[] | null; + devMeanTimeToRecoveryList?: ReportDataWithTwoColumns[] | null; leadTimeForChangesList?: ReportDataWithThreeColumns[] | null; - devChangeFailureRateList?: ReportDataWithThreeColumns[] | null; + devChangeFailureRateList?: ReportDataWithTwoColumns[] | null; exportValidityTimeMin?: number | null; } diff --git a/frontend/src/components/Common/DateRangeViewer/index.tsx b/frontend/src/components/Common/DateRangeViewer/index.tsx index a16ce8244e..80237642e7 100644 --- a/frontend/src/components/Common/DateRangeViewer/index.tsx +++ b/frontend/src/components/Common/DateRangeViewer/index.tsx @@ -1,31 +1,47 @@ import { DateRangeContainer, DateRangeExpandContainer, + DateRangeFailedIconContainer, SingleDateRange, StyledArrowForward, StyledCalendarToday, + StyledDateRangeViewerContainer, StyledDivider, + StyledExpandContainer, StyledExpandMoreIcon, } from './style'; +import { + selectMetricsPageFailedTimeRangeInfos, + selectReportPageFailedTimeRangeInfos, + selectStepNumber, +} from '@src/context/stepper/StepperSlice'; import React, { useRef, useState, forwardRef, useEffect, useCallback } from 'react'; -import { TDateRange } from '@src/context/config/configSlice'; -import { formatDate } from '@src/utils/util'; +import { DateRange, DateRangeList } from '@src/context/config/configSlice'; +import { formatDate, formatDateToTimestampString } from '@src/utils/util'; +import PriorityHighIcon from '@mui/icons-material/PriorityHigh'; +import { useAppSelector } from '@src/hooks'; import { theme } from '@src/theme'; type Props = { - dateRanges: TDateRange; - expandColor?: string; - expandBackgroundColor?: string; + dateRangeList: DateRangeList; + selectedDateRange?: DateRange; + changeDateRange?: (dateRange: DateRange) => void; + disabledAll?: boolean; }; -const DateRangeViewer = ({ - dateRanges, - expandColor = theme.palette.text.disabled, - expandBackgroundColor = theme.palette.secondary.dark, -}: Props) => { +const DateRangeViewer = ({ dateRangeList, changeDateRange, selectedDateRange, disabledAll = true }: Props) => { const [showMoreDateRange, setShowMoreDateRange] = useState(false); - const datePick = dateRanges[0]; const DateRangeExpandRef = useRef(null); + const metricsPageFailedTimeRangeInfos = useAppSelector(selectMetricsPageFailedTimeRangeInfos); + const reportPageFailedTimeRangeInfos = useAppSelector(selectReportPageFailedTimeRangeInfos); + const stepNumber = useAppSelector(selectStepNumber); + const currentDateRange: DateRange = selectedDateRange || dateRangeList[0]; + const isMetricsPage = stepNumber === 1; + + const backgroundColor = isMetricsPage ? theme.palette.secondary.dark : theme.palette.common.white; + const currentDateRangeHasFailed = getCurrentDateRangeHasFailed( + formatDateToTimestampString(currentDateRange.startDate!), + ); const handleClickOutside = useCallback((event: MouseEvent) => { if (DateRangeExpandRef.current && !DateRangeExpandRef.current?.contains(event.target as Node)) { @@ -33,6 +49,11 @@ const DateRangeViewer = ({ } }, []); + const handleClick = (key: string) => { + changeDateRange && changeDateRange(dateRangeList.find((dateRange) => dateRange.startDate === key)!); + setShowMoreDateRange(false); + }; + useEffect(() => { document.addEventListener('mousedown', handleClickOutside); return () => { @@ -40,12 +61,32 @@ const DateRangeViewer = ({ }; }, [handleClickOutside]); + function getCurrentDateRangeHasFailed(startDate: string) { + if (isMetricsPage) { + const errorInfo = metricsPageFailedTimeRangeInfos[startDate]; + return !!(errorInfo?.isPipelineInfoError || errorInfo?.isBoardInfoError || errorInfo?.isPipelineStepError); + } else { + const errorInfo = reportPageFailedTimeRangeInfos[startDate]; + return !!(errorInfo?.isPollingError || errorInfo?.isGainPollingUrlError); + } + } + const DateRangeExpand = forwardRef((props, ref: React.ForwardedRef) => { return ( - - {dateRanges.map((dateRange, index) => { + + {dateRangeList.map((dateRange) => { + const disabled = dateRange.disabled || disabledAll; + const hasError = getCurrentDateRangeHasFailed(formatDateToTimestampString(dateRange.startDate!)); return ( - + handleClick(dateRange.startDate!)} + key={dateRange.startDate!} + > + + {hasError && } + {formatDate(dateRange.startDate as string)} {formatDate(dateRange.endDate as string)} @@ -57,15 +98,26 @@ const DateRangeViewer = ({ }); return ( - - {formatDate(datePick.startDate as string)} - - {formatDate(datePick.endDate as string)} - + + + + {currentDateRangeHasFailed && } + + {formatDate(currentDateRange.startDate!)} + + {formatDate(currentDateRange.endDate!)} + + - setShowMoreDateRange(true)} /> - {showMoreDateRange && } - + setShowMoreDateRange(true)}> + + {showMoreDateRange && } + + ); }; diff --git a/frontend/src/components/Common/DateRangeViewer/style.tsx b/frontend/src/components/Common/DateRangeViewer/style.tsx index 531eaad9a0..eb6dc62a4e 100644 --- a/frontend/src/components/Common/DateRangeViewer/style.tsx +++ b/frontend/src/components/Common/DateRangeViewer/style.tsx @@ -4,32 +4,49 @@ import { Divider } from '@mui/material'; import styled from '@emotion/styled'; import { theme } from '@src/theme'; -export const DateRangeContainer = styled.div({ +interface DateRangeContainerProps { + backgroundColor: string; + color: string; +} + +export const StyledDateRangeViewerContainer = styled('div')(({ backgroundColor, color }) => ({ position: 'relative', + width: 'fit-content', + height: '3rem', display: 'flex', - justifyContent: 'flex-start', + flexDirection: 'row', alignItems: 'center', - backgroundColor: theme.palette.secondary.dark, borderRadius: '0.5rem', - border: '0.07rem solid', - borderColor: theme.palette.grey[400], - width: 'fit-content', + border: `0.07rem solid ${theme.palette.grey[400]}`, + backgroundColor: backgroundColor, + color: color, +})); + +export const DateRangeContainer = styled('div')({ + position: 'relative', + display: 'flex', + justifyContent: 'flex-start', + alignItems: 'center', + borderTopLeftRadius: '0.5rem', + borderBottomLeftRadius: '0.5rem', padding: '.75rem', - color: theme.palette.text.disabled, fontSize: '.875rem', }); -export const DateRangeExpandContainer = styled.div({ +interface DateRangeExpandContainerProps { + backgroundColor: string; +} + +export const DateRangeExpandContainer = styled.div(({ backgroundColor }) => ({ position: 'absolute', - top: '4rem', right: '0', - width: '14rem', + top: '4rem', display: 'flex', flexDirection: 'column', gap: '0.0625rem', borderRadius: '0.25rem', filter: `drop-shadow(0 0 0.25rem ${theme.palette.grey[400]})`, - backgroundColor: theme.palette.common.white, + backgroundColor: backgroundColor, zIndex: Z_INDEX.POPOVER, padding: '0.25rem 0', '&:after': { @@ -40,34 +57,47 @@ export const DateRangeExpandContainer = styled.div({ width: '0', height: '0', border: '0.5rem solid transparent', - borderTopColor: theme.palette.common.white, - borderRightColor: theme.palette.common.white, + borderTopColor: backgroundColor, + borderRightColor: backgroundColor, transform: 'rotate(-45deg)', }, -}); +})); interface SingleDateRangeProps { + disabled: boolean; backgroundColor: string; - color: string; } -export const SingleDateRange = styled.div((props) => ({ +export const SingleDateRange = styled('div')(({ disabled, backgroundColor }: SingleDateRangeProps) => ({ display: 'flex', justifyContent: 'center', alignItems: 'center', - backgroundColor: props.backgroundColor, - color: props.color, + height: '1.25rem', + color: theme.palette.text.primary, + backgroundColor: backgroundColor, fontSize: '.875rem', padding: '0.5rem', + paddingRight: '1rem', + cursor: 'pointer', + ...(disabled && { + color: theme.palette.text.disabled, + cursor: 'default', + }), })); + +export const DateRangeFailedIconContainer = styled.div({ + minWidth: '1.5rem', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', +}); + export const StyledArrowForward = styled(ArrowForward)({ - color: theme.palette.text.disabled, margin: '0 .5rem', fontSize: '0.875rem', }); export const StyledCalendarToday = styled(CalendarToday)({ - color: theme.palette.text.disabled, marginLeft: '1rem', fontSize: '.875rem', }); @@ -80,7 +110,16 @@ export const StyledDivider = styled(Divider)({ }); export const StyledExpandMoreIcon = styled(ExpandMore)({ - marginLeft: '1.5rem', color: theme.palette.common.black, cursor: 'pointer', }); + +export const StyledExpandContainer = styled('div')({ + position: 'relative', + height: '100%', + width: '3rem', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + cursor: 'pointer', +}); diff --git a/frontend/src/components/Common/ReportForThreeColumns/index.tsx b/frontend/src/components/Common/ReportForThreeColumns/index.tsx index b5ea98f58c..c85e059a54 100644 --- a/frontend/src/components/Common/ReportForThreeColumns/index.tsx +++ b/frontend/src/components/Common/ReportForThreeColumns/index.tsx @@ -83,11 +83,7 @@ export const ReportForThreeColumns = ({ }); const getTitleUnit = (title: string) => { - return title === METRICS_TITLE.LEAD_TIME_FOR_CHANGES || title === METRICS_TITLE.DEV_MEAN_TIME_TO_RECOVERY - ? REPORT_SUFFIX_UNITS.HOURS - : title === METRICS_TITLE.DEPLOYMENT_FREQUENCY - ? REPORT_SUFFIX_UNITS.DEPLOYMENTS_DAY - : ''; + return title === METRICS_TITLE.LEAD_TIME_FOR_CHANGES ? REPORT_SUFFIX_UNITS.HOURS : ''; }; const renderLoading = () => ( diff --git a/frontend/src/components/Common/ReportForTwoColumns/index.tsx b/frontend/src/components/Common/ReportForTwoColumns/index.tsx index 427258a4af..cc668b12df 100644 --- a/frontend/src/components/Common/ReportForTwoColumns/index.tsx +++ b/frontend/src/components/Common/ReportForTwoColumns/index.tsx @@ -6,6 +6,9 @@ import { StyledTableCell, } from '@src/components/Common/ReportForTwoColumns/style'; import { ReportDataWithTwoColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { EmojiWrap, StyledAvatar, StyledTypography } from '@src/constants/emojis/style'; +import { getEmojiUrls, removeExtraEmojiName } from '@src/constants/emojis/emoji'; +import { METRICS_TITLE, REPORT_SUFFIX_UNITS } from '@src/constants/resources'; import { ReportSelectionTitle } from '@src/containers/MetricsStep/style'; import { Table, TableBody, TableHead, TableRow } from '@mui/material'; import React, { Fragment } from 'react'; @@ -16,11 +19,32 @@ interface ReportForTwoColumnsProps { } export const ReportForTwoColumns = ({ title, data }: ReportForTwoColumnsProps) => { + const transformEmoji = (row: ReportDataWithTwoColumns) => { + if (typeof row.name != 'string') { + return row.name; + } + const name = row.name as string; + const emojiUrls: string[] = getEmojiUrls(name); + if (name.includes(':') && emojiUrls.length > 0) { + const [prefix, suffix] = name.split('/'); + return ( + + {prefix}/ + {emojiUrls.map((url) => ( + + ))} + {removeExtraEmojiName(suffix)} + + ); + } + return {name}; + }; + const renderRows = () => { return data.map((row) => ( - {row.name} + {transformEmoji(row)} {row.valueList[0]?.unit ? `${row.valueList[0].value}${row.valueList[0].unit}` : row.valueList[0].value} @@ -42,7 +66,7 @@ export const ReportForTwoColumns = ({ title, data }: ReportForTwoColumnsProps) = Name - Value + {`Value${getTitleUnit(title)}`} {renderRows()} @@ -52,4 +76,15 @@ export const ReportForTwoColumns = ({ title, data }: ReportForTwoColumnsProps) = ); }; +const getTitleUnit = (title: string) => { + switch (title) { + case METRICS_TITLE.DEV_MEAN_TIME_TO_RECOVERY: + return REPORT_SUFFIX_UNITS.HOURS; + case METRICS_TITLE.DEPLOYMENT_FREQUENCY: + return REPORT_SUFFIX_UNITS.DEPLOYMENTS_DAY; + default: + return ''; + } +}; + export default ReportForTwoColumns; diff --git a/frontend/src/components/HomeGuide/index.tsx b/frontend/src/components/HomeGuide/index.tsx index 8c8247ce26..6ab6df6b2c 100644 --- a/frontend/src/components/HomeGuide/index.tsx +++ b/frontend/src/components/HomeGuide/index.tsx @@ -1,5 +1,6 @@ import { resetMetricData, + savePipelineCrews, setCycleTimeSettingsType, updateMetricsImportedData, } from '@src/context/Metrics/metricsSlice'; @@ -47,6 +48,7 @@ export const HomeGuide = () => { dispatch(updateProjectCreatedState(false)); dispatch(updateBasicConfigState(config)); dispatch(updateMetricsImportedData(config)); + dispatch(savePipelineCrews(config.pipelineCrews)); dispatch(setCycleTimeSettingsType(getCycleTimeSettingsType(config.cycleTime?.type))); navigate(ROUTE.METRICS_PAGE); } else { diff --git a/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PresentationForErrorCases/index.tsx b/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PresentationForErrorCases/index.tsx index 0edbd8d040..1f0efcce60 100644 --- a/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PresentationForErrorCases/index.tsx +++ b/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PresentationForErrorCases/index.tsx @@ -2,17 +2,15 @@ import { StyledContainer, StyledImageContainer, StyledImage, - StyledCommonTitle, - StyledCommonMessage, StyledRetryMessage, StyledRetryButton, } from '@src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PresentationForErrorCases/style'; +import { StyledErrorMessage, StyledErrorSection, StyledErrorTitle } from '@src/components/Common/EmptyContent/styles'; import { PIPELINE_TOOL_RETRY_MESSAGE, PIPELINE_TOOL_RETRY_TRIGGER_MESSAGE } from '@src/constants/resources'; import { IGetPipelineToolInfoResult } from '@src/clients/pipeline/PipelineToolClient'; import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; import errorSvg from '@src/assets/PipelineInfoError.svg'; import React, { useCallback } from 'react'; -import { Box } from '@mui/material'; export interface IPresentationForErrorCasesProps extends IGetPipelineToolInfoResult { retry: () => void; @@ -35,10 +33,10 @@ const PresentationForErrorCases = (props: IPresentationForErrorCasesProps) => { ) : ( - - {props.errorTitle} - {props.errorMessage} - + + {props.errorTitle} + {props.errorMessage} + )} ); diff --git a/frontend/src/constants/commons.ts b/frontend/src/constants/commons.ts index a6ac392633..01314193d1 100644 --- a/frontend/src/constants/commons.ts +++ b/frontend/src/constants/commons.ts @@ -87,3 +87,19 @@ export const GRID_CONFIG = { HALF: { XS: 6, MAX_INDEX: 2, FLEX: 1 }, FULL: { XS: 12, MAX_INDEX: 4, FLEX: 0.25 }, }; + +export enum METRICS_DATA_FAIL_STATUS { + NOT_FAILED, + PARTIAL_FAILED_4XX, + PARTIAL_FAILED_TIMEOUT, + PARTIAL_FAILED_NO_CARDS, + ALL_FAILED_4XX, + ALL_FAILED_TIMEOUT, + ALL_FAILED_NO_CARDS, +} + +export const DOWNLOAD_DIALOG_TITLE = { + [REPORT_TYPES.METRICS]: 'Metrics', + [REPORT_TYPES.BOARD]: 'Board', + [REPORT_TYPES.PIPELINE]: 'Pipeline', +}; diff --git a/frontend/src/constants/fileConfig.ts b/frontend/src/constants/fileConfig.ts index 737dc7ed2a..faa29d14c7 100644 --- a/frontend/src/constants/fileConfig.ts +++ b/frontend/src/constants/fileConfig.ts @@ -1,3 +1,4 @@ +import { SortType } from '@src/containers/ConfigStep/DateRangePicker/types'; import { CALENDAR, REWORK_TIME_LIST } from '@src/constants/resources'; import { IReworkConfig } from '@src/context/Metrics/metricsSlice'; @@ -62,6 +63,7 @@ export interface NewFileConfig { startDate: string; endDate: string; }[]; + sortType: SortType; calendarType: string; metrics: string[]; board?: { @@ -128,6 +130,7 @@ export const convertToNewFileConfig = (fileConfig: OldFileConfig | NewFileConfig reworkTimesSettings, } = fileConfig; return { + sortType: SortType?.DEFAULT, projectName, dateRange, calendarType: considerHoliday ? CALENDAR.CHINA : CALENDAR.REGULAR, diff --git a/frontend/src/constants/resources.ts b/frontend/src/constants/resources.ts index 1efe24ee53..d7561d3c7b 100644 --- a/frontend/src/constants/resources.ts +++ b/frontend/src/constants/resources.ts @@ -11,6 +11,9 @@ export const REPORT_PAGE_TYPE = { DORA: 'DoraReport', }; +export const REJECTED = 'rejected'; +export const FULFILLED = 'fulfilled'; + export const SHOW_MORE = 'show more >'; export const BACK = 'Back'; export const RETRY = 'retry'; @@ -218,7 +221,6 @@ export const REWORK_TIME_MAPPING = { fromAnalysis: 'analysis', fromInDev: 'in dev', fromBlock: 'block', - fromFlag: 'flag', fromReview: 'review', fromWaitingForTesting: 'waiting for testing', fromTesting: 'testing', @@ -231,7 +233,6 @@ export const REWORK_BOARD_STATUS: string[] = [ REWORK_TIME_MAPPING.fromAnalysis, REWORK_TIME_MAPPING.fromInDev, REWORK_TIME_MAPPING.fromBlock, - REWORK_TIME_MAPPING.fromFlag, REWORK_TIME_MAPPING.fromWaitingForTesting, REWORK_TIME_MAPPING.fromTesting, REWORK_TIME_MAPPING.fromReview, @@ -246,7 +247,7 @@ export const DEV_MEAN_TIME_TO_RECOVERY_NAME = 'Dev mean time to recovery'; export const PIPELINE_STEP = 'Pipeline/step'; -export const NAME = 'Name'; +export const SUBTITLE = 'Subtitle'; export const AVERAGE_FIELD = 'Average'; @@ -259,21 +260,18 @@ export enum REPORT_SUFFIX_UNITS { export const MESSAGE = { VERIFY_FAILED_ERROR: 'verify failed', - VERIFY_MAIL_FAILED_ERROR: 'Email is incorrect!', - VERIFY_TOKEN_FAILED_ERROR: 'Token is invalid, please change your token with correct access permission!', - VERIFY_SITE_FAILED_ERROR: 'Site is incorrect!', - VERIFY_BOARD_FAILED_ERROR: 'Board Id is incorrect!', UNKNOWN_ERROR: 'Unknown', GET_STEPS_FAILED: 'Failed to get', HOME_VERIFY_IMPORT_WARNING: 'The content of the imported JSON file is empty. Please confirm carefully', CONFIG_PAGE_VERIFY_IMPORT_ERROR: 'Imported data is not perfectly matched. Please review carefully before going next!', CLASSIFICATION_WARNING: 'Some classifications in import data might be removed.', + FLAG_CARD_DROPPED_WARNING: 'Please note: ’consider the “Flag” as “Block” ‘ has been dropped!', REAL_DONE_WARNING: 'Some selected doneStatus in import data might be removed', ORGANIZATION_WARNING: 'This organization in import data might be removed', PIPELINE_NAME_WARNING: 'This Pipeline in import data might be removed', STEP_WARNING: 'Selected step of this pipeline in import data might be removed', NO_STEP_WARNING: - 'There is no step during this period for this pipeline! Please change the search time in the Config page!', + 'There is no step during these periods for this pipeline! Please change the search time in the Config page!', ERROR_PAGE: 'Something on internet is not quite right. Perhaps head back to our homepage and try again.', EXPIRE_INFORMATION: (value: number) => `The file will expire in ${value} minutes, please download it in time.`, REPORT_LOADING: 'The report is being generated, please do not refresh the page or all the data will be disappeared.', @@ -283,6 +281,14 @@ export const MESSAGE = { 'Failed to get Classification data, please go back to previous page and try again!', FAILED_TO_EXPORT_CSV: 'Failed to export csv.', FAILED_TO_REQUEST: 'Failed to request!', + BOARD_INFO_REQUEST_PARTIAL_FAILED_4XX: + 'Failed to get partial Board configuration, please go back to the previous page and check your board info, or click "Next" button to go to Report page.', + BOARD_INFO_REQUEST_PARTIAL_FAILED_OTHERS: + 'Failed to get partial Board configuration, you can click "Next" button to go to Report page.', + PIPELINE_STEP_REQUEST_PARTIAL_FAILED_4XX: + 'Failed to get partial Pipeline configuration, please go back to the previous page and change your pipeline token with correct access permission, or click "Next" button to go to Report page.', + PIPELINE_STEP_REQUEST_PARTIAL_FAILED_OTHERS: + 'Failed to get partial Pipeline configuration, you can click "Next" button to go to Report page.', }; export const METRICS_CYCLE_SETTING_TABLE_HEADER_BY_COLUMN = [ @@ -335,6 +341,7 @@ export const NO_PIPELINE_STEP_ERROR = 'No steps for this pipeline!'; export enum AXIOS_REQUEST_ERROR_CODE { TIMEOUT = 'NETWORK_TIMEOUT', + NO_CARDS = 'NO_CARDS', } export const BOARD_CONFIG_INFO_TITLE = { @@ -343,13 +350,17 @@ export const BOARD_CONFIG_INFO_TITLE = { UNAUTHORIZED_REQUEST: 'Unauthorized request!', NOT_FOUND: 'Not found!', NO_CONTENT: 'No card within selected date range!', + GENERAL_ERROR: 'Failed to get Board configuration!', EMPTY: '', }; +export const PIPELINE_CONFIG_TITLE = 'Failed to get Pipeline configuration!'; + export const BOARD_CONFIG_INFO_ERROR = { FORBIDDEN: 'Please go back to the previous page and change your board token with correct access permission.', NOT_FOUND: 'Please go back to the previous page and check your board info!', NOT_CONTENT: 'Please go back to the previous page and change your collection date, or check your board info!', + GENERAL_ERROR: 'Please go back to the previous page and check your board info!', RETRY: 'Data loading failed, please', }; @@ -425,5 +436,12 @@ export const TIME_RANGE_TITLE = 'Time range settings'; export const ADD_TIME_RANGE_BUTTON_TEXT = 'New time range'; export const REMOVE_BUTTON_TEXT = 'Remove'; export const MAX_TIME_RANGE_AMOUNT = 6; -export const START_DATE_INVALID_TEXT = 'Start date is invalid'; -export const END_DATE_INVALID_TEXT = 'End date is invalid'; + +export enum SORTING_DATE_RANGE_TEXT { + DEFAULT = 'Default sort', + ASCENDING = 'Ascending', + DESCENDING = 'Descending', +} + +export const DISABLED_DATE_RANGE_MESSAGE = + 'Report generated failed during this period.'; diff --git a/frontend/src/containers/ConfigStep/BasicInfo/RequiredMetrics/index.tsx b/frontend/src/containers/ConfigStep/BasicInfo/RequiredMetrics/index.tsx index 07f490286d..e75fcacb05 100644 --- a/frontend/src/containers/ConfigStep/BasicInfo/RequiredMetrics/index.tsx +++ b/frontend/src/containers/ConfigStep/BasicInfo/RequiredMetrics/index.tsx @@ -1,10 +1,12 @@ import { Checkbox, FormHelperText, InputLabel, ListItemText, MenuItem, Select, SelectChangeEvent } from '@mui/material'; import { RequireDataSelections } from '@src/containers/ConfigStep/BasicInfo/RequiredMetrics/style'; +import { BASIC_INFO_ERROR_MESSAGE } from '@src/containers/ConfigStep/Form/literal'; import { selectMetrics, updateMetrics } from '@src/context/config/configSlice'; import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch'; +import { METRICS_LITERAL } from '@src/containers/ConfigStep/Form/literal'; import { SELECTED_VALUE_SEPARATOR } from '@src/constants/commons'; +import { Controller, useFormContext } from 'react-hook-form'; import { REQUIRED_DATA } from '@src/constants/resources'; -import { useMemo, useCallback } from 'react'; const ALL = 'All'; const ALL_REQUIRED_DATA = Object.values(REQUIRED_DATA) as string[]; @@ -12,50 +14,55 @@ const ALL_REQUIRED_DATA = Object.values(REQUIRED_DATA) as string[]; export const RequiredMetrics = () => { const dispatch = useAppDispatch(); const metrics = useAppSelector(selectMetrics); - - const isEveryOptionsSelected = useCallback( - (options: string[]) => ALL_REQUIRED_DATA.every((metric) => options.includes(metric)), - [], - ); - - const isAllSelected = useMemo(() => isEveryOptionsSelected(metrics), [isEveryOptionsSelected, metrics]); - - const isClickedAll = (options: string[]) => isEveryOptionsSelected(options) || options[options.length - 1] === ALL; - - const onChange = ({ target: { value: selectedOptions } }: SelectChangeEvent) => { - const nextSelectedOptions = isClickedAll(selectedOptions as string[]) - ? isAllSelected - ? [] - : ALL_REQUIRED_DATA - : selectedOptions; - dispatch(updateMetrics(nextSelectedOptions)); - }; - + const { control } = useFormContext(); const onRender = (selected: string[]) => selected.join(SELECTED_VALUE_SEPARATOR); return ( <> Required metrics - - {metrics.length === 0 && Metrics is required} + { + const isEveryOptionsSelected = ALL_REQUIRED_DATA.every((metric) => field.value.includes(metric)); + const onChange = ({ target: { value: selectedOptions } }: SelectChangeEvent) => { + const isClickingAll = selectedOptions[selectedOptions.length - 1] === ALL; + const nextSelectedOptions = isClickingAll + ? isEveryOptionsSelected + ? [] + : ALL_REQUIRED_DATA + : selectedOptions; + field.onChange(nextSelectedOptions); + dispatch(updateMetrics(nextSelectedOptions)); + }; + return ( + <> + + {field.value.length === 0 && ( + {BASIC_INFO_ERROR_MESSAGE.metrics.required} + )} + + ); + }} + /> ); diff --git a/frontend/src/containers/ConfigStep/BasicInfo/index.tsx b/frontend/src/containers/ConfigStep/BasicInfo/index.tsx index d1ad2bee12..e5b6f3191a 100644 --- a/frontend/src/containers/ConfigStep/BasicInfo/index.tsx +++ b/frontend/src/containers/ConfigStep/BasicInfo/index.tsx @@ -1,59 +1,69 @@ -import { - selectCalendarType, - selectProjectName, - selectWarningMessage, - updateCalendarType, - updateProjectName, -} from '@src/context/config/configSlice'; +import { selectWarningMessage, updateCalendarType, updateProjectName } from '@src/context/config/configSlice'; import { CollectionDateLabel, ProjectNameInput, StyledFormControlLabel } from './style'; import { RequiredMetrics } from '@src/containers/ConfigStep/BasicInfo/RequiredMetrics'; import { DateRangePickerSection } from '@src/containers/ConfigStep/DateRangePicker'; +import { BASIC_INFO_ERROR_MESSAGE } from '@src/containers/ConfigStep/Form/literal'; import { WarningNotification } from '@src/components/Common/WarningNotification'; import { ConfigSectionContainer } from '@src/components/Common/ConfigForms'; import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch'; import { ConfigSelectionTitle } from '@src/containers/MetricsStep/style'; -import { DEFAULT_HELPER_TEXT } from '@src/constants/commons'; +import { Controller, useFormContext } from 'react-hook-form'; import { CALENDAR } from '@src/constants/resources'; import { Radio, RadioGroup } from '@mui/material'; -import { useState } from 'react'; const BasicInfo = () => { const dispatch = useAppDispatch(); - const projectName = useAppSelector(selectProjectName); - const calendarType = useAppSelector(selectCalendarType); const warningMessage = useAppSelector(selectWarningMessage); - const [isEmptyProjectName, setIsEmptyProjectName] = useState(false); + const { setError, control } = useFormContext(); return ( <> {warningMessage && } Basic information - { - setIsEmptyProjectName(e.target.value === ''); - }} - onChange={(e) => { - dispatch(updateProjectName(e.target.value)); - setIsEmptyProjectName(e.target.value === ''); - }} - error={isEmptyProjectName} - helperText={isEmptyProjectName ? 'Project name is required' : DEFAULT_HELPER_TEXT} + ( + { + dispatch(updateProjectName(e.target.value)); + field.onChange(e.target.value); + }} + onFocus={() => { + if (field.value === '') { + setError('projectName', { message: BASIC_INFO_ERROR_MESSAGE.projectName.required }); + } + }} + error={fieldState.invalid} + helperText={fieldState.error?.message || ''} + /> + )} /> + Collection Date - { - dispatch(updateCalendarType(e.target.value)); + { + return ( + { + field.onChange(e.target.value); + dispatch(updateCalendarType(e.target.value)); + }} + > + } label={CALENDAR.REGULAR} /> + } label={CALENDAR.CHINA} /> + + ); }} - > - } label={CALENDAR.REGULAR} /> - } label={CALENDAR.CHINA} /> - + /> diff --git a/frontend/src/containers/ConfigStep/Board/FormTextField.tsx b/frontend/src/containers/ConfigStep/Board/FormTextField.tsx new file mode 100644 index 0000000000..fd5213c026 --- /dev/null +++ b/frontend/src/containers/ConfigStep/Board/FormTextField.tsx @@ -0,0 +1,66 @@ +import { BOARD_CONFIG_ERROR_MESSAGE } from '@src/containers/ConfigStep/Form/literal'; +import { IBoardConfigData } from '@src/containers/ConfigStep/Form/schema'; +import { TBoardFieldKeys } from '@src/containers/ConfigStep/Form/type'; +import { StyledTextField } from '@src/components/Common/ConfigForms'; +import { updateBoard } from '@src/context/config/configSlice'; +import { Controller, useFormContext } from 'react-hook-form'; +import { useAppDispatch } from '@src/hooks/useAppDispatch'; +import { KEYS } from '@src/hooks/useVerifyBoardEffect'; +interface IFormTextField { + name: Exclude; + col: number; + label: string; +} + +export const FormTextField = ({ name, col, label }: IFormTextField) => { + const dispatch = useAppDispatch(); + const { + control, + setError, + reset, + formState: { isSubmitSuccessful }, + getValues, + } = useFormContext(); + return ( + { + return ( + { + if (field.value === '') { + setError(name, { message: BOARD_CONFIG_ERROR_MESSAGE[name].required }); + } + }} + onChange={(e) => { + if (isSubmitSuccessful) { + reset(undefined, { keepValues: true, keepErrors: true }); + } + const values = getValues() as IBoardConfigData; + const boardConfig: IBoardConfigData = { + ...values, + [name]: e.target.value, + }; + dispatch(updateBoard(boardConfig)); + field.onChange(e.target.value); + }} + error={fieldState.invalid && fieldState.error?.message !== BOARD_CONFIG_ERROR_MESSAGE.token.timeout} + helperText={ + fieldState.error?.message && fieldState.error?.message !== BOARD_CONFIG_ERROR_MESSAGE.token.timeout + ? fieldState.error?.message + : '' + } + sx={{ gridColumn: `span ${col}` }} + /> + ); + }} + /> + ); +}; diff --git a/frontend/src/containers/ConfigStep/Board/index.tsx b/frontend/src/containers/ConfigStep/Board/index.tsx index ff3406aefc..d3923b9f1b 100644 --- a/frontend/src/containers/ConfigStep/Board/index.tsx +++ b/frontend/src/containers/ConfigStep/Board/index.tsx @@ -1,93 +1,55 @@ -import { - ConfigSectionContainer, - StyledForm, - StyledTextField, - StyledTypeSelections, -} from '@src/components/Common/ConfigForms'; -import { updateShouldGetBoardConfig } from '@src/context/Metrics/metricsSlice'; -import { KEYS, useVerifyBoardEffect } from '@src/hooks/useVerifyBoardEffect'; +import { ConfigSectionContainer, StyledForm } from '@src/components/Common/ConfigForms'; +import { BOARD_CONFIG_ERROR_MESSAGE } from '@src/containers/ConfigStep/Form/literal'; +import { FIELD_KEY, useVerifyBoardEffect } from '@src/hooks/useVerifyBoardEffect'; +import { FormTextField } from '@src/containers/ConfigStep/Board/FormTextField'; +import { FormSingleSelect } from '@src/containers/ConfigStep/Form/FormSelect'; import { ConfigButtonGrop } from '@src/containers/ConfigStep/ConfigButton'; -import { useAppSelector, useAppDispatch } from '@src/hooks/useAppDispatch'; -import { InputLabel, ListItemText, MenuItem, Select } from '@mui/material'; import { ConfigSelectionTitle } from '@src/containers/MetricsStep/style'; -import { selectIsBoardVerified } from '@src/context/config/configSlice'; import { TimeoutAlert } from '@src/containers/ConfigStep/TimeoutAlert'; import { StyledAlterWrapper } from '@src/containers/ConfigStep/style'; -import { BOARD_TYPES, CONFIG_TITLE } from '@src/constants/resources'; +import { CONFIG_TITLE, BOARD_TYPES } from '@src/constants/resources'; import { Loading } from '@src/components/Loading'; -import { FormEvent, useMemo } from 'react'; +import { useFormContext } from 'react-hook-form'; + export const Board = () => { - const dispatch = useAppDispatch(); - const isVerified = useAppSelector(selectIsBoardVerified); + const { verifyJira, isLoading, fields, resetFields } = useVerifyBoardEffect(); const { - verifyJira, - isLoading, - fields, - updateField, - isShowAlert, - setIsShowAlert, - validateField, - resetFields, - isVerifyTimeOut, - } = useVerifyBoardEffect(); - - const onSubmit = async (e: FormEvent) => { - e.preventDefault(); - await verifyJira(); - dispatch(updateShouldGetBoardConfig(true)); - }; + clearErrors, + formState: { isValid, isSubmitSuccessful, errors }, + handleSubmit, + } = useFormContext(); + const isVerifyTimeOut = errors.token?.message === BOARD_CONFIG_ERROR_MESSAGE.token.timeout; + const isVerified = isValid && isSubmitSuccessful; - const isDisableVerifyButton = useMemo( - () => isLoading || fields.some((field) => !field.value || field.validatedError || field.verifiedError), - [fields, isLoading], - ); + const onSubmit = async () => await verifyJira(); + const closeTimeoutAlert = () => clearErrors(fields[FIELD_KEY.TOKEN].key); return ( {isLoading && } {CONFIG_TITLE.BOARD} - + - - {fields.map(({ key, value, validatedError, verifiedError, col }, index) => - !index ? ( - - Board - - - ) : ( - validateField(key)} - onChange={(e) => updateField(key, e.target.value)} - error={!!validatedError || !!verifiedError} - type={key === KEYS.TOKEN ? 'password' : 'text'} - helperText={validatedError || verifiedError} - sx={{ gridColumn: `span ${col}` }} + + {fields.map(({ key, col, label }) => + key === 'type' ? ( + + ) : ( + ), )} diff --git a/frontend/src/containers/ConfigStep/DateRangePicker/DateRangePicker.tsx b/frontend/src/containers/ConfigStep/DateRangePicker/DateRangePicker.tsx index cb2e263119..583397156d 100644 --- a/frontend/src/containers/ConfigStep/DateRangePicker/DateRangePicker.tsx +++ b/frontend/src/containers/ConfigStep/DateRangePicker/DateRangePicker.tsx @@ -4,53 +4,47 @@ import { StyledDateRangePicker, RemoveButton, } from '@src/containers/ConfigStep/DateRangePicker/style'; -import { - DEFAULT_SPRINT_INTERVAL_OFFSET_DAYS, - REMOVE_BUTTON_TEXT, - DATE_RANGE_FORMAT, - START_DATE_INVALID_TEXT, - END_DATE_INVALID_TEXT, -} from '@src/constants/resources'; -import { - initDeploymentFrequencySettings, - updateShouldGetBoardConfig, - updateShouldGetPipelineConfig, -} from '@src/context/Metrics/metricsSlice'; +import { DEFAULT_SPRINT_INTERVAL_OFFSET_DAYS, REMOVE_BUTTON_TEXT, DATE_RANGE_FORMAT } from '@src/constants/resources'; import { isDateDisabled, calculateLastAvailableDate } from '@src/containers/ConfigStep/DateRangePicker/validation'; -import { IRangePickerProps } from '@src/containers/ConfigStep/DateRangePicker/types'; -import { selectDateRange, updateDateRange } from '@src/context/config/configSlice'; -import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch'; +import { BASIC_INFO_ERROR_MESSAGE, AGGREGATED_DATE_ERROR_REASON } from '@src/containers/ConfigStep/Form/literal'; +import { IRangeOnChangeData, IRangePickerProps } from '@src/containers/ConfigStep/DateRangePicker/types'; import CalendarTodayIcon from '@mui/icons-material/CalendarToday'; +import { DateValidationError } from '@mui/x-date-pickers'; import { TextField, TextFieldProps } from '@mui/material'; import { Z_INDEX } from '@src/constants/commons'; +import { useFormContext } from 'react-hook-form'; import { Nullable } from '@src/utils/types'; import dayjs, { Dayjs } from 'dayjs'; -import { useCallback } from 'react'; import isNull from 'lodash/isNull'; -const HelperTextForStartDate = (props: TextFieldProps) => ( - -); +const HelperTextForStartDate = (props: TextFieldProps) => { + const isBlank = props.value === null || props.value === ''; + const isError = props.error || isBlank; + const helperText = isBlank + ? BASIC_INFO_ERROR_MESSAGE.dateRange.startDate.required + : BASIC_INFO_ERROR_MESSAGE.dateRange.startDate.invalid; + return ; +}; -const HelperTextForEndDate = (props: TextFieldProps) => ( - -); +const HelperTextForEndDate = (props: TextFieldProps) => { + const isBlank = props.value === null || props.value === ''; + const isError = props.error || isBlank; + const helperText = isBlank + ? BASIC_INFO_ERROR_MESSAGE.dateRange.endDate.required + : BASIC_INFO_ERROR_MESSAGE.dateRange.endDate.invalid; + return ; +}; -export const DateRangePicker = ({ startDate, endDate, index }: IRangePickerProps) => { - const dispatch = useAppDispatch(); - const dateRangeGroup = useAppSelector(selectDateRange); - const isShowRemoveButton = dateRangeGroup.length > 1; - const dateRangeGroupExcludeSelf = dateRangeGroup.filter((_, idx) => idx !== index); +export const DateRangePicker = ({ startDate, endDate, index, onChange, onRemove, rangeList }: IRangePickerProps) => { + const isShowRemoveButton = rangeList.length > 1; + const dateRangeGroupExcludeSelf = rangeList.filter(({ sortIndex }: { sortIndex: number }) => sortIndex !== index); const shouldStartDateDisableDate = isDateDisabled.bind(null, dateRangeGroupExcludeSelf); const shouldEndDateDisableDate = isDateDisabled.bind(null, dateRangeGroupExcludeSelf); + const startDateFieldName = `dateRange[${index}].startDate`; + const endDateFieldName = `dateRange[${index}].endDate`; + const { setValue } = useFormContext(); - const dispatchUpdateConfig = useCallback(() => { - dispatch(updateShouldGetBoardConfig(true)); - dispatch(updateShouldGetPipelineConfig(true)); - dispatch(initDeploymentFrequencySettings()); - }, [dispatch]); - - const changeStartDate = (value: Nullable) => { + const changeStartDate = (value: Nullable, { validationError }: { validationError: DateValidationError }) => { let daysAddToEndDate = DEFAULT_SPRINT_INTERVAL_OFFSET_DAYS; if (value) { const valueDate = dayjs(value).startOf('day').format(DATE_RANGE_FORMAT); @@ -61,48 +55,71 @@ export const DateRangePicker = ({ startDate, endDate, index }: IRangePickerProps ? DEFAULT_SPRINT_INTERVAL_OFFSET_DAYS : draftDaysAddition; } - const newDateRangeGroup = dateRangeGroup.map(({ startDate, endDate }, idx) => { - if (idx === index) { - return isNull(value) - ? { - startDate: null, - endDate: null, - } - : { - startDate: value.startOf('date').format(DATE_RANGE_FORMAT), - endDate: value.endOf('date').add(daysAddToEndDate, 'day').format(DATE_RANGE_FORMAT), - }; - } - return { - startDate, + let result: IRangeOnChangeData = { startDate: null, endDate: null, startDateError: null, endDateError: null }; + if (isNull(validationError)) { + if (isNull(value)) { + result = { + startDate: null, + endDate: null, + startDateError: BASIC_INFO_ERROR_MESSAGE.dateRange.startDate.required, + endDateError: BASIC_INFO_ERROR_MESSAGE.dateRange.endDate.required, + }; + } else { + result = { + startDate: value.startOf('date').format(DATE_RANGE_FORMAT), + endDate: value.endOf('date').add(daysAddToEndDate, 'day').format(DATE_RANGE_FORMAT), + startDateError: null, + endDateError: null, + }; + } + setValue(startDateFieldName, result.startDate, { shouldValidate: true }); + setValue(endDateFieldName, result.endDate, { shouldValidate: true }); + } else { + result = { + startDate: value!.startOf('date').format(DATE_RANGE_FORMAT), endDate, + startDateError: BASIC_INFO_ERROR_MESSAGE.dateRange.startDate.invalid, + endDateError: rangeList.find((item) => item.sortIndex === index)!.endDateError, }; - }); - dispatch(updateDateRange(newDateRangeGroup)); - dispatchUpdateConfig(); + setValue(startDateFieldName, AGGREGATED_DATE_ERROR_REASON, { shouldValidate: true }); + } + onChange(result, index); }; - const changeEndDate = (value: Nullable) => { - const newDateRangeGroup = dateRangeGroup.map(({ startDate, endDate }, idx) => { - if (idx === index) { - return { + const changeEndDate = (value: Nullable, { validationError }: { validationError: DateValidationError }) => { + let result: IRangeOnChangeData = { startDate: null, endDate: null, startDateError: null, endDateError: null }; + if (isNull(validationError)) { + if (isNull(value)) { + result = { + startDate, + endDate: null, + startDateError: rangeList.find((item) => item.sortIndex === index)!.startDateError, + endDateError: BASIC_INFO_ERROR_MESSAGE.dateRange.endDate.required, + }; + } else { + result = { startDate, - endDate: !isNull(value) ? value.endOf('date').format(DATE_RANGE_FORMAT) : null, + endDate: value.endOf('date').format(DATE_RANGE_FORMAT), + startDateError: null, + endDateError: null, }; } - - return { startDate, endDate }; - }); - dispatch(updateDateRange(newDateRangeGroup)); - dispatchUpdateConfig(); + setValue(startDateFieldName, result.startDate, { shouldValidate: true }); + setValue(endDateFieldName, result.endDate, { shouldValidate: true }); + } else { + result = { + startDate, + endDate: value!.endOf('date').format(DATE_RANGE_FORMAT), + startDateError: rangeList.find((item) => item.sortIndex === index)!.startDateError, + endDateError: BASIC_INFO_ERROR_MESSAGE.dateRange.endDate.invalid, + }; + setValue(endDateFieldName, AGGREGATED_DATE_ERROR_REASON, { shouldValidate: true }); + } + onChange(result, index); }; - const removeSelfHandler = useCallback(() => { - const newDateRangeGroup = dateRangeGroup.filter((_, idx) => idx !== index); - dispatch(updateDateRange(newDateRangeGroup)); - dispatchUpdateConfig(); - }, [dateRangeGroup, dispatch, index, dispatchUpdateConfig]); + const removeSelfHandler = () => onRemove(index); return ( @@ -110,7 +127,7 @@ export const DateRangePicker = ({ startDate, endDate, index }: IRangePickerProps { +const deriveErrorMessageByDate = (date: Nullable, message: string) => (isNull(date) ? message : null); +const enhanceRangeWithMeta = ( + item: { + startDate: string | null; + endDate: string | null; + }, + index: number, +) => ({ + ...item, + startDateError: deriveErrorMessageByDate(item.startDate, BASIC_INFO_ERROR_MESSAGE.dateRange.startDate.required), + endDateError: deriveErrorMessageByDate(item.endDate, BASIC_INFO_ERROR_MESSAGE.dateRange.endDate.required), + sortIndex: index, +}); + +export const DateRangePickerGroup = ({ onError }: TProps) => { const dispatch = useAppDispatch(); const dateRangeGroup = useAppSelector(selectDateRange); + const sortType = useAppSelector(selectDateRangeSortType); const isAddButtonDisabled = dateRangeGroup.length === MAX_TIME_RANGE_AMOUNT; + const [rangeListWithMeta, setRangeListWithMeta] = useState( + dateRangeGroup.map(enhanceRangeWithMeta), + ); + const { setValue } = useFormContext(); + + useEffect(() => { + const rangeListWithErrors = rangeListWithMeta.filter( + ({ startDateError, endDateError }) => startDateError || endDateError, + ); + onError?.(rangeListWithErrors); + }, [onError, rangeListWithMeta]); + + const dispatchUpdateConfig = () => { + dispatch(updateShouldGetBoardConfig(true)); + dispatch(updateShouldGetPipelineConfig(true)); + dispatch(updateShouldMetricsLoaded(true)); + }; + const addRangeHandler = () => { - const newDateRangeGroup = [...dateRangeGroup, { startDate: null, endDate: null }]; - dispatch(updateDateRange(newDateRangeGroup)); + const result = [...rangeListWithMeta, { startDate: null, endDate: null }]; + setRangeListWithMeta(result.map(enhanceRangeWithMeta)); + setValue( + `dateRange`, + result.map(({ startDate, endDate }) => ({ startDate, endDate })), + { shouldValidate: true }, + ); + dispatch(updateDateRange(result.map(({ startDate, endDate }) => ({ startDate, endDate })))); }; + + const handleChange = ({ startDate, endDate, startDateError, endDateError }: IRangeOnChangeData, index: number) => { + const result = rangeListWithMeta.map((item) => + item.sortIndex === index + ? { + ...item, + startDate, + endDate, + startDateError, + endDateError, + } + : { ...item }, + ); + setRangeListWithMeta(result); + dispatchUpdateConfig(); + dispatch(updateDateRange(result.map(({ startDate, endDate }) => ({ startDate, endDate })))); + }; + + const handleRemove = (index: number) => { + const result = [...rangeListWithMeta] + .filter(({ sortIndex }) => sortIndex !== index) + .map((item, index) => ({ ...item, sortIndex: index })); + setValue( + `dateRange`, + result.map(({ startDate, endDate }) => ({ startDate, endDate })), + { shouldValidate: true }, + ); + setRangeListWithMeta(result); + dispatchUpdateConfig(); + dispatch(updateDateRange(result.map(({ startDate, endDate }) => ({ startDate, endDate })))); + }; + return ( - {dateRangeGroup.map(({ startDate, endDate }, index) => ( - + {sortBy(rangeListWithMeta, get(sortFn, sortType)).map(({ startDate, endDate, sortIndex }, index) => ( + ))} { + const dispatch = useAppDispatch(); + const currentSortType = useAppSelector(selectDateRangeSortType); + + const handleChangeSort = () => { + const sortTypes = Object.values(SortType); + const totalSortTypes = sortTypes.length; + const currentIndex = sortTypes.indexOf(currentSortType); + const newIndex = (currentIndex + 1) % totalSortTypes; + const newSortType = sortTypes[newIndex]; + + dispatch(updateDateRangeSortType(newSortType)); + }; + + return ( + + + {SORTING_DATE_RANGE_TEXT[currentSortType]} + + {currentSortType === SortType.ASCENDING ? ( + + ) : ( + + )} + {currentSortType === SortType.DESCENDING ? ( + + ) : ( + + )} + + + + ); +}; diff --git a/frontend/src/containers/ConfigStep/DateRangePicker/index.tsx b/frontend/src/containers/ConfigStep/DateRangePicker/index.tsx index 228097dcd9..5e54fda6a3 100644 --- a/frontend/src/containers/ConfigStep/DateRangePicker/index.tsx +++ b/frontend/src/containers/ConfigStep/DateRangePicker/index.tsx @@ -1,18 +1,33 @@ import { DateRangePickerGroup } from '@src/containers/ConfigStep/DateRangePicker/DateRangePickerGroup'; +import { SortingDateRange } from '@src/containers/ConfigStep/DateRangePicker/SortingDateRange'; +import { SortedDateRangeType } from '@src/containers/ConfigStep/DateRangePicker/types'; import SectionTitleWithTooltip from '@src/components/Common/SectionTitleWithTooltip'; +import { TitleContainer } from '@src/containers/ConfigStep/DateRangePicker/style'; +import { selectDateRange } from '@src/context/config/configSlice'; import { TIME_RANGE_TITLE, TIPS } from '@src/constants/resources'; +import { useAppSelector } from '@src/hooks/useAppDispatch'; +import { useState } from 'react'; export const DateRangePickerSection = () => { + const dateRangeGroup = useAppSelector(selectDateRange); + const [hasError, setHasError] = useState(false); + const handleError = (err: SortedDateRangeType[]) => { + setHasError(!!err.length); + }; + return (
- - + + + {dateRangeGroup.length > 1 && } + +
); }; diff --git a/frontend/src/containers/ConfigStep/DateRangePicker/style.tsx b/frontend/src/containers/ConfigStep/DateRangePicker/style.tsx index f5f3d87e08..f666b92e53 100644 --- a/frontend/src/containers/ConfigStep/DateRangePicker/style.tsx +++ b/frontend/src/containers/ConfigStep/DateRangePicker/style.tsx @@ -1,9 +1,14 @@ +import { ArrowDropDown, ArrowDropUp } from '@mui/icons-material'; import { BasicButton } from '@src/components/Common/Buttons'; +import { Button, IconButton } from '@mui/material'; import { DatePicker } from '@mui/x-date-pickers'; import { styled } from '@mui/material/styles'; -import { Button } from '@mui/material'; import { theme } from '@src/theme'; +type IconProps = { + disabled: boolean; +}; + export const DateRangePickerGroupContainer = styled('div')({ border: `${theme.main.cardBorder}`, borderRadius: '0.25rem', @@ -14,6 +19,7 @@ export const TitleContainer = styled('div')({ display: 'flex', flexDirection: 'row', alignItems: 'center', + justifyContent: 'space-between', }); export const StyledFeaturedRangePickerContainer = styled('div')({ @@ -60,3 +66,38 @@ export const RemoveButton = styled(BasicButton)({ right: 0, top: '3rem', }); + +export const SortingTextButton = styled(Button)({ + cursor: 'default', + backgroundColor: theme.main.button.disabled.color, + '&:hover': { + backgroundColor: theme.main.button.disabled.color, + }, + color: theme.main.color, +}); + +export const SortingButton = styled(IconButton)({ + display: 'flex', + flexDirection: 'column', + padding: 0, + '& svg': { + margin: '-0.9rem 0', + }, + marginRight: '2.4rem', + fontSize: '2.4rem', + color: theme.main.button.disabled.color, +}); + +export const SortingButtoningContainer = styled('div')({ + display: 'flex', +}); + +export const AscendingIcon = styled(ArrowDropUp)(({ theme, disabled }) => ({ + color: disabled ? theme.main.button.disabled.color : theme.main.backgroundColor, + fontSize: 'inherit', +})); + +export const DescendingIcon = styled(ArrowDropDown)(({ theme, disabled }) => ({ + color: disabled ? theme.main.button.disabled.color : theme.main.backgroundColor, + fontSize: 'inherit', +})); diff --git a/frontend/src/containers/ConfigStep/DateRangePicker/types.ts b/frontend/src/containers/ConfigStep/DateRangePicker/types.ts index 18eac31ec6..26337c4add 100644 --- a/frontend/src/containers/ConfigStep/DateRangePicker/types.ts +++ b/frontend/src/containers/ConfigStep/DateRangePicker/types.ts @@ -1,6 +1,44 @@ +import { DateValidationError } from '@mui/x-date-pickers'; +import { Nullable } from '@src/utils/types'; +import dayjs from 'dayjs'; + +export type SortedDateRangeType = { + startDate: string | null; + endDate: string | null; + sortIndex: number; + startDateError: DateValidationError | string | null; + endDateError: DateValidationError | string | null; +}; + +export interface IRangeOnChangeData { + startDate: Nullable; + endDate: Nullable; + startDateError: Nullable; + endDateError: Nullable; +} + export interface IRangePickerProps { startDate: string | null; endDate: string | null; index: number; key?: string | number; + onChange: (data: IRangeOnChangeData, index: number) => void; + onRemove: (index: number) => void; + rangeList: SortedDateRangeType[]; +} + +export enum SortType { + DESCENDING = 'DESCENDING', + ASCENDING = 'ASCENDING', + DEFAULT = 'DEFAULT', } + +export const sortFn = { + DEFAULT: ({ sortIndex }: SortedDateRangeType) => sortIndex, + DESCENDING: ({ startDate }: SortedDateRangeType) => -dayjs(startDate).unix(), + ASCENDING: ({ startDate }: SortedDateRangeType) => dayjs(startDate).unix(), +}; +export type TProps = { + onChange?: (data: SortedDateRangeType[]) => void; + onError?: (data: SortedDateRangeType[]) => void; +}; diff --git a/frontend/src/containers/ConfigStep/DateRangePicker/validation.ts b/frontend/src/containers/ConfigStep/DateRangePicker/validation.ts index ce0edf0666..a3cdf9a38f 100644 --- a/frontend/src/containers/ConfigStep/DateRangePicker/validation.ts +++ b/frontend/src/containers/ConfigStep/DateRangePicker/validation.ts @@ -1,12 +1,12 @@ import dayjsSameOrBeforePlugin from 'dayjs/plugin/isSameOrBefore'; +import { DateRangeList } from '@src/context/config/configSlice'; import dayjsSameOrAfterPlugin from 'dayjs/plugin/isSameOrAfter'; -import { TDateRange } from '@src/context/config/configSlice'; import dayjs, { Dayjs } from 'dayjs'; dayjs.extend(dayjsSameOrBeforePlugin); dayjs.extend(dayjsSameOrAfterPlugin); -export const calculateLastAvailableDate = (date: Dayjs, coveredRange: TDateRange) => { +export const calculateLastAvailableDate = (date: Dayjs, coveredRange: DateRangeList) => { let lastAvailableDate = dayjs(new Date()).startOf('date'); let minimumDiffDays = lastAvailableDate.diff(date, 'days'); @@ -24,7 +24,7 @@ export const calculateLastAvailableDate = (date: Dayjs, coveredRange: TDateRange return lastAvailableDate; }; -export const isDateDisabled = (coveredRange: TDateRange, date: Dayjs) => +export const isDateDisabled = (coveredRange: DateRangeList, date: Dayjs) => coveredRange.some( ({ startDate, endDate }) => date.isSameOrAfter(startDate, 'date') && date.isSameOrBefore(endDate, 'date'), ); diff --git a/frontend/src/containers/ConfigStep/Form/FormSelect.tsx b/frontend/src/containers/ConfigStep/Form/FormSelect.tsx new file mode 100644 index 0000000000..1762a3ccab --- /dev/null +++ b/frontend/src/containers/ConfigStep/Form/FormSelect.tsx @@ -0,0 +1,43 @@ +import { InputLabel, ListItemText, MenuItem, Select } from '@mui/material'; +import { StyledTypeSelections } from '@src/components/Common/ConfigForms'; +import { Controller, useFormContext } from 'react-hook-form'; + +interface IFormSingleSelect { + name: string; + options: string[]; + labelText: string; + labelId?: string; + selectLabelId?: string; + selectAriaLabel?: string; +} + +export const FormSingleSelect = ({ + name, + options, + labelText, + labelId, + selectLabelId, + selectAriaLabel, +}: IFormSingleSelect) => { + const { control } = useFormContext(); + return ( + { + return ( + + {labelText} + + + ); + }} + /> + ); +}; diff --git a/frontend/src/containers/ConfigStep/Form/literal.ts b/frontend/src/containers/ConfigStep/Form/literal.ts new file mode 100644 index 0000000000..b2ac5423c5 --- /dev/null +++ b/frontend/src/containers/ConfigStep/Form/literal.ts @@ -0,0 +1,81 @@ +import { + IBasicInfoErrorMessage, + IBoardConfigErrorMessage, + IPipelineToolErrorMessage, + ISourceControlErrorMessage, +} from '@src/containers/ConfigStep/Form/type'; + +export const AGGREGATED_DATE_ERROR_REASON = 'Invalid date'; +export const CALENDAR_TYPE_LITERAL = ['Regular Calendar(Weekend Considered)', 'Calendar with Chinese Holiday']; +export const METRICS_LITERAL = [ + 'Velocity', + 'Cycle time', + 'Classification', + 'Rework times', + 'Lead time for changes', + 'Deployment frequency', + 'Dev change failure rate', + 'Dev mean time to recovery', +]; +export const BOARD_TYPE_LITERAL = ['Jira']; +export const PIPELINE_TOOL_TYPE_LITERAL = ['BuildKite']; +export const SOURCE_CONTROL_TYPE_LITERAL = ['GitHub']; + +export const BASIC_INFO_ERROR_MESSAGE: IBasicInfoErrorMessage = { + projectName: { + required: 'Project name is required', + }, + metrics: { + required: 'Metrics is required', + }, + dateRange: { + startDate: { + required: 'Start date is required', + invalid: 'Start date is invalid', + }, + endDate: { + required: 'End date is required', + invalid: 'End date is invalid', + }, + }, +}; +export const BOARD_CONFIG_ERROR_MESSAGE: IBoardConfigErrorMessage = { + boardId: { + required: 'Board Id is required!', + invalid: 'Board Id is invalid!', + verifyFailed: 'Board Id is incorrect!', + }, + email: { + required: 'Email is required!', + invalid: 'Email is invalid!', + verifyFailed: 'Email is incorrect!', + }, + site: { + required: 'Site is required!', + verifyFailed: 'Site is incorrect!', + }, + token: { + required: 'Token is required!', + invalid: 'Token is invalid!', + verifyFailed: 'Token is invalid, please change your token with correct access permission!', + timeout: 'Timeout!', + }, +}; +export const PIPELINE_TOOL_ERROR_MESSAGE: IPipelineToolErrorMessage = { + token: { + required: 'Token is required!', + invalid: 'Token is invalid!', + unauthorized: 'Token is incorrect!', + forbidden: 'Forbidden request, please change your token with correct access permission.', + timeout: 'Timeout!', + }, +}; + +export const SOURCE_CONTROL_ERROR_MESSAGE: ISourceControlErrorMessage = { + token: { + required: 'Token is required!', + invalid: 'Token is invalid!', + unauthorized: 'Token is incorrect!', + timeout: 'Timeout!', + }, +}; diff --git a/frontend/src/containers/ConfigStep/Form/schema.ts b/frontend/src/containers/ConfigStep/Form/schema.ts new file mode 100644 index 0000000000..39fa622797 --- /dev/null +++ b/frontend/src/containers/ConfigStep/Form/schema.ts @@ -0,0 +1,101 @@ +import { + CALENDAR_TYPE_LITERAL, + METRICS_LITERAL, + BOARD_TYPE_LITERAL, + PIPELINE_TOOL_TYPE_LITERAL, + SOURCE_CONTROL_TYPE_LITERAL, + BASIC_INFO_ERROR_MESSAGE, + BOARD_CONFIG_ERROR_MESSAGE, + PIPELINE_TOOL_ERROR_MESSAGE, + SOURCE_CONTROL_ERROR_MESSAGE, + AGGREGATED_DATE_ERROR_REASON, +} from '@src/containers/ConfigStep/Form/literal'; +import { object, string, mixed, InferType, array } from 'yup'; +import { REGEX } from '@src/constants/regex'; + +export const basicInfoSchema = object().shape({ + projectName: string().required(BASIC_INFO_ERROR_MESSAGE.projectName.required), + dateRange: array() + .of( + object().shape({ + startDate: string() + .nullable() + .test({ + name: 'CustomStartDateValidation', + test: function (value, context) { + if (value === null) { + return this.createError({ + path: context.path, + message: BASIC_INFO_ERROR_MESSAGE.dateRange.startDate.required, + }); + } + if (value === AGGREGATED_DATE_ERROR_REASON) { + return this.createError({ + path: context.path, + message: BASIC_INFO_ERROR_MESSAGE.dateRange.startDate.invalid, + }); + } else { + return true; + } + }, + }), + endDate: string() + .nullable() + .test({ + name: 'CustomEndDateValidation', + test: function (value, context) { + if (value === null) { + return this.createError({ + path: context.path, + message: BASIC_INFO_ERROR_MESSAGE.dateRange.endDate.required, + }); + } + if (value === AGGREGATED_DATE_ERROR_REASON) { + return this.createError({ + path: context.path, + message: BASIC_INFO_ERROR_MESSAGE.dateRange.endDate.invalid, + }); + } else { + return true; + } + }, + }), + }), + ) + .required(), + calendarType: mixed().oneOf(CALENDAR_TYPE_LITERAL), + metrics: array().of(mixed().oneOf(METRICS_LITERAL)).min(1, BASIC_INFO_ERROR_MESSAGE.metrics.required), +}); + +export const boardConfigSchema = object().shape({ + type: mixed().oneOf(BOARD_TYPE_LITERAL), + boardId: string() + .required(BOARD_CONFIG_ERROR_MESSAGE.boardId.required) + .matches(REGEX.BOARD_ID, { message: BOARD_CONFIG_ERROR_MESSAGE.boardId.invalid }), + email: string() + .required(BOARD_CONFIG_ERROR_MESSAGE.email.required) + .matches(REGEX.EMAIL, { message: BOARD_CONFIG_ERROR_MESSAGE.email.invalid }), + site: string().required(BOARD_CONFIG_ERROR_MESSAGE.site.required), + token: string() + .required(BOARD_CONFIG_ERROR_MESSAGE.token.invalid) + .matches(REGEX.BOARD_TOKEN, { message: BOARD_CONFIG_ERROR_MESSAGE.token.invalid }), +}); + +export const pipelineToolSchema = object().shape({ + type: mixed().oneOf(PIPELINE_TOOL_TYPE_LITERAL), + token: string() + .required(PIPELINE_TOOL_ERROR_MESSAGE.token.required) + .matches(REGEX.BUILDKITE_TOKEN, { message: PIPELINE_TOOL_ERROR_MESSAGE.token.invalid }), +}); + +export const sourceControlSchema = object().shape({ + type: mixed().oneOf(SOURCE_CONTROL_TYPE_LITERAL), + token: string() + .required(SOURCE_CONTROL_ERROR_MESSAGE.token.required) + .matches(REGEX.GITHUB_TOKEN, { message: SOURCE_CONTROL_ERROR_MESSAGE.token.invalid }), +}); + +export type IBasicInfoData = InferType; +export type IBoardConfigData = InferType; +export type IPipelineToolData = InferType; +export type ISourceControlData = InferType; diff --git a/frontend/src/containers/ConfigStep/Form/type.ts b/frontend/src/containers/ConfigStep/Form/type.ts new file mode 100644 index 0000000000..e315cb7094 --- /dev/null +++ b/frontend/src/containers/ConfigStep/Form/type.ts @@ -0,0 +1,69 @@ +export type TBoardFieldKeys = 'type' | 'boardId' | 'email' | 'site' | 'token'; +export type TPipelineToolFieldKeys = 'type' | 'token'; +export type TSourceControlFieldKeys = 'type' | 'token'; +export type TBasicInfoFieldKeys = 'projectName' | 'calendarType' | 'dateRange' | 'metrics'; + +export interface IDateRangeErrorMessage { + startDate: { + required: string; + invalid: string; + }; + endDate: { + required: string; + invalid: string; + }; +} +export interface IBasicInfoErrorMessage + extends Record, Record | IDateRangeErrorMessage> { + projectName: { + required: string; + }; + dateRange: IDateRangeErrorMessage; + metrics: { + required: string; + }; +} +export interface IBoardConfigErrorMessage extends Record, Record> { + boardId: { + required: string; + invalid: string; + verifyFailed: string; + }; + email: { + required: string; + invalid: string; + verifyFailed: string; + }; + site: { + required: string; + verifyFailed: string; + }; + token: { + required: string; + invalid: string; + verifyFailed: string; + timeout: string; + [other: string]: string; + }; +} +export interface IPipelineToolErrorMessage + extends Record, Record> { + token: { + required: string; + invalid: string; + unauthorized: string; + forbidden: string; + timeout: string; + [other: string]: string; + }; +} +export interface ISourceControlErrorMessage + extends Record, Record> { + token: { + required: string; + invalid: string; + unauthorized: string; + timeout: string; + [other: string]: string; + }; +} diff --git a/frontend/src/containers/ConfigStep/Form/useDefaultValues.ts b/frontend/src/containers/ConfigStep/Form/useDefaultValues.ts new file mode 100644 index 0000000000..3bcc57c13a --- /dev/null +++ b/frontend/src/containers/ConfigStep/Form/useDefaultValues.ts @@ -0,0 +1,86 @@ +import { + CALENDAR_TYPE_LITERAL, + BOARD_TYPE_LITERAL, + PIPELINE_TOOL_TYPE_LITERAL, + SOURCE_CONTROL_TYPE_LITERAL, +} from '@src/containers/ConfigStep/Form/literal'; +import { + IBasicInfoData, + IBoardConfigData, + IPipelineToolData, + ISourceControlData, +} from '@src/containers/ConfigStep/Form/schema'; +import { selectBasicInfo, selectBoard, selectPipelineTool, selectSourceControl } from '@src/context/config/configSlice'; +import { SortType } from '@src/containers/ConfigStep/DateRangePicker/types'; +import { useAppSelector } from '@src/hooks/useAppDispatch'; + +export const basicInfoDefaultValues: IBasicInfoData = { + projectName: '', + dateRange: [], + calendarType: CALENDAR_TYPE_LITERAL[0], + metrics: [], +}; + +export const boardConfigDefaultValues: IBoardConfigData = { + type: BOARD_TYPE_LITERAL[0], + boardId: '', + email: '', + site: '', + token: '', +}; + +export const pipelineToolDefaultValues: IPipelineToolData = { + type: PIPELINE_TOOL_TYPE_LITERAL[0], + token: '', +}; + +export const sourceControlDefaultValues: ISourceControlData = { + type: SOURCE_CONTROL_TYPE_LITERAL[0], + token: '', +}; + +export const useDefaultValues = () => { + const basicInfo = useAppSelector(selectBasicInfo); + const boardConfig = useAppSelector(selectBoard); + const pipelineTool = useAppSelector(selectPipelineTool); + const sourceControl = useAppSelector(selectSourceControl); + + const basicInfoWithImport: IBasicInfoData & { sortType: SortType } = { + ...basicInfoDefaultValues, + projectName: basicInfo.projectName, + calendarType: basicInfo.calendarType, + dateRange: basicInfo.dateRange as { startDate: string; endDate: string }[], + metrics: basicInfo.metrics, + sortType: basicInfo.sortType, + }; + + const boardConfigWithImport: IBoardConfigData = { + ...boardConfigDefaultValues, + type: boardConfig.type, + boardId: boardConfig.boardId, + email: boardConfig.email, + site: boardConfig.site, + token: boardConfig.token, + }; + + const pipelineToolWithImport: IPipelineToolData = { + ...pipelineToolDefaultValues, + ...pipelineTool, + }; + + const sourceControlWithImport: ISourceControlData = { + ...sourceControlDefaultValues, + ...sourceControl, + }; + + return { + basicInfoOriginal: basicInfoDefaultValues, + basicInfoWithImport, + boardConfigOriginal: boardConfigDefaultValues, + boardConfigWithImport, + pipelineToolOriginal: pipelineToolDefaultValues, + pipelineToolWithImport, + sourceControlOriginal: sourceControlDefaultValues, + sourceControlWithImport, + }; +}; diff --git a/frontend/src/containers/ConfigStep/MetricsTypeCheckbox/index.tsx b/frontend/src/containers/ConfigStep/MetricsTypeCheckbox/index.tsx deleted file mode 100644 index 789cdc02f6..0000000000 --- a/frontend/src/containers/ConfigStep/MetricsTypeCheckbox/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { SourceControl } from '@src/containers/ConfigStep/SourceControl'; -import { PipelineTool } from '@src/containers/ConfigStep/PipelineTool'; -import { selectConfig } from '@src/context/config/configSlice'; -import { useAppSelector } from '@src/hooks/useAppDispatch'; -import { Board } from '@src/containers/ConfigStep/Board'; - -export const MetricsTypeCheckbox = () => { - const configData = useAppSelector(selectConfig); - const { isShow: isShowBoard } = configData.board; - const { isShow: isShowPipeline } = configData.pipelineTool; - const { isShow: isShowSourceControl } = configData.sourceControl; - - return ( - <> - {isShowBoard && } - {isShowPipeline && } - {isShowSourceControl && } - - ); -}; diff --git a/frontend/src/containers/ConfigStep/PipelineTool/index.tsx b/frontend/src/containers/ConfigStep/PipelineTool/index.tsx index 5ef8da234b..f40e56e9f8 100644 --- a/frontend/src/containers/ConfigStep/PipelineTool/index.tsx +++ b/frontend/src/containers/ConfigStep/PipelineTool/index.tsx @@ -1,168 +1,99 @@ -import { - isPipelineToolVerified, - selectPipelineTool, - updatePipelineTool, - updatePipelineToolVerifyState, -} from '@src/context/config/configSlice'; -import { - ConfigSectionContainer, - StyledForm, - StyledTextField, - StyledTypeSelections, -} from '@src/components/Common/ConfigForms'; -import { CONFIG_TITLE, PIPELINE_TOOL_TYPES, TOKEN_HELPER_TEXT } from '@src/constants/resources'; -import { useVerifyPipelineToolEffect } from '@src/hooks/useVerifyPipelineToolEffect'; -import { updateShouldGetPipelineConfig } from '@src/context/Metrics/metricsSlice'; +import { ConfigSectionContainer, StyledForm, StyledTextField } from '@src/components/Common/ConfigForms'; +import { FIELD_KEY, useVerifyPipelineToolEffect } from '@src/hooks/useVerifyPipelineToolEffect'; +import { PIPELINE_TOOL_ERROR_MESSAGE } from '@src/containers/ConfigStep/Form/literal'; +import { FormSingleSelect } from '@src/containers/ConfigStep/Form/FormSelect'; +import { CONFIG_TITLE, PIPELINE_TOOL_TYPES } from '@src/constants/resources'; import { ConfigButtonGrop } from '@src/containers/ConfigStep/ConfigButton'; -import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch'; -import { DEFAULT_HELPER_TEXT, EMPTY_STRING } from '@src/constants/commons'; -import { InputLabel, ListItemText, MenuItem, Select } from '@mui/material'; +import { IPipelineToolData } from '@src/containers/ConfigStep/Form/schema'; import { ConfigSelectionTitle } from '@src/containers/MetricsStep/style'; import { TimeoutAlert } from '@src/containers/ConfigStep/TimeoutAlert'; import { StyledAlterWrapper } from '@src/containers/ConfigStep/style'; -import { findCaseInsensitiveType } from '@src/utils/util'; -import { FormEvent, useMemo, useState } from 'react'; +import { updatePipelineTool } from '@src/context/config/configSlice'; +import { Controller, useFormContext } from 'react-hook-form'; +import { useAppDispatch } from '@src/hooks/useAppDispatch'; import { Loading } from '@src/components/Loading'; -import { REGEX } from '@src/constants/regex'; - -enum FIELD_KEY { - TYPE = 0, - TOKEN = 1, -} - -const getErrorMessage = (value: string) => { - if (!value) { - return TOKEN_HELPER_TEXT.RequiredTokenText; - } - if (!REGEX.BUILDKITE_TOKEN.test(value.trim())) { - return TOKEN_HELPER_TEXT.InvalidTokenText; - } - return DEFAULT_HELPER_TEXT; -}; export const PipelineTool = () => { const dispatch = useAppDispatch(); - const pipelineToolFields = useAppSelector(selectPipelineTool); - const isVerified = useAppSelector(isPipelineToolVerified); + const { fields, verifyPipelineTool, isLoading, resetFields } = useVerifyPipelineToolEffect(); const { - verifyPipelineTool, - isLoading, - verifiedError, - clearVerifiedError, - isVerifyTimeOut, - isShowAlert, - setIsShowAlert, - } = useVerifyPipelineToolEffect(); - const type = findCaseInsensitiveType(Object.values(PIPELINE_TOOL_TYPES), pipelineToolFields.type); - const [fields, setFields] = useState([ - { - key: 'PipelineTool', - value: type, - validatedError: '', - }, - { - key: 'Token', - value: pipelineToolFields.token, - validatedError: pipelineToolFields.token ? getErrorMessage(pipelineToolFields.token) : '', - }, - ]); - - const handleUpdate = (fields: { key: string; value: string; validatedError: string }[]) => { - clearVerifiedError(); - setFields(fields); - dispatch(updatePipelineToolVerifyState(false)); - dispatch( - updatePipelineTool({ - type: fields[FIELD_KEY.TYPE].value, - token: fields[FIELD_KEY.TOKEN].value, - }), - ); - }; - - const getNewFields = (value: string) => - fields.map((field, index) => - index === FIELD_KEY.TOKEN - ? { - key: field.key, - value: value.trim(), - validatedError: getErrorMessage(value.trim()), - } - : field, - ); - - const onInputUpdate = (value: string) => handleUpdate(getNewFields(value)); - - const onInputFocus = (value: string) => setFields(getNewFields(value)); + control, + setError, + clearErrors, + formState: { isValid, isSubmitSuccessful, errors }, + handleSubmit, + reset, + getValues, + } = useFormContext(); + const isVerifyTimeOut = errors.token?.message === PIPELINE_TOOL_ERROR_MESSAGE.token.timeout; + const isVerified = isValid && isSubmitSuccessful; - const onReset = () => { - const newFields = fields.map(({ key }, index) => ({ - key, - value: index === FIELD_KEY.TYPE ? PIPELINE_TOOL_TYPES.BUILD_KITE : EMPTY_STRING, - validatedError: '', - })); - handleUpdate(newFields); - setIsShowAlert(false); - }; - - const onSubmit = async (e: FormEvent) => { - e.preventDefault(); - await verifyPipelineTool({ - type: fields[FIELD_KEY.TYPE].value, - token: fields[FIELD_KEY.TOKEN].value, - }); - dispatch(updateShouldGetPipelineConfig(true)); - }; - - const isDisableVerifyButton = useMemo( - () => isLoading || fields.some((field) => !field.value || field.validatedError) || !!verifiedError, - [fields, isLoading, verifiedError], - ); + const onSubmit = async () => await verifyPipelineTool(); + const closeTimeoutAlert = () => clearErrors(fields[FIELD_KEY.TOKEN].key); return ( {isLoading && } {CONFIG_TITLE.PIPELINE_TOOL} - + - - - Pipeline Tool - - - onInputFocus(e.target.value)} - onChange={(e) => onInputUpdate(e.target.value)} - error={!!fields[FIELD_KEY.TOKEN].validatedError || !!verifiedError} - helperText={fields[FIELD_KEY.TOKEN].validatedError || verifiedError} + + + { + return ( + { + if (field.value === '') { + setError(fields[FIELD_KEY.TOKEN].key, { + message: PIPELINE_TOOL_ERROR_MESSAGE.token.required, + }); + } + }} + onChange={(e) => { + if (isSubmitSuccessful) { + reset(undefined, { keepValues: true, keepErrors: true }); + } + const pipelineToolConfig: IPipelineToolData = { + ...getValues(), + token: e.target.value, + }; + dispatch(updatePipelineTool(pipelineToolConfig)); + field.onChange(e.target.value); + }} + error={fieldState.invalid && fieldState.error?.message !== PIPELINE_TOOL_ERROR_MESSAGE.token.timeout} + helperText={ + fieldState.error?.message && fieldState.error?.message !== PIPELINE_TOOL_ERROR_MESSAGE.token.timeout + ? fieldState.error?.message + : '' + } + /> + ); + }} /> diff --git a/frontend/src/containers/ConfigStep/SourceControl/index.tsx b/frontend/src/containers/ConfigStep/SourceControl/index.tsx index 69304d3575..f7503b2ece 100644 --- a/frontend/src/containers/ConfigStep/SourceControl/index.tsx +++ b/frontend/src/containers/ConfigStep/SourceControl/index.tsx @@ -1,158 +1,98 @@ -import { - isSourceControlVerified, - selectSourceControl, - updateSourceControl, - updateSourceControlVerifyState, -} from '@src/context/config/configSlice'; -import { - ConfigSectionContainer, - StyledForm, - StyledTextField, - StyledTypeSelections, -} from '@src/components/Common/ConfigForms'; -import { initDeploymentFrequencySettings, updateShouldGetPipelineConfig } from '@src/context/Metrics/metricsSlice'; -import { useVerifySourceControlTokenEffect } from '@src/hooks/useVerifySourceControlTokenEffect'; -import { CONFIG_TITLE, SOURCE_CONTROL_TYPES, TOKEN_HELPER_TEXT } from '@src/constants/resources'; +import { FIELD_KEY, useVerifySourceControlTokenEffect } from '@src/hooks/useVerifySourceControlTokenEffect'; +import { ConfigSectionContainer, StyledForm, StyledTextField } from '@src/components/Common/ConfigForms'; +import { SOURCE_CONTROL_ERROR_MESSAGE } from '@src/containers/ConfigStep/Form/literal'; +import { FormSingleSelect } from '@src/containers/ConfigStep/Form/FormSelect'; +import { CONFIG_TITLE, SOURCE_CONTROL_TYPES } from '@src/constants/resources'; +import { ISourceControlData } from '@src/containers/ConfigStep/Form/schema'; import { ConfigButtonGrop } from '@src/containers/ConfigStep/ConfigButton'; -import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch'; -import { InputLabel, ListItemText, MenuItem, Select } from '@mui/material'; import { ConfigSelectionTitle } from '@src/containers/MetricsStep/style'; import { TimeoutAlert } from '@src/containers/ConfigStep/TimeoutAlert'; import { StyledAlterWrapper } from '@src/containers/ConfigStep/style'; -import { DEFAULT_HELPER_TEXT } from '@src/constants/commons'; -import { findCaseInsensitiveType } from '@src/utils/util'; -import { FormEvent, useMemo, useState } from 'react'; +import { updateSourceControl } from '@src/context/config/configSlice'; +import { Controller, useFormContext } from 'react-hook-form'; +import { useAppDispatch } from '@src/hooks/useAppDispatch'; import { Loading } from '@src/components/Loading'; -import { REGEX } from '@src/constants/regex'; - -enum FIELD_KEY { - TYPE = 0, - TOKEN = 1, -} - -const getErrorMessage = (value: string) => { - if (!value) { - return TOKEN_HELPER_TEXT.RequiredTokenText; - } - if (!REGEX.GITHUB_TOKEN.test(value.trim())) { - return TOKEN_HELPER_TEXT.InvalidTokenText; - } - return DEFAULT_HELPER_TEXT; -}; export const SourceControl = () => { const dispatch = useAppDispatch(); - const sourceControlFields = useAppSelector(selectSourceControl); - const isVerified = useAppSelector(isSourceControlVerified); - const { verifyToken, isLoading, verifiedError, clearVerifiedError, isVerifyTimeOut, isShowAlert, setIsShowAlert } = - useVerifySourceControlTokenEffect(); - const type = findCaseInsensitiveType(Object.values(SOURCE_CONTROL_TYPES), sourceControlFields.type); - const [fields, setFields] = useState([ - { - key: 'SourceControl', - value: type, - validatedError: '', - }, - { - key: 'Token', - value: sourceControlFields.token, - validatedError: sourceControlFields.token ? getErrorMessage(sourceControlFields.token) : '', - }, - ]); - - const handleUpdate = (fields: { key: string; value: string; validatedError: string }[]) => { - clearVerifiedError(); - setFields(fields); - dispatch(updateSourceControlVerifyState(false)); - dispatch( - updateSourceControl({ - type: fields[FIELD_KEY.TYPE].value, - token: fields[FIELD_KEY.TOKEN].value, - }), - ); - dispatch(updateShouldGetPipelineConfig(true)); - dispatch(initDeploymentFrequencySettings()); - }; - - const getNewFields = (value: string) => - fields.map((field, index) => - index === FIELD_KEY.TOKEN - ? { - key: field.key, - value: value.trim(), - validatedError: getErrorMessage(value.trim()), - } - : field, - ); - - const onInputChange = (value: string) => handleUpdate(getNewFields(value)); - - const onInputFocus = (value: string) => setFields(getNewFields(value)); + const { fields, verifyToken, isLoading, resetFields } = useVerifySourceControlTokenEffect(); + const { + control, + setError, + clearErrors, + formState: { isValid, isSubmitSuccessful, errors }, + handleSubmit, + reset, + getValues, + } = useFormContext(); + const isVerifyTimeOut = errors.token?.message === SOURCE_CONTROL_ERROR_MESSAGE.token.timeout; + const isVerified = isValid && isSubmitSuccessful; - const onReset = () => { - const newFields = fields.map(({ key }, index) => ({ - key, - value: index === FIELD_KEY.TOKEN ? '' : SOURCE_CONTROL_TYPES.GITHUB, - validatedError: '', - })); - handleUpdate(newFields); - setIsShowAlert(false); - }; - - const onSubmit = async (e: FormEvent) => { - e.preventDefault(); - await verifyToken({ - type: fields[FIELD_KEY.TYPE].value as SOURCE_CONTROL_TYPES, - token: fields[FIELD_KEY.TOKEN].value, - }); - }; - - const isDisableVerifyButton = useMemo( - () => isLoading || fields.some((field) => !field.value || field.validatedError) || !!verifiedError, - [verifiedError, fields, isLoading], - ); + const onSubmit = async () => await verifyToken(); + const closeTimeoutAlert = () => clearErrors(fields[FIELD_KEY.TOKEN].key); return ( {isLoading && } {CONFIG_TITLE.SOURCE_CONTROL} - + - - - Source Control - - - onInputFocus(e.target.value)} - onChange={(e) => onInputChange(e.target.value)} - error={!!fields[FIELD_KEY.TOKEN].validatedError || !!verifiedError} - helperText={fields[FIELD_KEY.TOKEN].validatedError || verifiedError} + + + { + return ( + { + if (field.value === '') { + setError(fields[FIELD_KEY.TOKEN].key, { + message: SOURCE_CONTROL_ERROR_MESSAGE.token.required, + }); + } + }} + onChange={(e) => { + if (isSubmitSuccessful) { + reset(undefined, { keepValues: true, keepErrors: true }); + } + const sourceControl: ISourceControlData = { + ...getValues(), + token: e.target.value, + }; + dispatch(updateSourceControl(sourceControl)); + field.onChange(e.target.value); + }} + error={fieldState.invalid && fieldState.error?.message !== SOURCE_CONTROL_ERROR_MESSAGE.token.timeout} + helperText={ + fieldState.error?.message && fieldState.error?.message !== SOURCE_CONTROL_ERROR_MESSAGE.token.timeout + ? fieldState.error?.message + : '' + } + /> + ); + }} /> diff --git a/frontend/src/containers/ConfigStep/TimeoutAlert/index.tsx b/frontend/src/containers/ConfigStep/TimeoutAlert/index.tsx index 4a66619849..f855dcf0f0 100644 --- a/frontend/src/containers/ConfigStep/TimeoutAlert/index.tsx +++ b/frontend/src/containers/ConfigStep/TimeoutAlert/index.tsx @@ -4,22 +4,19 @@ import BoldText from '@src/components/Common/BoldText'; import CancelIcon from '@mui/icons-material/Cancel'; interface PropsInterface { - isVerifyTimeOut: boolean; - isShowAlert: boolean; - setIsShowAlert: (value: boolean) => void; + showAlert: boolean; + onClose: () => void; moduleType: string; } -export const TimeoutAlert = ({ isVerifyTimeOut, isShowAlert, setIsShowAlert, moduleType }: PropsInterface) => { +export const TimeoutAlert = ({ showAlert, onClose, moduleType }: PropsInterface) => { return ( <> - {isVerifyTimeOut && isShowAlert && ( + {showAlert && ( } severity='error' - onClose={() => { - setIsShowAlert(false); - }} + onClose={onClose} > Submission timeout on {moduleType}, please reverify! diff --git a/frontend/src/containers/ConfigStep/index.tsx b/frontend/src/containers/ConfigStep/index.tsx index 327f131468..7ab849a79a 100644 --- a/frontend/src/containers/ConfigStep/index.tsx +++ b/frontend/src/containers/ConfigStep/index.tsx @@ -1,22 +1,69 @@ -import { MetricsTypeCheckbox } from '@src/containers/ConfigStep/MetricsTypeCheckbox'; +import { + IBasicInfoData, + IBoardConfigData, + IPipelineToolData, + ISourceControlData, +} from '@src/containers/ConfigStep/Form/schema'; import { closeAllNotifications } from '@src/context/notification/NotificationSlice'; +import { useAppSelector, useAppDispatch } from '@src/hooks/useAppDispatch'; +import { SourceControl } from '@src/containers/ConfigStep/SourceControl'; +import { PipelineTool } from '@src/containers/ConfigStep/PipelineTool'; +import { selectConfig } from '@src/context/config/configSlice'; +import { FormProvider, UseFormReturn } from 'react-hook-form'; import BasicInfo from '@src/containers/ConfigStep/BasicInfo'; -import { useAppDispatch } from '@src/hooks/useAppDispatch'; +import { Board } from '@src/containers/ConfigStep/Board'; +import { useEffect, useLayoutEffect } from 'react'; import { ConfigStepWrapper } from './style'; -import { useLayoutEffect } from 'react'; -const ConfigStep = () => { - const dispatch = useAppDispatch(); +interface IConfigStepProps { + basicInfoMethods: UseFormReturn; + boardConfigMethods: UseFormReturn; + pipelineToolMethods: UseFormReturn; + sourceControlMethods: UseFormReturn; +} +const ConfigStep = ({ + basicInfoMethods, + boardConfigMethods, + pipelineToolMethods, + sourceControlMethods, +}: IConfigStepProps) => { + const dispatch = useAppDispatch(); useLayoutEffect(() => { dispatch(closeAllNotifications()); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const configData = useAppSelector(selectConfig); + const { isShow: isShowBoard } = configData.board; + const { isShow: isShowPipeline } = configData.pipelineTool; + const { isShow: isShowSourceControl } = configData.sourceControl; + + useEffect(() => { + basicInfoMethods.trigger(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return ( - - + + + + {isShowBoard && ( + + + + )} + {isShowPipeline && ( + + + + )} + {isShowSourceControl && ( + + + + )} ); }; diff --git a/frontend/src/containers/MetricsStep/Advance/Advance.tsx b/frontend/src/containers/MetricsStep/Advance/Advance.tsx index aef862eeda..d9c5c4a043 100644 --- a/frontend/src/containers/MetricsStep/Advance/Advance.tsx +++ b/frontend/src/containers/MetricsStep/Advance/Advance.tsx @@ -4,11 +4,19 @@ import { ItemCheckbox, TitleAndTooltipContainer, TooltipContainer } from '../Cyc import HelpOutlineOutlinedIcon from '@mui/icons-material/HelpOutlineOutlined'; import { StyledLink } from '@src/containers/MetricsStep/style'; import { useAppDispatch } from '@src/hooks/useAppDispatch'; -import { Field } from '@src/hooks/useVerifyBoardEffect'; import { useAppSelector } from '@src/hooks'; import { TextField } from '@mui/material'; import React, { useState } from 'react'; +export interface Field { + key: string; + value: string; + validateRule?: (value: string) => boolean; + validatedError: string; + verifiedError: string; + col: number; +} + export const Advance = () => { const url = 'https://github.com/au-heartbeat/Heartbeat/blob/main/README.md#323-setting-advanced-setting'; const dispatch = useAppDispatch(); diff --git a/frontend/src/containers/MetricsStep/CycleTime/Table/index.tsx b/frontend/src/containers/MetricsStep/CycleTime/Table/index.tsx index b53e8533c8..56f6aa7eda 100644 --- a/frontend/src/containers/MetricsStep/CycleTime/Table/index.tsx +++ b/frontend/src/containers/MetricsStep/CycleTime/Table/index.tsx @@ -1,17 +1,19 @@ -import { - CYCLE_TIME_SETTINGS_TYPES, - DONE, - METRICS_CONSTANTS, - METRICS_CYCLE_SETTING_TABLE_HEADER_BY_COLUMN, - METRICS_CYCLE_SETTING_TABLE_HEADER_BY_STATUS, -} from '@src/constants/resources'; import { updateCycleTimeSettings, saveDoneColumn, selectMetricsContent, setCycleTimeSettingsType, updateReworkTimesSettings, + updateTreatFlagCardAsBlock, + ICycleTimeSetting, } from '@src/context/Metrics/metricsSlice'; +import { + CYCLE_TIME_SETTINGS_TYPES, + DONE, + METRICS_CONSTANTS, + METRICS_CYCLE_SETTING_TABLE_HEADER_BY_COLUMN, + METRICS_CYCLE_SETTING_TABLE_HEADER_BY_STATUS, +} from '@src/constants/resources'; import { StyledRadioGroup, StyledTableHeaderCell, @@ -21,6 +23,7 @@ import { FormControlLabel, Radio, Table, TableBody, TableContainer, TableHead, T import CellAutoComplete from '@src/containers/MetricsStep/CycleTime/Table/CellAutoComplete'; import EllipsisText from '@src/components/Common/EllipsisText'; import { useAppDispatch } from '@src/hooks/useAppDispatch'; +import { existBlockState } from '@src/utils/util'; import { useAppSelector } from '@src/hooks'; import React, { useCallback } from 'react'; import { theme } from '@src/theme'; @@ -45,8 +48,18 @@ const CycleTimeTable = () => { [cycleTimeSettings, dispatch], ); + const updateTreatFlagCardAsBlockByCycleTimeSetting = useCallback( + (newCycleTimeSettings: ICycleTimeSetting[], preHasBlockState: boolean) => { + if (!existBlockState(newCycleTimeSettings) && preHasBlockState) { + dispatch(updateTreatFlagCardAsBlock(true)); + } + }, + [dispatch], + ); + const saveCycleTimeOptions = useCallback( (name: string, value: string) => { + const preHasBlockState = existBlockState(cycleTimeSettings); const newCycleTimeSettings = cycleTimeSettings.map((item) => (isColumnAsKey ? item.column === name : item.status === name) ? { @@ -56,10 +69,11 @@ const CycleTimeTable = () => { : item, ); isColumnAsKey && resetRealDoneColumn(name, value); + updateTreatFlagCardAsBlockByCycleTimeSetting(newCycleTimeSettings, preHasBlockState); dispatch(updateCycleTimeSettings(newCycleTimeSettings)); dispatch(updateReworkTimesSettings({ excludeStates: [], reworkState: null })); }, - [cycleTimeSettings, dispatch, isColumnAsKey, resetRealDoneColumn], + [updateTreatFlagCardAsBlockByCycleTimeSetting, cycleTimeSettings, dispatch, isColumnAsKey, resetRealDoneColumn], ); const header = isColumnAsKey @@ -89,6 +103,9 @@ const CycleTimeTable = () => { ), ); dispatch(saveDoneColumn([])); + if (!existBlockState(cycleTimeSettings)) { + dispatch(updateTreatFlagCardAsBlock(true)); + } }; return ( diff --git a/frontend/src/containers/MetricsStep/CycleTime/index.tsx b/frontend/src/containers/MetricsStep/CycleTime/index.tsx index 2152dbc3e5..53683d4a93 100644 --- a/frontend/src/containers/MetricsStep/CycleTime/index.tsx +++ b/frontend/src/containers/MetricsStep/CycleTime/index.tsx @@ -1,20 +1,49 @@ +import { + selectCycleTimeWarningMessage, + selectDisplayFlagCardDropWarning, + selectMetricsContent, + selectTreatFlagCardAsBlock, + updateDisplayFlagCardDropWarning, + updateTreatFlagCardAsBlock, +} from '@src/context/Metrics/metricsSlice'; import SectionTitleWithTooltip from '@src/components/Common/SectionTitleWithTooltip'; -import { selectCycleTimeWarningMessage } from '@src/context/Metrics/metricsSlice'; import { WarningNotification } from '@src/components/Common/WarningNotification'; import CycleTimeTable from '@src/containers/MetricsStep/CycleTime/Table'; import FlagCard from '@src/containers/MetricsStep/CycleTime/FlagCard'; -import { TIPS } from '@src/constants/resources'; +import { useAppDispatch } from '@src/hooks/useAppDispatch'; +import { MESSAGE, TIPS } from '@src/constants/resources'; +import { useEffect, useMemo, useState } from 'react'; +import { existBlockState } from '@src/utils/util'; import { useAppSelector } from '@src/hooks'; export const CycleTime = () => { + const dispatch = useAppDispatch(); + const flagCardAsBlock = useAppSelector(selectTreatFlagCardAsBlock); + const displayFlagCardDropWarning = useAppSelector(selectDisplayFlagCardDropWarning); const warningMessage = useAppSelector(selectCycleTimeWarningMessage); + const { cycleTimeSettings } = useAppSelector(selectMetricsContent); + const hasBlockState = useMemo(() => { + return existBlockState(cycleTimeSettings); + }, [cycleTimeSettings]); + const [shouldShowConflictMessage, setShouldShowConflictMessage] = useState(false); + + useEffect(() => { + if (hasBlockState && displayFlagCardDropWarning) { + setShouldShowConflictMessage(true); + dispatch(updateDisplayFlagCardDropWarning(false)); + } + if (hasBlockState && flagCardAsBlock) { + dispatch(updateTreatFlagCardAsBlock(false)); + } + }, [dispatch, flagCardAsBlock, displayFlagCardDropWarning, hasBlockState]); return (
+ {shouldShowConflictMessage && } {warningMessage && } - + {hasBlockState || }
); }; diff --git a/frontend/src/containers/MetricsStep/DeploymentFrequencySettings/BranchSelection/index.tsx b/frontend/src/containers/MetricsStep/DeploymentFrequencySettings/BranchSelection/index.tsx index afc4020325..4e38543caa 100644 --- a/frontend/src/containers/MetricsStep/DeploymentFrequencySettings/BranchSelection/index.tsx +++ b/frontend/src/containers/MetricsStep/DeploymentFrequencySettings/BranchSelection/index.tsx @@ -20,16 +20,16 @@ export interface BranchSelectionProps { organization: string; pipelineName: string; branches: string[]; + isStepLoading: boolean; onUpdatePipeline: (id: number, label: string, value: string[] | unknown) => void; } export const BranchSelection = (props: BranchSelectionProps) => { const dispatch = useAppDispatch(); - const { id, organization, pipelineName, branches, onUpdatePipeline } = props; + const { id, organization, pipelineName, branches, onUpdatePipeline, isStepLoading } = props; const formMeta = useAppSelector(getFormMeta); const pipelineList = useAppSelector(selectPipelineList); const sourceControlFields = useAppSelector(selectSourceControl); - const currentPipeline = useMemo( () => pipelineList.find((pipeline) => pipeline.name === pipelineName && pipeline.orgName === organization), [organization, pipelineList, pipelineName], @@ -132,10 +132,10 @@ export const BranchSelection = (props: BranchSelectionProps) => { )} renderTags={(selectedOptions, getTagProps) => diff --git a/frontend/src/containers/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection/index.tsx b/frontend/src/containers/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection/index.tsx index d4264dd74d..0b0614cf0e 100644 --- a/frontend/src/containers/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection/index.tsx +++ b/frontend/src/containers/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection/index.tsx @@ -5,13 +5,16 @@ import { updatePipelineStep, updateShouldGetPipelineConfig, selectShouldGetPipelineConfig, + updatePiplineCrews, } from '@src/context/Metrics/metricsSlice'; import { + updatePipelineToolVerifyResponseCrews, selectPipelineNames, selectPipelineOrganizations, selectSteps, selectStepsParams, updatePipelineToolVerifyResponseSteps, + selectPipelineList, } from '@src/context/config/configSlice'; import { SingleSelection } from '@src/containers/MetricsStep/DeploymentFrequencySettings/SingleSelection'; @@ -19,11 +22,14 @@ import { BranchSelection } from '@src/containers/MetricsStep/DeploymentFrequency import { ButtonWrapper, PipelineMetricSelectionWrapper, RemoveButton, WarningMessage } from './style'; import { WarningNotification } from '@src/components/Common/WarningNotification'; import { useGetMetricsStepsEffect } from '@src/hooks/useGetMetricsStepsEffect'; +import { addNotification } from '@src/context/notification/NotificationSlice'; +import { uniqPipelineListCrews, updateResponseCrews } from '@src/utils/util'; import { MESSAGE, NO_PIPELINE_STEP_ERROR } from '@src/constants/resources'; +import { shouldMetricsLoaded } from '@src/context/stepper/StepperSlice'; import { ErrorNotification } from '@src/components/ErrorNotification'; -import { shouldMetricsLoad } from '@src/context/stepper/StepperSlice'; +import { METRICS_DATA_FAIL_STATUS } from '@src/constants/commons'; import { useAppDispatch, useAppSelector } from '@src/hooks'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { Loading } from '@src/components/Loading'; import { store } from '@src/store'; @@ -41,6 +47,8 @@ interface pipelineMetricSelectionProps { onRemovePipeline: (id: number) => void; onUpdatePipeline: (id: number, label: string, value: string | StringConstructor[] | unknown) => void; isDuplicated: boolean; + setLoadingCompletedNumber: React.Dispatch>; + totalPipelineNumber: number; } export const PipelineMetricSelection = ({ @@ -51,24 +59,33 @@ export const PipelineMetricSelection = ({ onUpdatePipeline, isDuplicated, isInfoLoading, + setLoadingCompletedNumber, + totalPipelineNumber, }: pipelineMetricSelectionProps) => { const { id, organization, pipelineName, step } = pipelineSetting; const dispatch = useAppDispatch(); - const { isLoading, errorMessage, getSteps } = useGetMetricsStepsEffect(); - const organizationNameOptions = selectPipelineOrganizations(store.getState()); - const pipelineNameOptions = selectPipelineNames(store.getState(), organization); - const stepsOptions = selectSteps(store.getState(), organization, pipelineName); - const organizationWarningMessage = selectOrganizationWarningMessage(store.getState(), id); - const pipelineNameWarningMessage = selectPipelineNameWarningMessage(store.getState(), id); - const stepWarningMessage = selectStepWarningMessage(store.getState(), id); + const { isLoading, errorMessage, getSteps, stepFailedStatus } = useGetMetricsStepsEffect(); + const storeContext = store.getState(); + const organizationNameOptions = selectPipelineOrganizations(storeContext); + const pipelineNameOptions = selectPipelineNames(storeContext, organization); + const stepsOptions = selectSteps(storeContext, organization, pipelineName); + const organizationWarningMessage = selectOrganizationWarningMessage(storeContext, id); + const pipelineNameWarningMessage = selectPipelineNameWarningMessage(storeContext, id); + const stepWarningMessage = selectStepWarningMessage(storeContext, id); const [isShowNoStepWarning, setIsShowNoStepWarning] = useState(false); - const shouldLoad = useAppSelector(shouldMetricsLoad); + const shouldLoad = useAppSelector(shouldMetricsLoaded); + const pipelineList = useAppSelector(selectPipelineList); const shouldGetPipelineConfig = useAppSelector(selectShouldGetPipelineConfig); + const isLoadingRef = useRef(false); - const validStepValue = useMemo(() => (stepsOptions.includes(step) ? step : ''), [step, stepsOptions]); + const validStepValue = stepsOptions.includes(step) ? step : ''; const handleRemoveClick = () => { + const newCrews = uniqPipelineListCrews(updateResponseCrews(organization, pipelineName, pipelineList)); + dispatch(updatePipelineToolVerifyResponseCrews({ organization, pipelineName })); + dispatch(updatePiplineCrews(newCrews)); onRemovePipeline(id); + setLoadingCompletedNumber((value) => Math.max(value - 1, 0)); }; useEffect(() => { @@ -76,34 +93,66 @@ export const PipelineMetricSelection = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [shouldLoad, pipelineName, isInfoLoading, shouldGetPipelineConfig]); + useEffect(() => { + if (isLoadingRef.current && !isLoading) { + setLoadingCompletedNumber((value) => Math.min(totalPipelineNumber, value + 1)); + } else if (!shouldGetPipelineConfig && !isLoading) { + setLoadingCompletedNumber(totalPipelineNumber); + } + isLoadingRef.current = isLoading; + }, [isLoading, setLoadingCompletedNumber, totalPipelineNumber, shouldGetPipelineConfig]); + const handleGetPipelineData = (_pipelineName: string) => { const { params, buildId, organizationId, pipelineType, token } = selectStepsParams( store.getState(), organization, _pipelineName, ); + setLoadingCompletedNumber((value) => Math.max(value - 1, 0)); getSteps(params, organizationId, buildId, pipelineType, token).then((res) => { - if (res && !res.haveStep) { - isShowRemoveButton && handleRemoveClick(); - } else { - const steps = res?.response ?? []; - const branches = res?.branches ?? []; - const pipelineCrews = res?.pipelineCrews ?? []; + const steps = res?.response ?? []; + const branches = res?.branches ?? []; + const pipelineCrews = res?.pipelineCrews ?? []; + dispatch( + updatePipelineToolVerifyResponseSteps({ + organization, + pipelineName: _pipelineName, + steps, + branches, + pipelineCrews, + }), + ); + res?.haveStep && dispatch(updatePipelineStep({ steps, id, type, branches, pipelineCrews })); + dispatch(updateShouldGetPipelineConfig(false)); + res && setIsShowNoStepWarning(!res.haveStep); + }); + }; + + useEffect(() => { + const popup = () => { + if (stepFailedStatus === METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_4XX) { dispatch( - updatePipelineToolVerifyResponseSteps({ - organization, - pipelineName: _pipelineName, - steps, - branches, - pipelineCrews, + addNotification({ + type: 'warning', + message: MESSAGE.PIPELINE_STEP_REQUEST_PARTIAL_FAILED_4XX, + }), + ); + } else if ( + stepFailedStatus === METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_NO_CARDS || + stepFailedStatus === METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_TIMEOUT + ) { + dispatch( + addNotification({ + type: 'warning', + message: MESSAGE.PIPELINE_STEP_REQUEST_PARTIAL_FAILED_OTHERS, }), ); - res?.haveStep && dispatch(updatePipelineStep({ steps, id, type, branches, pipelineCrews })); - dispatch(updateShouldGetPipelineConfig(false)); } - res && setIsShowNoStepWarning(!res.haveStep); - }); - }; + }; + if (!isLoading) { + popup(); + } + }, [stepFailedStatus, dispatch, isLoading]); return ( @@ -142,7 +191,9 @@ export const PipelineMetricSelection = ({ onUpDatePipeline={(id, label, value) => onUpdatePipeline(id, label, value)} /> )} - {organization && pipelineName && } + {organization && pipelineName && ( + + )} {isShowRemoveButton && ( diff --git a/frontend/src/containers/MetricsStep/DeploymentFrequencySettings/SingleSelection/index.tsx b/frontend/src/containers/MetricsStep/DeploymentFrequencySettings/SingleSelection/index.tsx index 5f2a43e800..70082169d3 100644 --- a/frontend/src/containers/MetricsStep/DeploymentFrequencySettings/SingleSelection/index.tsx +++ b/frontend/src/containers/MetricsStep/DeploymentFrequencySettings/SingleSelection/index.tsx @@ -1,9 +1,11 @@ import { selectDeploymentFrequencySettings } from '@src/context/Metrics/metricsSlice'; import { getEmojiUrls, removeExtraEmojiName } from '@src/constants/emojis/emoji'; +import { initSinglePipelineListBranches } from '@src/context/meta/metaSlice'; import { Autocomplete, Box, ListItemText, TextField } from '@mui/material'; import { getDisabledOptions, sortDisabledOptions } from '@src/utils/util'; import { EmojiWrap, StyledAvatar } from '@src/constants/emojis/style'; import { DEFAULT_HELPER_TEXT, Z_INDEX } from '@src/constants/commons'; +import { useAppDispatch } from '@src/hooks/useAppDispatch'; import { FormControlWrapper } from './style'; import { useAppSelector } from '@src/hooks'; import React, { useState } from 'react'; @@ -16,7 +18,7 @@ interface Props { isError?: boolean; errorText?: string; onGetSteps?: (pipelineName: string) => void; - onUpDatePipeline: (id: number, label: string, value: string) => void; + onUpDatePipeline: (id: number, label: string, value: string | []) => void; } export const SingleSelection = ({ @@ -32,11 +34,14 @@ export const SingleSelection = ({ const labelId = `single-selection-${label.toLowerCase().replace(' ', '-')}`; const [inputValue, setInputValue] = useState(value); const deploymentFrequencySettings = useAppSelector(selectDeploymentFrequencySettings); + const dispatch = useAppDispatch(); const handleSelectedOptionsChange = (value: string) => { if (onGetSteps) { onUpDatePipeline(id, 'Step', ''); + onUpDatePipeline(id, 'Branches', []); onGetSteps(value); + dispatch(initSinglePipelineListBranches(id)); } onUpDatePipeline(id, label, value); }; diff --git a/frontend/src/containers/MetricsStep/DeploymentFrequencySettings/index.tsx b/frontend/src/containers/MetricsStep/DeploymentFrequencySettings/index.tsx index c03ae7f5d5..c8c56b6881 100644 --- a/frontend/src/containers/MetricsStep/DeploymentFrequencySettings/index.tsx +++ b/frontend/src/containers/MetricsStep/DeploymentFrequencySettings/index.tsx @@ -19,20 +19,22 @@ import { useAppDispatch, useAppSelector } from '@src/hooks'; import { Crews } from '@src/containers/MetricsStep/Crews'; import { Loading } from '@src/components/Loading'; import { HttpStatusCode } from 'axios'; -import isEmpty from 'lodash/isEmpty'; +import { useState } from 'react'; export const DeploymentFrequencySettings = () => { const dispatch = useAppDispatch(); - const { isLoading, result: pipelineInfoResult, apiCallFunc } = useGetPipelineToolInfoEffect(); + const { isLoading, result: pipelineInfoResult, apiCallFunc, isFirstFetch } = useGetPipelineToolInfoEffect(); const deploymentFrequencySettings = useAppSelector(selectDeploymentFrequencySettings); + const [loadingCompletedNumber, setLoadingCompletedNumber] = useState(0); const { getDuplicatedPipeLineIds } = useMetricsStepValidationCheckContext(); const pipelineCrews = useAppSelector(selectPipelineCrews); const errorDetail = useAppSelector(getErrorDetail) as number; const handleAddPipeline = () => { dispatch(addADeploymentFrequencySetting()); + setLoadingCompletedNumber((value) => value + 1); }; - + const realDeploymentFrequencySettings = isFirstFetch ? [] : deploymentFrequencySettings; const handleRemovePipeline = (id: number) => { dispatch(deleteADeploymentFrequencySetting(id)); dispatch(deleteMetricsPipelineFormMeta(id)); @@ -42,6 +44,10 @@ export const DeploymentFrequencySettings = () => { dispatch(updateDeploymentFrequencySettings({ updateId: id, label, value })); }; + const totalPipelineNumber = realDeploymentFrequencySettings.length; + const shouldShowCrews = + loadingCompletedNumber !== 0 && totalPipelineNumber !== 0 && loadingCompletedNumber === totalPipelineNumber; + return ( <> {isLoading && } @@ -53,22 +59,24 @@ export const DeploymentFrequencySettings = () => { - {deploymentFrequencySettings.map((deploymentFrequencySetting) => ( + {realDeploymentFrequencySettings.map((deploymentFrequencySetting) => ( 1} + isShowRemoveButton={totalPipelineNumber > 1} onRemovePipeline={(id) => handleRemovePipeline(id)} onUpdatePipeline={(id, label, value) => handleUpdatePipeline(id, label, value)} - isDuplicated={getDuplicatedPipeLineIds(deploymentFrequencySettings).includes( + isDuplicated={getDuplicatedPipeLineIds(realDeploymentFrequencySettings).includes( deploymentFrequencySetting.id, )} + totalPipelineNumber={totalPipelineNumber} + setLoadingCompletedNumber={setLoadingCompletedNumber} /> ))} - {!isEmpty(pipelineCrews) && ( + {shouldShowCrews && ( { const boardConfig = useAppSelector(selectBoard); @@ -63,29 +68,55 @@ const MetricsStep = () => { const isShowRealDone = cycleTimeSettingsType === CYCLE_TIME_SETTINGS_TYPES.BY_COLUMN && cycleTimeSettings.filter((e) => e.value === DONE).length > 1; - const { getBoardInfo, isLoading, errorMessage } = useGetBoardInfoEffect(); - const shouldLoad = useAppSelector(shouldMetricsLoad); + const { getBoardInfo, isLoading, errorMessage, boardInfoFailedStatus } = useGetBoardInfoEffect(); + const shouldLoad = useAppSelector(shouldMetricsLoaded); const shouldGetBoardConfig = useAppSelector(selectShouldGetBoardConfig); const getInfo = useCallback( - () => + async () => { getBoardInfo({ ...boardConfig, - startTime: dayjs(startDate).valueOf().toString(), - endTime: dayjs(endDate).valueOf().toString(), + dateRanges, }).then((res) => { - if (res.data) { - dispatch(updateBoardVerifyState(true)); - dispatch(updateJiraVerifyResponse(res.data)); - dispatch(updateMetricsState(merge(res.data, { isProjectCreated: isProjectCreated }))); + if (res && res.length) { + const commonPayload = combineBoardInfo(res); + dispatch(updateJiraVerifyResponse(commonPayload)); + dispatch(updateMetricsState(merge(commonPayload, { isProjectCreated: isProjectCreated }))); dispatch(updateShouldGetBoardConfig(false)); dispatch(updateFirstTimeRoadMetricsBoardData(false)); } - }), + }); + }, // eslint-disable-next-line react-hooks/exhaustive-deps [], ); + useEffect(() => { + const popup = () => { + if (boardInfoFailedStatus === METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_4XX) { + dispatch( + addNotification({ + type: 'warning', + message: MESSAGE.BOARD_INFO_REQUEST_PARTIAL_FAILED_4XX, + }), + ); + } else if ( + boardInfoFailedStatus === METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_NO_CARDS || + boardInfoFailedStatus === METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_TIMEOUT + ) { + dispatch( + addNotification({ + type: 'warning', + message: MESSAGE.BOARD_INFO_REQUEST_PARTIAL_FAILED_OTHERS, + }), + ); + } + }; + if (!isLoading) { + popup(); + } + }, [boardInfoFailedStatus, dispatch, isLoading]); + useLayoutEffect(() => { if (!shouldLoad) return; dispatch(closeAllNotifications()); @@ -97,19 +128,18 @@ const MetricsStep = () => { <> {startDate && endDate && ( - + )} - {isShowCrewsAndRealDone && ( {isLoading && } - Board configuration - {isEmpty(errorMessage) ? ( + Board configuration + + {isEmpty(errorMessage) || + (boardInfoFailedStatus !== METRICS_DATA_FAIL_STATUS.ALL_FAILED_4XX && + boardInfoFailedStatus !== METRICS_DATA_FAIL_STATUS.ALL_FAILED_TIMEOUT && + boardInfoFailedStatus !== METRICS_DATA_FAIL_STATUS.ALL_FAILED_NO_CARDS) ? ( <> diff --git a/frontend/src/containers/MetricsStepper/index.tsx b/frontend/src/containers/MetricsStepper/index.tsx index 9ba7979379..6cfd93f7c0 100644 --- a/frontend/src/containers/MetricsStepper/index.tsx +++ b/frontend/src/containers/MetricsStepper/index.tsx @@ -1,3 +1,13 @@ +import { + basicInfoSchema, + boardConfigSchema, + pipelineToolSchema, + sourceControlSchema, + IBasicInfoData, + IBoardConfigData, + IPipelineToolData, + ISourceControlData, +} from '@src/containers/ConfigStep/Form/schema'; import { BackButton, ButtonContainer, @@ -18,14 +28,17 @@ import { backStep, nextStep, selectStepNumber, updateTimeStamp } from '@src/cont import { useMetricsStepValidationCheckContext } from '@src/hooks/useMetricsStepValidationCheckContext'; import { convertCycleTimeSettings, exportToJsonFile, onlyEmptyAndDoneState } from '@src/utils/util'; import { selectConfig, selectMetrics, selectPipelineList } from '@src/context/config/configSlice'; +import { useDefaultValues } from '@src/containers/ConfigStep/Form/useDefaultValues'; import { COMMON_BUTTONS, METRICS_STEPS, STEPS } from '@src/constants/commons'; import { ConfirmDialog } from '@src/containers/MetricsStepper/ConfirmDialog'; import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch'; import { lazy, Suspense, useEffect, useMemo, useState } from 'react'; import { getFormMeta } from '@src/context/meta/metaSlice'; import SaveAltIcon from '@mui/icons-material/SaveAlt'; +import { yupResolver } from '@hookform/resolvers/yup'; import { useNavigate } from 'react-router-dom'; import { ROUTE } from '@src/constants/router'; +import { useForm } from 'react-hook-form'; import { Tooltip } from '@mui/material'; import isEmpty from 'lodash/isEmpty'; import every from 'lodash/every'; @@ -49,10 +62,73 @@ const MetricsStepper = () => { const { getDuplicatedPipeLineIds } = useMetricsStepValidationCheckContext(); const formMeta = useAppSelector(getFormMeta); const pipelineList = useAppSelector(selectPipelineList); + const defaultValues = useDefaultValues(); + const { isShow: isShowBoard } = config.board; + const { isShow: isShowPipeline } = config.pipelineTool; + const { isShow: isShowSourceControl } = config.sourceControl; + + const basicInfoMethods = useForm({ + defaultValues: defaultValues.basicInfoWithImport, + resolver: yupResolver(basicInfoSchema), + mode: 'onChange', + }); + + const boardConfigMethods = useForm({ + defaultValues: defaultValues.boardConfigWithImport, + resolver: yupResolver(boardConfigSchema), + mode: 'onChange', + }); + + const pipelineToolMethods = useForm({ + defaultValues: defaultValues.pipelineToolWithImport, + resolver: yupResolver(pipelineToolSchema), + mode: 'onChange', + }); + + const sourceControlMethods = useForm({ + defaultValues: defaultValues.sourceControlWithImport, + resolver: yupResolver(sourceControlSchema), + mode: 'onChange', + }); + + const { isValid: isBasicInfoValid } = basicInfoMethods.formState; + const { isValid: isBoardConfigValid, isSubmitSuccessful: isBoardConfigSubmitSuccessful } = + boardConfigMethods.formState; + const { isValid: isPipelineToolValid, isSubmitSuccessful: isPipelineToolSubmitSuccessful } = + pipelineToolMethods.formState; + const { isValid: isSourceControlValid, isSubmitSuccessful: isSourceControlSubmitSuccessful } = + sourceControlMethods.formState; + + const configPageFormMeta = useMemo( + () => [ + { isShow: isShowBoard, isValid: isBoardConfigValid, isSubmitSuccessful: isBoardConfigSubmitSuccessful }, + { isShow: isShowPipeline, isValid: isPipelineToolValid, isSubmitSuccessful: isPipelineToolSubmitSuccessful }, + { + isShow: isShowSourceControl, + isValid: isSourceControlValid, + isSubmitSuccessful: isSourceControlSubmitSuccessful, + }, + ], + [ + isShowBoard, + isBoardConfigValid, + isBoardConfigSubmitSuccessful, + isShowPipeline, + isPipelineToolValid, + isPipelineToolSubmitSuccessful, + isShowSourceControl, + isSourceControlValid, + isSourceControlSubmitSuccessful, + ], + ); + const activeFormMeta = useMemo(() => configPageFormMeta.filter(({ isShow }) => isShow), [configPageFormMeta]); + const shownFormsVerified = useMemo( + () => + activeFormMeta.length > 0 && + activeFormMeta.every(({ isValid, isSubmitSuccessful }) => isValid && isSubmitSuccessful), + [activeFormMeta], + ); - const { isShow: isShowBoard, isVerified: isBoardVerified } = config.board; - const { isShow: isShowPipeline, isVerified: isPipelineToolVerified } = config.pipelineTool; - const { isShow: isShowSourceControl, isVerified: isSourceControlVerified } = config.sourceControl; const isShowCycleTimeSettings = requiredData.includes(REQUIRED_DATA.CYCLE_TIME) || requiredData.includes(REQUIRED_DATA.CLASSIFICATION) || @@ -100,18 +176,6 @@ const MetricsStepper = () => { }, [pipelineList, formMeta.metrics.pipelines, getDuplicatedPipeLineIds, metricsConfig.deploymentFrequencySettings]); useEffect(() => { - if (activeStep === METRICS_STEPS.CONFIG) { - const nextButtonValidityOptions = [ - { isShow: isShowBoard, isValid: isBoardVerified }, - { isShow: isShowPipeline, isValid: isPipelineToolVerified }, - { isShow: isShowSourceControl, isValid: isSourceControlVerified }, - ]; - const activeNextButtonValidityOptions = nextButtonValidityOptions.filter(({ isShow }) => isShow); - projectName && dateRange && dateRange.length && metrics.length - ? setIsDisableNextButton(!activeNextButtonValidityOptions.every(({ isValid }) => isValid)) - : setIsDisableNextButton(true); - } - if (activeStep === METRICS_STEPS.METRICS) { const nextButtonValidityOptions = [ { isShow: isShowBoard, isValid: isCrewsSettingValid }, @@ -131,12 +195,9 @@ const MetricsStepper = () => { } }, [ activeStep, - isBoardVerified, - isPipelineToolVerified, isShowBoard, isShowSourceControl, isShowPipeline, - isSourceControlVerified, metrics, projectName, dateRange, @@ -156,6 +217,9 @@ const MetricsStepper = () => { onlyIncludeReworkMetrics, ]); + const isNextDisabledTempForFormRefactor = + activeStep === METRICS_STEPS.CONFIG ? !(isBasicInfoValid && shownFormsVerified) : isDisableNextButton; + const filterMetricsConfig = (metricsConfig: ISavedMetricsSettingState) => { return Object.fromEntries( Object.entries(metricsConfig).filter(([, value]) => { @@ -175,12 +239,13 @@ const MetricsStepper = () => { /* istanbul ignore next */ const handleSave = () => { - const { projectName, dateRange, calendarType, metrics } = config.basic; + const { projectName, dateRange, calendarType, metrics, sortType } = config.basic; const configData = { projectName, dateRange, calendarType, metrics, + sortType, board: isShowBoard ? omit(config.board.config, ['projectKey']) : undefined, /* istanbul ignore next */ @@ -260,7 +325,14 @@ const MetricsStepper = () => { - {activeStep === METRICS_STEPS.CONFIG && } + {activeStep === METRICS_STEPS.CONFIG && ( + + )} {activeStep === METRICS_STEPS.METRICS && } {activeStep === METRICS_STEPS.REPORT && } @@ -277,7 +349,7 @@ const MetricsStepper = () => { {COMMON_BUTTONS.BACK} - + {COMMON_BUTTONS.NEXT} diff --git a/frontend/src/containers/ReportButtonGroup/index.tsx b/frontend/src/containers/ReportButtonGroup/index.tsx index b635b18dbc..ade827ff51 100644 --- a/frontend/src/containers/ReportButtonGroup/index.tsx +++ b/frontend/src/containers/ReportButtonGroup/index.tsx @@ -1,42 +1,81 @@ import { StyledButtonGroup, StyledExportButton, StyledRightButtonGroup } from '@src/containers/ReportButtonGroup/style'; +import { COMMON_BUTTONS, DOWNLOAD_DIALOG_TITLE, REPORT_TYPES } from '@src/constants/commons'; +import { DateRangeItem, DownloadDialog } from '@src/containers/ReportStep/DownloadDialog'; import { BackButton, SaveButton } from '@src/containers/MetricsStepper/style'; import { ExpiredDialog } from '@src/containers/ReportStep/ExpiredDialog'; import { CSVReportRequestDTO } from '@src/clients/report/dto/request'; -import { COMMON_BUTTONS, REPORT_TYPES } from '@src/constants/commons'; -import { ReportResponseDTO } from '@src/clients/report/dto/response'; +import { AllErrorResponse } from '@src/clients/report/dto/response'; +import { DateRangeRequestResult } from '@src/containers/ReportStep'; import { useExportCsvEffect } from '@src/hooks/useExportCsvEffect'; import SaveAltIcon from '@mui/icons-material/SaveAlt'; import { TIPS } from '@src/constants/resources'; import { Tooltip } from '@mui/material'; -import React from 'react'; +import React, { useState } from 'react'; interface ReportButtonGroupProps { handleSave?: () => void; handleBack: () => void; csvTimeStamp: number; - startDate: string; - endDate: string; - reportData: ReportResponseDTO | undefined; isShowSave: boolean; isShowExportBoardButton: boolean; isShowExportPipelineButton: boolean; isShowExportMetrics: boolean; + dateRangeRequestResults: DateRangeRequestResult[]; } export const ReportButtonGroup = ({ handleSave, handleBack, csvTimeStamp, - startDate, - endDate, - reportData, isShowSave, isShowExportMetrics, isShowExportBoardButton, isShowExportPipelineButton, + dateRangeRequestResults, }: ReportButtonGroupProps) => { + const [isShowDialog, setIsShowDialog] = useState(false); + const [downloadReportList, setDownloadReportList] = useState([]); + const [dataType, setDataType] = useState(null); const { fetchExportData, isExpired } = useExportCsvEffect(); + const isReportHasError = (reportMetricsError: AllErrorResponse) => { + return ( + !!reportMetricsError.boardMetricsError || + !!reportMetricsError.pipelineMetricsError || + !!reportMetricsError.sourceControlMetricsError + ); + }; + + const isReportHasDoraError = (reportMetricsError: AllErrorResponse) => { + return !!reportMetricsError.pipelineMetricsError || !!reportMetricsError.sourceControlMetricsError; + }; + + const overallMetricsResults = dateRangeRequestResults.map((item) => ({ + startDate: item.startDate, + endDate: item.endDate, + disabled: !(item.overallMetricsCompleted && !isReportHasError(item.reportMetricsError)), + })); + const boardMetricsResults = dateRangeRequestResults.map((item) => ({ + startDate: item.startDate, + endDate: item.endDate, + disabled: !(item.boardMetricsCompleted && !item.reportMetricsError.boardMetricsError), + })); + const pipelineMetricsResults = dateRangeRequestResults.map((item) => ({ + startDate: item.startDate, + endDate: item.endDate, + disabled: !(item.doraMetricsCompleted && !isReportHasDoraError(item.reportMetricsError)), + })); + + const isExportMetricsButtonClickable = + dateRangeRequestResults.every(({ overallMetricsCompleted }) => overallMetricsCompleted) && + overallMetricsResults.some(({ disabled }) => !disabled); + const isExportBoardButtonClickable = + dateRangeRequestResults.every(({ boardMetricsCompleted }) => boardMetricsCompleted) && + boardMetricsResults.some(({ disabled }) => !disabled); + const isExportPipelineButtonClickable = + dateRangeRequestResults.every(({ doraMetricsCompleted }) => doraMetricsCompleted) && + pipelineMetricsResults.some(({ disabled }) => !disabled); + const exportCSV = (dataType: REPORT_TYPES, startDate: string, endDate: string): CSVReportRequestDTO => ({ dataType: dataType, csvTimeStamp: csvTimeStamp, @@ -44,23 +83,32 @@ export const ReportButtonGroup = ({ endDate: endDate, }); - const handleDownload = (dataType: REPORT_TYPES, startDate: string, endDate: string) => { - fetchExportData(exportCSV(dataType, startDate, endDate)); + const handleDownload = (dateRange: DateRangeItem[], dataType: REPORT_TYPES) => { + if (dateRange.length > 1) { + setDownloadReportList(dateRange); + setDataType(dataType); + setIsShowDialog(true); + } else { + fetchExportData(exportCSV(dataType, dateRange[0].startDate, dateRange[0].endDate)); + } }; - const pipelineButtonDisabled = - !reportData || - reportData.doraMetricsCompleted === false || - reportData?.reportMetricsError?.pipelineMetricsError || - reportData?.reportMetricsError?.sourceControlMetricsError; - - const isReportHasError = - !!reportData?.reportMetricsError.boardMetricsError || - !!reportData?.reportMetricsError.pipelineMetricsError || - !!reportData?.reportMetricsError.sourceControlMetricsError; + const handleCloseDialog = () => { + setIsShowDialog(false); + setDataType(null); + }; return ( <> + {dataType && ( + fetchExportData(exportCSV(dataType, startDate, endDate))} + title={DOWNLOAD_DIALOG_TITLE[dataType]} + /> + )} {isShowSave && ( @@ -75,24 +123,24 @@ export const ReportButtonGroup = ({ {isShowExportMetrics && ( handleDownload(REPORT_TYPES.METRICS, startDate, endDate)} + disabled={!isExportMetricsButtonClickable} + onClick={() => handleDownload(overallMetricsResults, REPORT_TYPES.METRICS)} > {COMMON_BUTTONS.EXPORT_METRIC_DATA} )} {isShowExportBoardButton && ( handleDownload(REPORT_TYPES.BOARD, startDate, endDate)} + disabled={!isExportBoardButtonClickable} + onClick={() => handleDownload(boardMetricsResults, REPORT_TYPES.BOARD)} > {COMMON_BUTTONS.EXPORT_BOARD_DATA} )} {isShowExportPipelineButton && ( handleDownload(REPORT_TYPES.PIPELINE, startDate, endDate)} + disabled={!isExportPipelineButtonClickable} + onClick={() => handleDownload(pipelineMetricsResults, REPORT_TYPES.PIPELINE)} > {COMMON_BUTTONS.EXPORT_PIPELINE_DATA} diff --git a/frontend/src/containers/ReportStep/BoardMetrics/BoardMetrics.tsx b/frontend/src/containers/ReportStep/BoardMetrics/BoardMetrics.tsx deleted file mode 100644 index dcdafd59a7..0000000000 --- a/frontend/src/containers/ReportStep/BoardMetrics/BoardMetrics.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { styled } from '@mui/material/styles'; -import { theme } from '@src/theme'; - -export const StyledMetricsSection = styled('div')({ - marginTop: '2rem', -}); - -export const StyledTitleWrapper = styled('div')({ - display: 'flex', - alignItems: 'center', - marginBottom: '1rem', - position: 'relative', -}); - -export const StyledShowMore = styled('div')({ - marginLeft: '0.5rem', - fontSize: '0.8rem', - textDecoration: 'none', - color: theme.main.alert.info.iconColor, - cursor: 'pointer', -}); - -export const StyledLoading = styled('div')({ - position: 'relative', - left: '1rem', -}); - -export const StyledRetry = styled('div')({ - marginLeft: '0.5rem', - fontSize: '0.8rem', - textDecoration: 'none', - color: theme.main.alert.info.iconColor, - cursor: 'pointer', -}); diff --git a/frontend/src/containers/ReportStep/BoardMetrics/index.tsx b/frontend/src/containers/ReportStep/BoardMetrics/index.tsx index 3836b326cd..d72d48927b 100644 --- a/frontend/src/containers/ReportStep/BoardMetrics/index.tsx +++ b/frontend/src/containers/ReportStep/BoardMetrics/index.tsx @@ -1,3 +1,11 @@ +import { + GridContainer, + StyledLoading, + StyledMetricsSection, + StyledRetry, + StyledShowMore, + StyledTitleWrapper, +} from '@src/containers/ReportStep/BoardMetrics/style'; import { BOARD_METRICS, BOARD_METRICS_MAPPING, @@ -8,14 +16,6 @@ import { RETRY, SHOW_MORE, } from '@src/constants/resources'; -import { - StyledLoading, - StyledMetricsSection, - StyledRetry, - StyledShowMore, - StyledTitleWrapper, -} from '@src/containers/ReportStep/BoardMetrics/BoardMetrics'; -import { GridContainer } from '@src/containers/ReportStep/BoardMetrics/style'; import { ReportTitle } from '@src/components/Common/ReportGrid/ReportTitle'; import { selectMetricsContent } from '@src/context/Metrics/metricsSlice'; import { ReportResponseDTO } from '@src/clients/report/dto/response'; diff --git a/frontend/src/containers/ReportStep/BoardMetrics/style.tsx b/frontend/src/containers/ReportStep/BoardMetrics/style.tsx index a76fbca15c..adb5abd181 100644 --- a/frontend/src/containers/ReportStep/BoardMetrics/style.tsx +++ b/frontend/src/containers/ReportStep/BoardMetrics/style.tsx @@ -1,7 +1,37 @@ import { styled } from '@mui/material/styles'; +import { theme } from '@src/theme'; export const GridContainer = styled('div')({ display: 'flex', flexDirection: 'column', gap: '1rem', }); +export const StyledMetricsSection = styled('div')({ + marginTop: '2rem', +}); +export const StyledTitleWrapper = styled('div')({ + display: 'flex', + alignItems: 'center', + marginBottom: '1rem', + position: 'relative', +}); +export const StyledShowMore = styled('div')({ + marginLeft: '0.5rem', + fontSize: '0.8rem', + textDecoration: 'none', + color: theme.main.alert.info.iconColor, + cursor: 'pointer', +}); +export const StyledLoading = styled('div')({ + position: 'relative', + left: '1rem', +}); +export const StyledRetry = styled('div')({ + // todo: update retry logic + display: 'none', + marginLeft: '0.5rem', + fontSize: '0.8rem', + textDecoration: 'none', + color: theme.main.alert.info.iconColor, + cursor: 'pointer', +}); diff --git a/frontend/src/containers/ReportStep/DoraMetrics/index.tsx b/frontend/src/containers/ReportStep/DoraMetrics/index.tsx index 1879bb6b41..116ef2d3a4 100644 --- a/frontend/src/containers/ReportStep/DoraMetrics/index.tsx +++ b/frontend/src/containers/ReportStep/DoraMetrics/index.tsx @@ -12,11 +12,11 @@ import { import { StyledMetricsSection, StyledShowMore, StyledTitleWrapper } from '@src/containers/ReportStep/DoraMetrics/style'; import { formatMillisecondsToHours, formatMinToHours } from '@src/utils/util'; import { ReportTitle } from '@src/components/Common/ReportGrid/ReportTitle'; +import { StyledRetry } from '@src/containers/ReportStep/BoardMetrics/style'; import { ReportResponseDTO } from '@src/clients/report/dto/response'; import { StyledSpacing } from '@src/containers/ReportStep/style'; import { ReportGrid } from '@src/components/Common/ReportGrid'; import { selectConfig } from '@src/context/config/configSlice'; -import { StyledRetry } from '../BoardMetrics/BoardMetrics'; import { useAppSelector } from '@src/hooks'; import React from 'react'; import _ from 'lodash'; diff --git a/frontend/src/containers/ReportStep/DownloadDialog/index.tsx b/frontend/src/containers/ReportStep/DownloadDialog/index.tsx new file mode 100644 index 0000000000..3d4887cd1c --- /dev/null +++ b/frontend/src/containers/ReportStep/DownloadDialog/index.tsx @@ -0,0 +1,108 @@ +import { + CloseButton, + DialogContainer, + StyledButton, + StyledCalendarToday, + StyledDialog, + StyledDialogContent, + StyledDialogTitle, + StyledFormControlLabel, + StyledFormGroup, + TimePeriodSelectionMessage, + tooltipModifiers, +} from '@src/containers/ReportStep/DownloadDialog/style'; +import { DISABLED_DATE_RANGE_MESSAGE } from '@src/constants/resources'; +import { COMMON_BUTTONS } from '@src/constants/commons'; +import { Checkbox, Tooltip } from '@mui/material'; +import { formatDate } from '@src/utils/util'; +import React, { useState } from 'react'; + +interface DownloadDialogProps { + isShowDialog: boolean; + handleClose: () => void; + dateRangeList: DateRangeItem[]; + downloadCSVFile: (startDate: string, endDate: string) => void; + title: string; +} + +export interface DateRangeItem { + startDate: string; + endDate: string; + disabled: boolean; +} + +export const DownloadDialog = ({ + isShowDialog, + handleClose, + dateRangeList, + downloadCSVFile, + title, +}: DownloadDialogProps) => { + const [selectedRangeItems, setSelectedRangeItems] = useState([]); + const confirmButtonDisabled = selectedRangeItems.length === 0; + + const handleChange = (targetItem: DateRangeItem) => { + if (selectedRangeItems.includes(targetItem)) { + setSelectedRangeItems(selectedRangeItems.filter((item) => targetItem !== item)); + } else { + setSelectedRangeItems([...selectedRangeItems, targetItem]); + } + }; + + const handleDownload = () => { + selectedRangeItems.forEach((item) => { + downloadCSVFile(item.startDate, item.endDate); + }); + handleClose(); + }; + + const getLabel = (item: DateRangeItem) => { + if (item.disabled) { + return ( + + {`${formatDate(item.startDate)} - ${formatDate(item.endDate)}`} + + ); + } else { + return `${formatDate(item.startDate)} - ${formatDate(item.endDate)}`; + } + }; + + return ( + + + + Export {title} Data + + + + + + Select the time period + + + {dateRangeList.map((item) => ( + handleChange(item)} />} + label={getLabel(item)} + checked={selectedRangeItems.includes(item)} + disabled={item.disabled} + /> + ))} + + + {COMMON_BUTTONS.CONFIRM} + + + + + ); +}; diff --git a/frontend/src/containers/ReportStep/DownloadDialog/style.tsx b/frontend/src/containers/ReportStep/DownloadDialog/style.tsx new file mode 100644 index 0000000000..940111244b --- /dev/null +++ b/frontend/src/containers/ReportStep/DownloadDialog/style.tsx @@ -0,0 +1,91 @@ +import { Button, Dialog, DialogContent, DialogTitle, FormControlLabel, FormGroup } from '@mui/material'; +import { CalendarToday } from '@mui/icons-material'; +import CloseIcon from '@mui/icons-material/Close'; +import { styled } from '@mui/material/styles'; +import { theme } from '@src/theme'; + +export const StyledDialog = styled(Dialog)({ + '& .MuiDialog-paper': { + borderRadius: '1rem', + }, +}); + +export const DialogContainer = styled('div')({ + width: '24rem', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'flex-start', +}); + +export const StyledDialogTitle = styled(DialogTitle)({ + boxSizing: 'border-box', + width: '100%', + padding: '2.5rem 1.375rem 1.5rem 1.375rem', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + fontSize: '1rem', +}); + +export const StyledDialogContent = styled(DialogContent)({ + boxSizing: 'border-box', + width: '100%', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + padding: '1.25rem 1.375rem 1.875rem 1.375rem', +}); + +export const StyledCalendarToday = styled(CalendarToday)({ + color: theme.palette.text.primary, + marginRight: '0.75rem', + fontSize: '1.5rem', +}); + +export const TimePeriodSelectionMessage = styled('div')({ + width: '100%', + display: 'flex', + justifyContent: 'flex-start', + alignItems: 'center', + fontSize: '0.875rem', +}); + +export const StyledFormGroup = styled(FormGroup)({ + margin: '2.25rem 0', +}); + +export const StyledButton = styled(Button)({ + alignSelf: 'flex-end', +}); + +export const tooltipModifiers = { + modifiers: [ + { + name: 'offset', + options: { + offset: [190, 0], + }, + }, + ], +}; + +export const StyledFormControlLabel = styled(FormControlLabel)(({ checked }) => ({ + width: '21rem', + height: '2rem', + borderRadius: '0.75rem', + border: `0.0625rem solid ${theme.main.boardColor}`, + margin: '0.375rem 0', + '& .MuiTypography-root': { + fontSize: '0.875rem', + }, + + ...(checked && { + background: theme.main.downloadListLabel.backgroundColor, + }), +})); + +export const CloseButton = styled(CloseIcon)({ + cursor: 'pointer', +}); diff --git a/frontend/src/containers/ReportStep/ReportDetail/dora.tsx b/frontend/src/containers/ReportStep/ReportDetail/dora.tsx index 1ca30a04ce..cc4d0519bf 100644 --- a/frontend/src/containers/ReportStep/ReportDetail/dora.tsx +++ b/frontend/src/containers/ReportStep/ReportDetail/dora.tsx @@ -1,6 +1,7 @@ -import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { ReportDataWithThreeColumns, ReportDataWithTwoColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { METRICS_TITLE, PIPELINE_STEP, SUBTITLE } from '@src/constants/resources'; import ReportForThreeColumns from '@src/components/Common/ReportForThreeColumns'; -import { METRICS_TITLE, NAME, PIPELINE_STEP } from '@src/constants/resources'; +import ReportForTwoColumns from '@src/components/Common/ReportForTwoColumns'; import { ReportResponseDTO } from '@src/clients/report/dto/response'; import { reportMapper } from '@src/hooks/reportMapper/report'; import { Optional } from '@src/utils/types'; @@ -11,18 +12,22 @@ interface Property { data: ReportResponseDTO; onBack: () => void; } -const showSection = (title: string, value: Optional) => - value && ; + +const showTwoColumnSection = (title: string, value: Optional) => + value && ; + +const showThreeColumnSection = (title: string, value: Optional) => + value && ; export const DoraDetail = withGoBack(({ data }: Property) => { const mappedData = reportMapper(data); return ( <> - {showSection(METRICS_TITLE.DEPLOYMENT_FREQUENCY, mappedData.deploymentFrequencyList)} - {showSection(METRICS_TITLE.LEAD_TIME_FOR_CHANGES, mappedData.leadTimeForChangesList)} - {showSection(METRICS_TITLE.DEV_CHANGE_FAILURE_RATE, mappedData.devChangeFailureRateList)} - {showSection(METRICS_TITLE.DEV_MEAN_TIME_TO_RECOVERY, mappedData.devMeanTimeToRecoveryList)} + {showTwoColumnSection(METRICS_TITLE.DEPLOYMENT_FREQUENCY, mappedData.deploymentFrequencyList)} + {showThreeColumnSection(METRICS_TITLE.LEAD_TIME_FOR_CHANGES, mappedData.leadTimeForChangesList)} + {showTwoColumnSection(METRICS_TITLE.DEV_CHANGE_FAILURE_RATE, mappedData.devChangeFailureRateList)} + {showTwoColumnSection(METRICS_TITLE.DEV_MEAN_TIME_TO_RECOVERY, mappedData.devMeanTimeToRecoveryList)} ); }); diff --git a/frontend/src/containers/ReportStep/index.tsx b/frontend/src/containers/ReportStep/index.tsx index 2b98f4c5e6..3aad0b6a4e 100644 --- a/frontend/src/containers/ReportStep/index.tsx +++ b/frontend/src/containers/ReportStep/index.tsx @@ -1,6 +1,5 @@ import { filterAndMapCycleTimeSettings, - formatDateToTimestampString, formatDuplicatedNameWithSuffix, getJiraBoardToken, getRealDoneStatus, @@ -8,11 +7,27 @@ import { sortDateRanges, } from '@src/utils/util'; import { + DateRange, + DateRangeList, isOnlySelectClassification, isSelectBoardMetrics, isSelectDoraMetrics, selectConfig, } from '@src/context/config/configSlice'; +import { + GeneralErrorKey, + initReportInfo, + IReportError, + IReportInfo, + TimeoutErrorKey, + useGenerateReportEffect, +} from '@src/hooks/useGenerateReportEffect'; +import { + addNotification, + closeAllNotifications, + closeNotification, + Notification, +} from '@src/context/notification/NotificationSlice'; import { BOARD_METRICS, CALENDAR, @@ -21,46 +36,64 @@ import { REPORT_PAGE_TYPE, REQUIRED_DATA, } from '@src/constants/resources'; -import { addNotification, closeAllNotifications, Notification } from '@src/context/notification/NotificationSlice'; import { IPipelineConfig, selectMetricsContent } from '@src/context/Metrics/metricsSlice'; +import { AllErrorResponse, ReportResponseDTO } from '@src/clients/report/dto/response'; import { backStep, selectTimeStamp } from '@src/context/stepper/StepperSlice'; -import { useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect'; -import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react'; import { StyledCalendarWrapper } from '@src/containers/ReportStep/style'; import { ReportButtonGroup } from '@src/containers/ReportButtonGroup'; import DateRangeViewer from '@src/components/Common/DateRangeViewer'; -import { ReportResponseDTO } from '@src/clients/report/dto/response'; import BoardMetrics from '@src/containers/ReportStep/BoardMetrics'; import DoraMetrics from '@src/containers/ReportStep/DoraMetrics'; +import React, { useEffect, useMemo, useState } from 'react'; import { useAppDispatch } from '@src/hooks/useAppDispatch'; import { BoardDetail, DoraDetail } from './ReportDetail'; import { METRIC_TYPES } from '@src/constants/commons'; import { useAppSelector } from '@src/hooks'; +import { uniqueId } from 'lodash'; export interface ReportStepProps { handleSave: () => void; } +const timeoutNotificationMessages = { + [TimeoutErrorKey[METRIC_TYPES.BOARD]]: 'Board metrics', + [TimeoutErrorKey[METRIC_TYPES.DORA]]: 'DORA metrics', + [TimeoutErrorKey[METRIC_TYPES.ALL]]: 'Report', +}; + +export interface DateRangeRequestResult { + startDate: string; + endDate: string; + overallMetricsCompleted: boolean | null; + boardMetricsCompleted: boolean | null; + doraMetricsCompleted: boolean | null; + reportMetricsError: AllErrorResponse; +} + const ReportStep = ({ handleSave }: ReportStepProps) => { const dispatch = useAppDispatch(); + const configData = useAppSelector(selectConfig); + const descendingDateRanges = sortDateRanges(configData.basic.dateRange); + const [selectedDateRange, setSelectedDateRange] = useState(descendingDateRanges[0]); + const [currentDataInfo, setCurrentDataInfo] = useState(initReportInfo()); + const { startToRequestData, - reportData, + reportInfos, stopPollingReports, - timeout4Board, - timeout4Dora, - timeout4Report, - generalError4Board, - generalError4Dora, - generalError4Report, + closeReportInfosErrorStatus, + closeBoardMetricsError, + closePipelineMetricsError, + closeSourceControlMetricsError, + hasPollingStarted, } = useGenerateReportEffect(); const [exportValidityTimeMin, setExportValidityTimeMin] = useState(undefined); const [pageType, setPageType] = useState(REPORT_PAGE_TYPE.SUMMARY); const [isCsvFileGeneratedAtEnd, setIsCsvFileGeneratedAtEnd] = useState(false); const [notifications4SummaryPage, setNotifications4SummaryPage] = useState[]>([]); + const [errorNotificationIds, setErrorNotificationIds] = useState([]); - const configData = useAppSelector(selectConfig); const csvTimeStamp = useAppSelector(selectTimeStamp); const { cycleTimeSettingsType, @@ -76,9 +109,8 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { leadTimeForChanges, } = useAppSelector(selectMetricsContent); - const descendingDateRanges = sortDateRanges(configData.basic.dateRange); - const startDate = configData.basic.dateRange[0]?.startDate ?? ''; - const endDate = configData.basic.dateRange[0]?.endDate ?? ''; + const startDate = selectedDateRange?.startDate as string; + const endDate = selectedDateRange?.endDate as string; const { metrics, calendarType } = configData.basic; const boardingMappingStates = [...new Set(cycleTimeSettings.map((item) => item.value))]; const isOnlyEmptyAndDoneState = onlyEmptyAndDoneState(boardingMappingStates); @@ -88,11 +120,33 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { const onlySelectClassification = useAppSelector(isOnlySelectClassification); const isSummaryPage = useMemo(() => pageType === REPORT_PAGE_TYPE.SUMMARY, [pageType]); + const mapDateResult = (descendingDateRanges: DateRangeList, reportInfos: IReportInfo[]) => + descendingDateRanges.map(({ startDate, endDate }) => { + const reportData = reportInfos.find((singleResult) => singleResult.id === startDate)?.reportData ?? null; + return { + startDate: startDate, + endDate: endDate, + overallMetricsCompleted: reportData?.overallMetricsCompleted ?? null, + boardMetricsCompleted: reportData?.boardMetricsCompleted ?? null, + doraMetricsCompleted: reportData?.doraMetricsCompleted ?? null, + reportMetricsError: reportData?.reportMetricsError ?? { + boardMetricsError: null, + pipelineMetricsError: null, + sourceControlMetricsError: null, + }, + } as DateRangeRequestResult; + }); + const getErrorMessage4Board = () => { - if (reportData?.reportMetricsError.boardMetricsError) { - return `Failed to get Jira info, status: ${reportData.reportMetricsError.boardMetricsError.status}`; + if (currentDataInfo.reportData?.reportMetricsError.boardMetricsError) { + return `Failed to get Jira info, status: ${currentDataInfo.reportData.reportMetricsError.boardMetricsError.status}`; } - return timeout4Board || timeout4Report || generalError4Board || generalError4Report; + return ( + currentDataInfo.timeout4Board.message || + currentDataInfo.timeout4Report.message || + currentDataInfo.generalError4Board.message || + currentDataInfo.generalError4Report.message + ); }; const getJiraBoardSetting = () => { @@ -172,8 +226,8 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }); const basicReportRequestBody = { - startTime: formatDateToTimestampString(startDate), - endTime: formatDateToTimestampString(endDate), + startTime: null, + endTime: null, considerHoliday: calendarType === CALENDAR.CHINA, csvTimeStamp, metrics, @@ -201,14 +255,19 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }; useEffect(() => { - setPageType(onlySelectClassification ? REPORT_PAGE_TYPE.BOARD : REPORT_PAGE_TYPE.SUMMARY); - return () => { - stopPollingReports(); - }; + setCurrentDataInfo(reportInfos.find((singleResult) => singleResult.id === selectedDateRange.startDate)!); // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [reportInfos, selectedDateRange]); + + useEffect(() => { + errorNotificationIds.forEach((notificationId) => { + dispatch(closeNotification(notificationId)); + }); + setErrorNotificationIds([]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedDateRange]); - useLayoutEffect(() => { + useEffect(() => { exportValidityTimeMin && isCsvFileGeneratedAtEnd && dispatch( @@ -218,7 +277,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { ); }, [dispatch, exportValidityTimeMin, isCsvFileGeneratedAtEnd]); - useLayoutEffect(() => { + useEffect(() => { if (exportValidityTimeMin && isCsvFileGeneratedAtEnd) { const startTime = Date.now(); const timer = setInterval(() => { @@ -243,14 +302,19 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { } }, [dispatch, exportValidityTimeMin, isCsvFileGeneratedAtEnd]); - useLayoutEffect(() => { + useEffect(() => { dispatch(closeAllNotifications()); }, [dispatch, pageType]); useEffect(() => { - setExportValidityTimeMin(reportData?.exportValidityTime); - reportData && setIsCsvFileGeneratedAtEnd(reportData.allMetricsCompleted && reportData.isSuccessfulCreateCsvFile); - }, [dispatch, reportData]); + if (hasPollingStarted) return; + const successfulReportInfos = reportInfos.filter((reportInfo) => reportInfo.reportData); + if (successfulReportInfos.length === 0) return; + setExportValidityTimeMin(successfulReportInfos[0].reportData?.exportValidityTime); + setIsCsvFileGeneratedAtEnd( + successfulReportInfos.some((reportInfo) => reportInfo.reportData?.isSuccessfulCreateCsvFile), + ); + }, [dispatch, reportInfos, hasPollingStarted]); useEffect(() => { if (isSummaryPage && notifications4SummaryPage.length > 0) { @@ -261,109 +325,78 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { }, [dispatch, notifications4SummaryPage, isSummaryPage]); useEffect(() => { - if (reportData?.reportMetricsError.boardMetricsError) { + if (!currentDataInfo.shouldShowBoardMetricsError) return; + if (currentDataInfo.reportData?.reportMetricsError.boardMetricsError) { + const notificationId = uniqueId(); + setErrorNotificationIds((pre) => [...pre, notificationId]); setNotifications4SummaryPage((prevState) => [ ...prevState, { + id: notificationId, message: MESSAGE.FAILED_TO_GET_DATA('Board Metrics'), type: 'error', }, ]); } - }, [reportData?.reportMetricsError.boardMetricsError]); + closeBoardMetricsError(selectedDateRange.startDate as string); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentDataInfo.reportData?.reportMetricsError.boardMetricsError]); useEffect(() => { - if (reportData?.reportMetricsError.pipelineMetricsError) { + if (!currentDataInfo.shouldShowPipelineMetricsError) return; + if (currentDataInfo.reportData?.reportMetricsError.pipelineMetricsError) { + const notificationId = uniqueId(); + setErrorNotificationIds((pre) => [...pre, notificationId]); setNotifications4SummaryPage((prevState) => [ ...prevState, { + id: notificationId, message: MESSAGE.FAILED_TO_GET_DATA('Buildkite'), type: 'error', }, ]); } - }, [reportData?.reportMetricsError.pipelineMetricsError]); + closePipelineMetricsError(selectedDateRange.startDate as string); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentDataInfo.reportData?.reportMetricsError.pipelineMetricsError]); useEffect(() => { - if (reportData?.reportMetricsError.sourceControlMetricsError) { + if (!currentDataInfo.shouldShowSourceControlMetricsError) return; + if (currentDataInfo.reportData?.reportMetricsError.sourceControlMetricsError) { + const notificationId = uniqueId(); + setErrorNotificationIds((pre) => [...pre, notificationId]); setNotifications4SummaryPage((prevState) => [ ...prevState, { + id: notificationId, message: MESSAGE.FAILED_TO_GET_DATA('GitHub'), type: 'error', }, ]); } - }, [reportData?.reportMetricsError.sourceControlMetricsError]); - - useEffect(() => { - timeout4Report && - setNotifications4SummaryPage((prevState) => [ - ...prevState, - { - message: MESSAGE.LOADING_TIMEOUT('Report'), - type: 'error', - }, - ]); - }, [timeout4Report]); - - useEffect(() => { - timeout4Board && - setNotifications4SummaryPage((prevState) => [ - ...prevState, - { - message: MESSAGE.LOADING_TIMEOUT('Board metrics'), - type: 'error', - }, - ]); - }, [timeout4Board]); - - useEffect(() => { - timeout4Dora && - setNotifications4SummaryPage((prevState) => [ - ...prevState, - { - message: MESSAGE.LOADING_TIMEOUT('DORA metrics'), - type: 'error', - }, - ]); - }, [timeout4Dora]); - - useEffect(() => { - generalError4Board && - setNotifications4SummaryPage((prevState) => [ - ...prevState, - { - message: MESSAGE.FAILED_TO_REQUEST, - type: 'error', - }, - ]); - }, [generalError4Board]); - - useEffect(() => { - generalError4Dora && - setNotifications4SummaryPage((prevState) => [ - ...prevState, - { - message: MESSAGE.FAILED_TO_REQUEST, - type: 'error', - }, - ]); - }, [generalError4Dora]); + closeSourceControlMetricsError(selectedDateRange.startDate as string); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentDataInfo.reportData?.reportMetricsError.sourceControlMetricsError]); useEffect(() => { - generalError4Report && - setNotifications4SummaryPage((prevState) => [ - ...prevState, - { - message: MESSAGE.FAILED_TO_REQUEST, - type: 'error', - }, - ]); - }, [generalError4Report]); + Object.values(TimeoutErrorKey).forEach((value) => handleTimeoutAndGeneralError(value)); + Object.values(GeneralErrorKey).forEach((value) => handleTimeoutAndGeneralError(value)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + currentDataInfo.timeout4Board, + currentDataInfo.timeout4Report, + currentDataInfo.timeout4Dora, + currentDataInfo.generalError4Board, + currentDataInfo.generalError4Dora, + currentDataInfo.generalError4Report, + ]); useEffect(() => { + setPageType(onlySelectClassification ? REPORT_PAGE_TYPE.BOARD : REPORT_PAGE_TYPE.SUMMARY); startToRequestData(basicReportRequestBody); + return () => { + stopPollingReports(); + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -373,7 +406,7 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { startToRequestData(boardReportRequestBody)} onShowDetail={() => setPageType(REPORT_PAGE_TYPE.BOARD)} - boardReport={reportData} + boardReport={currentDataInfo.reportData} errorMessage={getErrorMessage4Board()} /> )} @@ -381,8 +414,13 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { startToRequestData(doraReportRequestBody)} onShowDetail={() => setPageType(REPORT_PAGE_TYPE.DORA)} - doraReport={reportData} - errorMessage={timeout4Dora || timeout4Report || generalError4Dora || generalError4Report} + doraReport={currentDataInfo.reportData} + errorMessage={ + currentDataInfo.timeout4Dora.message || + currentDataInfo.timeout4Report.message || + currentDataInfo.generalError4Dora.message || + currentDataInfo.generalError4Report.message + } /> )} @@ -400,18 +438,43 @@ const ReportStep = ({ handleSave }: ReportStepProps) => { setPageType(REPORT_PAGE_TYPE.SUMMARY); }; + const handleTimeoutAndGeneralError = (value: string) => { + const errorKey = value as keyof IReportError; + if (!currentDataInfo[errorKey].shouldShow) return; + if (currentDataInfo[errorKey].message) { + const notificationId = uniqueId(); + setErrorNotificationIds((pre) => [...pre, notificationId]); + setNotifications4SummaryPage((prevState) => [ + ...prevState, + { + id: notificationId, + message: timeoutNotificationMessages[errorKey] + ? MESSAGE.LOADING_TIMEOUT(timeoutNotificationMessages[errorKey]) + : MESSAGE.FAILED_TO_REQUEST, + type: 'error', + }, + ]); + } + closeReportInfosErrorStatus(selectedDateRange.startDate as string, errorKey); + }; + return ( <> {startDate && endDate && ( - + setSelectedDateRange(dateRange)} + disabledAll={false} + /> )} {isSummaryPage ? showSummary() : pageType === REPORT_PAGE_TYPE.BOARD - ? showBoardDetail(reportData) - : !!reportData && showDoraDetail(reportData)} + ? showBoardDetail(currentDataInfo.reportData) + : !!currentDataInfo.reportData && showDoraDetail(currentDataInfo.reportData)} { isShowExportPipelineButton={isSummaryPage ? shouldShowDoraMetrics : pageType === REPORT_PAGE_TYPE.DORA} handleBack={() => handleBack()} handleSave={() => handleSave()} - reportData={reportData} - startDate={startDate} - endDate={endDate} csvTimeStamp={csvTimeStamp} + dateRangeRequestResults={mapDateResult(descendingDateRanges, reportInfos)} /> ); diff --git a/frontend/src/containers/ReportStep/style.tsx b/frontend/src/containers/ReportStep/style.tsx index 9186edec3f..960d9412dd 100644 --- a/frontend/src/containers/ReportStep/style.tsx +++ b/frontend/src/containers/ReportStep/style.tsx @@ -1,3 +1,4 @@ +import { Z_INDEX } from '@src/constants/commons'; import { styled } from '@mui/material/styles'; import { theme } from '@src/theme'; @@ -19,4 +20,5 @@ export const StyledCalendarWrapper = styled('div')((props: { isSummaryPage: bool justifyContent: 'flex-end', marginTop: '0.25rem', marginBottom: props.isSummaryPage ? '-3.5rem' : '-2rem', + zIndex: Z_INDEX.DROPDOWN, })); diff --git a/frontend/src/context/Metrics/metricsSlice.ts b/frontend/src/context/Metrics/metricsSlice.ts index 2b6316b564..c5f95e0f17 100644 --- a/frontend/src/context/Metrics/metricsSlice.ts +++ b/frontend/src/context/Metrics/metricsSlice.ts @@ -6,11 +6,11 @@ import { METRICS_CONSTANTS, } from '@src/constants/resources'; import { convertCycleTimeSettings, getSortedAndDeduplicationBoardingMapping } from '@src/utils/util'; -import { pipeline } from '@src/context/config/pipelineTool/verifyResponseSlice'; +import { IPipeline } from '@src/context/config/pipelineTool/verifyResponseSlice'; +import _, { omit, uniqWith, isEqual, intersection, concat } from 'lodash'; import { createSlice } from '@reduxjs/toolkit'; import camelCase from 'lodash.camelcase'; import { RootState } from '@src/store'; -import _ from 'lodash'; export interface IPipelineConfig { id: number; @@ -18,6 +18,7 @@ export interface IPipelineConfig { pipelineName: string; step: string; branches: string[]; + isStepEmptyString?: boolean; } export interface IReworkConfig { @@ -41,6 +42,7 @@ export interface ICycleTimeSetting { export interface ISavedMetricsSettingState { shouldGetBoardConfig: boolean; shouldGetPipeLineConfig: boolean; + shouldRetryPipelineConfig: boolean; jiraColumns: { key: string; value: { name: string; statuses: string[] } }[]; targetFields: { name: string; key: string; flag: boolean }[]; users: string[]; @@ -51,6 +53,7 @@ export interface ISavedMetricsSettingState { deploymentFrequencySettings: IPipelineConfig[]; leadTimeForChanges: IPipelineConfig[]; treatFlagCardAsBlock: boolean; + displayFlagCardDropWarning: boolean; assigneeFilter: string; firstTimeRoadMetricData: boolean; importedData: { @@ -76,6 +79,7 @@ export interface ISavedMetricsSettingState { const initialState: ISavedMetricsSettingState = { shouldGetBoardConfig: false, shouldGetPipeLineConfig: false, + shouldRetryPipelineConfig: false, jiraColumns: [], targetFields: [], users: [], @@ -86,6 +90,7 @@ const initialState: ISavedMetricsSettingState = { deploymentFrequencySettings: [], leadTimeForChanges: [{ id: 0, organization: '', pipelineName: '', step: '', branches: [] }], treatFlagCardAsBlock: true, + displayFlagCardDropWarning: true, assigneeFilter: ASSIGNEE_FILTER_TYPES.LAST_ASSIGNEE, firstTimeRoadMetricData: true, importedData: { @@ -159,14 +164,14 @@ const setCreateSelectUsers = (metricsState: ISavedMetricsSettingState, users: st } }; -const setPipelineCrews = (isProjectCreated: boolean, pipelineCrews: string[], importedPipelineCrews: string[]) => { +const setPipelineCrews = (isProjectCreated: boolean, pipelineCrews: string[], currentCrews: string[]) => { if (_.isEmpty(pipelineCrews)) { return []; } if (isProjectCreated) { return pipelineCrews; } - return pipelineCrews.filter((item: string) => importedPipelineCrews?.includes(item)); + return intersection(pipelineCrews, currentCrews); }; const setSelectTargetFields = ( @@ -304,7 +309,7 @@ export const metricsSlice = createSlice({ state.users = action.payload; }, savePipelineCrews: (state, action) => { - state.pipelineCrews = action.payload; + state.pipelineCrews = action.payload || []; }, updateCycleTimeSettings: (state, action) => { state.cycleTimeSettings = action.payload; @@ -332,7 +337,7 @@ export const metricsSlice = createSlice({ return deploymentFrequencySetting.id === updateId ? { ...deploymentFrequencySetting, - [label === 'Steps' ? 'step' : camelCase(label)]: value, + [label === 'Step' ? 'step' : camelCase(label)]: value, } : deploymentFrequencySetting; }); @@ -380,6 +385,9 @@ export const metricsSlice = createSlice({ const preJiraColumnsValue = getSortedAndDeduplicationBoardingMapping(state.cycleTimeSettings).filter( (item) => item !== METRICS_CONSTANTS.cycleTimeEmptyStr, ); + + state.displayFlagCardDropWarning = + state.displayFlagCardDropWarning && !isProjectCreated && importedCycleTime.importedTreatFlagCardAsBlock; state.users = isProjectCreated ? setCreateSelectUsers(state, users) : setImportSelectUsers(state, users, importedCrews); @@ -457,36 +465,65 @@ export const metricsSlice = createSlice({ importedAssigneeFilter === ASSIGNEE_FILTER_TYPES.HISTORICAL_ASSIGNEE ? importedAssigneeFilter : ASSIGNEE_FILTER_TYPES.LAST_ASSIGNEE; + }, - state.treatFlagCardAsBlock = - typeof importedCycleTime.importedTreatFlagCardAsBlock === 'boolean' - ? importedCycleTime.importedTreatFlagCardAsBlock - : true; + updatePiplineCrews: (state, action) => { + state.pipelineCrews = intersection(state.pipelineCrews, action.payload); }, updatePipelineSettings: (state, action) => { const { pipelineList, isProjectCreated, pipelineCrews } = action.payload; - const { importedDeployment, importedPipelineCrews } = state.importedData; - + const { importedDeployment } = state.importedData; if (pipelineCrews) { - state.pipelineCrews = setPipelineCrews(isProjectCreated, pipelineCrews, importedPipelineCrews); + state.pipelineCrews = setPipelineCrews(isProjectCreated, pipelineCrews, state.pipelineCrews); } - const orgNames: Array = _.uniq(pipelineList.map((item: pipeline) => item.orgName)); + const orgNames: Array = _.uniq(pipelineList.map((item: IPipeline) => item.orgName)); const filteredPipelineNames = (organization: string) => pipelineList - .filter((pipeline: pipeline) => pipeline.orgName.toLowerCase() === organization.toLowerCase()) - .map((item: pipeline) => item.name); + .filter((pipeline: IPipeline) => pipeline.orgName.toLowerCase() === organization.toLowerCase()) + .map((item: IPipeline) => item.name); + + const uniqueResponse = (res: IPipelineConfig[]) => { + let itemsOmitId = uniqWith( + res.map((value) => omit(value, ['id', 'isStepEmptyString'])), + isEqual, + ); + if (itemsOmitId.length > 1) { + itemsOmitId = itemsOmitId.filter( + (item) => !Object.values(item).every((value) => value === '' || !value?.length), + ); + } + return itemsOmitId.length < res.length + ? itemsOmitId.map((item, index) => { + return { + id: index, + ...item, + }; + }) + : res; + }; + const getValidPipelines = (pipelines: IPipelineConfig[]) => { const hasPipeline = pipelines.filter(({ id }) => id !== undefined).length; - return pipelines.length && hasPipeline - ? pipelines.map(({ id, organization, pipelineName, step, branches }) => ({ - id, - organization: orgNames.find((i) => (i as string).toLowerCase() === organization.toLowerCase()) || '', - pipelineName: filteredPipelineNames(organization).includes(pipelineName) ? pipelineName : '', - step: step || '', - branches: branches || [], - })) - : [{ id: 0, organization: '', pipelineName: '', step: '', branches: [] }]; + const res = + pipelines.length && hasPipeline + ? pipelines.map(({ id, organization, pipelineName, step, branches, isStepEmptyString }) => { + const matchedOrganization = + orgNames.find((i) => (i as string).toLowerCase() === organization.toLowerCase()) || ''; + const matchedPipelineName = filteredPipelineNames(organization).includes(pipelineName) + ? pipelineName + : ''; + return { + id, + isStepEmptyString: isStepEmptyString || false, + organization: matchedOrganization, + pipelineName: matchedPipelineName, + step: matchedPipelineName ? step : '', + branches: matchedPipelineName ? branches : [], + }; + }) + : [{ id: 0, organization: '', pipelineName: '', step: '', branches: [], isStepEmptyString: false }]; + return uniqueResponse(res); }; const createPipelineWarning = ({ id, organization, pipelineName }: IPipelineConfig) => { const orgWarning = orgNames.some((i) => (i as string).toLowerCase() === organization.toLowerCase()) @@ -519,47 +556,59 @@ export const metricsSlice = createSlice({ state.deploymentFrequencySettings = getValidPipelines(deploymentSettings); state.deploymentWarningMessage = getPipelinesWarningMessage(deploymentSettings); }, - updatePipelineStep: (state, action) => { const { steps, id, branches, pipelineCrews } = action.payload; - const { importedDeployment, importedPipelineCrews } = state.importedData; - const updatedImportedPipelineStep = importedDeployment.find((pipeline) => pipeline.id === id)?.step ?? ''; - const updatedImportedPipelineBranches = importedDeployment.find((pipeline) => pipeline.id === id)?.branches ?? []; const selectedPipelineStep = state.deploymentFrequencySettings.find((pipeline) => pipeline.id === id)?.step ?? ''; - state.pipelineCrews = _.filter(pipelineCrews, (crew) => importedPipelineCrews.includes(crew)); - const stepWarningMessage = (selectedStep: string) => (steps.includes(selectedStep) ? null : MESSAGE.STEP_WARNING); + const currentCrews = concat(pipelineCrews, state.pipelineCrews); + + state.pipelineCrews = intersection(currentCrews, state.pipelineCrews); + const stepWarningMessage = (selectedStep: string, isStepEmptyString: boolean) => + steps.includes(selectedStep) || isStepEmptyString ? null : MESSAGE.STEP_WARNING; - const validStep = (selectedStep: string): string => (steps.includes(selectedStep) ? selectedStep : ''); + const validStep = (pipeline: IPipelineConfig): string => { + const selectedStep = pipeline.step; + if (!selectedStep) { + pipeline.isStepEmptyString = true; + } + return steps.includes(selectedStep) ? selectedStep : ''; + }; const validBranches = (selectedBranches: string[]): string[] => _.filter(branches, (branch) => selectedBranches.includes(branch)); - const getPipelineSettings = (pipelines: IPipelineConfig[]) => - pipelines.map((pipeline) => - pipeline.id === id + const getPipelineSettings = (pipelines: IPipelineConfig[]) => { + return pipelines.map((pipeline) => { + const filterValidStep = validStep(pipeline); + return pipeline.id === id ? { ...pipeline, - step: validStep(pipeline.step || updatedImportedPipelineStep), - branches: validBranches( - pipeline.branches.length > 0 ? pipeline.branches : updatedImportedPipelineBranches, - ), + step: filterValidStep, + branches: validBranches(pipeline.branches.length > 0 ? pipeline.branches : []), } - : pipeline, - ); + : pipeline; + }); + }; - const getStepWarningMessage = (pipelines: IPipelineWarningMessageConfig[]) => { - return pipelines.map((pipeline) => - pipeline?.id === id + const getStepWarningMessage = ( + pipelinesWarning: IPipelineWarningMessageConfig[], + pipelinesValue: IPipelineConfig[], + ) => { + return pipelinesWarning.map((pipeline) => { + const matchedPipeline = pipelinesValue.find((pipeline) => pipeline.id === id); + return pipeline?.id === id ? { ...pipeline, - step: stepWarningMessage(selectedPipelineStep || updatedImportedPipelineStep), + step: stepWarningMessage(selectedPipelineStep, matchedPipeline?.isStepEmptyString || false), } - : pipeline, - ); + : pipeline; + }); }; + state.deploymentWarningMessage = getStepWarningMessage( + state.deploymentWarningMessage, + state.deploymentFrequencySettings, + ); state.deploymentFrequencySettings = getPipelineSettings(state.deploymentFrequencySettings); - state.deploymentWarningMessage = getStepWarningMessage(state.deploymentWarningMessage); }, deleteADeploymentFrequencySetting: (state, action) => { @@ -575,6 +624,10 @@ export const metricsSlice = createSlice({ state.treatFlagCardAsBlock = action.payload; }, + updateDisplayFlagCardDropWarning: (state, action) => { + state.displayFlagCardDropWarning = action.payload; + }, + updateAssigneeFilter: (state, action) => { state.assigneeFilter = action.payload; }, @@ -592,6 +645,10 @@ export const metricsSlice = createSlice({ updateFirstTimeRoadMetricsBoardData: (state, action) => { state.firstTimeRoadMetricData = action.payload; }, + + updateShouldRetryPipelineConfig: (state, action) => { + state.shouldRetryPipelineConfig = action.payload; + }, }, }); @@ -607,9 +664,11 @@ export const { updateMetricsImportedData, initDeploymentFrequencySettings, updateTreatFlagCardAsBlock, + updateDisplayFlagCardDropWarning, updateAssigneeFilter, updateMetricsState, updatePipelineSettings, + updatePiplineCrews, updatePipelineStep, setCycleTimeSettingsType, resetMetricData, @@ -618,6 +677,7 @@ export const { updateShouldGetPipelineConfig, updateReworkTimesSettings, updateFirstTimeRoadMetricsBoardData, + updateShouldRetryPipelineConfig, } = metricsSlice.actions; export const selectShouldGetBoardConfig = (state: RootState) => state.metrics.shouldGetBoardConfig; @@ -630,10 +690,12 @@ export const selectCycleTimeSettings = (state: RootState) => state.metrics.cycle export const selectMetricsContent = (state: RootState) => state.metrics; export const selectAdvancedSettings = (state: RootState) => state.metrics.importedData.importedAdvancedSettings; export const selectTreatFlagCardAsBlock = (state: RootState) => state.metrics.treatFlagCardAsBlock; +export const selectDisplayFlagCardDropWarning = (state: RootState) => state.metrics.displayFlagCardDropWarning; export const selectAssigneeFilter = (state: RootState) => state.metrics.assigneeFilter; export const selectCycleTimeWarningMessage = (state: RootState) => state.metrics.cycleTimeWarningMessage; export const selectClassificationWarningMessage = (state: RootState) => state.metrics.classificationWarningMessage; export const selectRealDoneWarningMessage = (state: RootState) => state.metrics.realDoneWarningMessage; +export const selectShouldRetryPipelineConfig = (state: RootState) => state.metrics.shouldRetryPipelineConfig; export const selectOrganizationWarningMessage = (state: RootState, id: number) => { const { deploymentWarningMessage } = state.metrics; diff --git a/frontend/src/context/config/board/boardSlice.ts b/frontend/src/context/config/board/boardSlice.ts index ba01fc7718..a4fff1aff2 100644 --- a/frontend/src/context/config/board/boardSlice.ts +++ b/frontend/src/context/config/board/boardSlice.ts @@ -14,7 +14,6 @@ export interface IBoardState { site: string; token: string; }; - isVerified: boolean; isShow: boolean; verifiedResponse: IBoardVerifyResponseState; } @@ -34,7 +33,6 @@ export const initialBoardState: IBoardState = { site: '', token: '', }, - isVerified: false, isShow: false, verifiedResponse: initialVerifiedBoardState, }; diff --git a/frontend/src/context/config/configSlice.ts b/frontend/src/context/config/configSlice.ts index 2bcec116f8..191fa283dc 100644 --- a/frontend/src/context/config/configSlice.ts +++ b/frontend/src/context/config/configSlice.ts @@ -3,32 +3,36 @@ import { CALENDAR, DORA_METRICS, IMPORT_METRICS_MAPPING, + MAX_TIME_RANGE_AMOUNT, MESSAGE, REQUIRED_DATA, - MAX_TIME_RANGE_AMOUNT, } from '@src/constants/resources'; import { initialPipelineToolState, IPipelineToolState } from '@src/context/config/pipelineTool/pipelineToolSlice'; import { initialSourceControlState, ISourceControl } from '@src/context/config/sourceControl/sourceControlSlice'; import { IBoardState, initialBoardState } from '@src/context/config/board/boardSlice'; -import { pipeline } from '@src/context/config/pipelineTool/verifyResponseSlice'; +import { IPipeline } from '@src/context/config/pipelineTool/verifyResponseSlice'; +import { uniqPipelineListCrews, updateResponseCrews } from '@src/utils/util'; +import { SortType } from '@src/containers/ConfigStep/DateRangePicker/types'; import { createSlice } from '@reduxjs/toolkit'; import type { RootState } from '@src/store'; -import union from 'lodash/union'; import merge from 'lodash/merge'; import { isArray } from 'lodash'; import dayjs from 'dayjs'; - -export type TDateRange = { +export interface DateRange { startDate: string | null; endDate: string | null; -}[]; + disabled?: boolean; +} + +export type DateRangeList = DateRange[]; export interface BasicConfigState { isProjectCreated: boolean; basic: { projectName: string; calendarType: string; - dateRange: TDateRange; + dateRange: DateRangeList; + sortType: SortType; metrics: string[]; }; board: IBoardState; @@ -48,6 +52,7 @@ export const initialBasicConfigState: BasicConfigState = { endDate: null, }, ], + sortType: SortType.DEFAULT, metrics: [], }, board: initialBoardState, @@ -82,6 +87,8 @@ const getMetricsInfo = (metrics: string[]) => { }; }; +const isSortType = (value: string): value is SortType => Object.values(SortType).includes(value as SortType); + export const configSlice = createSlice({ name: 'config', initialState: { @@ -100,6 +107,9 @@ export const configSlice = createSlice({ updateDateRange: (state, action) => { state.basic.dateRange = action.payload; }, + updateDateRangeSortType: (state, action) => { + state.basic.sortType = action.payload; + }, updateMetrics: (state, action) => { const { metrics, shouldBoardShow, shouldPipelineToolShow, shouldSourceControlShow } = getMetricsInfo( action.payload, @@ -123,6 +133,8 @@ export const configSlice = createSlice({ Array.isArray(importedDateRanges) && importedDateRanges.length > MAX_TIME_RANGE_AMOUNT ? importedDateRanges.slice(0, MAX_TIME_RANGE_AMOUNT) : importedDateRanges; + const importedSortType = action.payload.sortType; + action.payload.sortType = isSortType(importedSortType) ? importedSortType : SortType.DEFAULT; state.basic.metrics = metrics; state.board.isShow = shouldBoardShow; state.pipelineTool.isShow = shouldPipelineToolShow; @@ -134,20 +146,18 @@ export const configSlice = createSlice({ isArray(importedDateRanges) && importedDateRanges.length > 0 && importedDateRanges.length <= 6 && - metrics.length > 0 + metrics.length > 0 && + ((importedDateRanges.length === 1 && !importedSortType) || isSortType(importedSortType)) ? null : MESSAGE.CONFIG_PAGE_VERIFY_IMPORT_ERROR; } - state.board.config = merge(action.payload.board, { type: 'jira' }); + state.board.config = merge(action.payload.board, { type: 'Jira' }); state.pipelineTool.config = action.payload.pipelineTool || state.pipelineTool.config; state.sourceControl.config = action.payload.sourceControl || state.sourceControl.config; }, updateProjectCreatedState: (state, action) => { state.isProjectCreated = action.payload; }, - updateBoardVerifyState: (state, action) => { - state.board.isVerified = action.payload; - }, updateBoard: (state, action) => { state.board.config = action.payload; }, @@ -157,16 +167,12 @@ export const configSlice = createSlice({ state.board.verifiedResponse.targetFields = targetFields; state.board.verifiedResponse.users = users; }, - - updatePipelineToolVerifyState: (state, action) => { - state.pipelineTool.isVerified = action.payload; - }, updatePipelineTool: (state, action) => { state.pipelineTool.config = action.payload; }, updatePipelineToolVerifyResponse: (state, action) => { const { pipelineList } = action.payload; - state.pipelineTool.verifiedResponse.pipelineList = pipelineList.map((pipeline: pipeline) => ({ + state.pipelineTool.verifiedResponse.pipelineList = pipelineList.map((pipeline: IPipeline) => ({ ...pipeline, steps: [], })); @@ -180,17 +186,10 @@ export const configSlice = createSlice({ ...pipeline, branches: branches, steps: steps, + crews: pipelineCrews, } : pipeline, ); - - state.pipelineTool.verifiedResponse.pipelineCrews = union( - state.pipelineTool.verifiedResponse.pipelineCrews, - pipelineCrews, - ); - }, - updateSourceControlVerifyState: (state, action) => { - state.sourceControl.isVerified = action.payload; }, updateSourceControl: (state, action) => { state.sourceControl.config = action.payload; @@ -199,6 +198,14 @@ export const configSlice = createSlice({ const { githubRepos } = action.payload; state.sourceControl.verifiedResponse.repoList = githubRepos; }, + updatePipelineToolVerifyResponseCrews: (state, action) => { + const { organization, pipelineName } = action.payload; + state.pipelineTool.verifiedResponse.pipelineList = updateResponseCrews( + organization, + pipelineName, + state.pipelineTool.verifiedResponse.pipelineList, + ); + }, resetImportedData: () => initialBasicConfigState, }, }); @@ -207,24 +214,23 @@ export const { updateProjectName, updateCalendarType, updateDateRange, + updateDateRangeSortType, updateMetrics, updateBoard, - updateBoardVerifyState, updateJiraVerifyResponse, updateBasicConfigState, - updatePipelineToolVerifyState, updatePipelineTool, updatePipelineToolVerifyResponse, updateSourceControl, - updateSourceControlVerifyState, updateSourceControlVerifiedResponse, updatePipelineToolVerifyResponseSteps, resetImportedData, + updatePipelineToolVerifyResponseCrews, } = configSlice.actions; -export const selectProjectName = (state: RootState) => state.config.basic.projectName; -export const selectCalendarType = (state: RootState) => state.config.basic.calendarType; +export const selectBasicInfo = (state: RootState) => state.config.basic; export const selectDateRange = (state: RootState) => state.config.basic.dateRange; +export const selectDateRangeSortType = (state: RootState) => state.config.basic.sortType; export const selectMetrics = (state: RootState) => state.config.basic.metrics; export const isSelectBoardMetrics = (state: RootState) => state.config.basic.metrics.some((metric) => BOARD_METRICS.includes(metric)); @@ -233,15 +239,10 @@ export const isSelectDoraMetrics = (state: RootState) => export const isOnlySelectClassification = (state: RootState) => state.config.basic.metrics.length === 1 && state.config.basic.metrics[0] === REQUIRED_DATA.CLASSIFICATION; export const selectBoard = (state: RootState) => state.config.board.config; -export const isPipelineToolVerified = (state: RootState) => state.config.pipelineTool.isVerified; export const selectPipelineTool = (state: RootState) => state.config.pipelineTool.config; -export const isSourceControlVerified = (state: RootState) => state.config.sourceControl.isVerified; export const selectSourceControl = (state: RootState) => state.config.sourceControl.config; export const selectWarningMessage = (state: RootState) => state.config.warningMessage; - export const selectConfig = (state: RootState) => state.config; - -export const selectIsBoardVerified = (state: RootState) => state.config.board.isVerified; export const selectUsers = (state: RootState) => state.config.board.verifiedResponse.users; export const selectJiraColumns = (state: RootState) => state.config.board.verifiedResponse.jiraColumns; export const selectIsProjectCreated = (state: RootState) => state.config.isProjectCreated; @@ -260,22 +261,23 @@ export const selectStepsParams = (state: RootState, organizationName: string, pi (pipeline) => pipeline.name === pipelineName && pipeline.orgName === organizationName, ); - const { startDate, endDate } = state.config.basic.dateRange[0]; const pipelineType = state.config.pipelineTool.config.type; const token = state.config.pipelineTool.config.token; return { - params: { - pipelineName: pipeline?.name ?? '', - repository: pipeline?.repository ?? '', - orgName: pipeline?.orgName ?? '', - startTime: dayjs(startDate).startOf('date').valueOf(), - endTime: dayjs(endDate).endOf('date').valueOf(), - }, buildId: pipeline?.id ?? '', organizationId: pipeline?.orgId ?? '', pipelineType, token, + params: state.config.basic.dateRange.map((dateRange) => { + return { + pipelineName: pipeline?.name ?? '', + repository: pipeline?.repository ?? '', + orgName: pipeline?.orgName ?? '', + startTime: dayjs(dateRange.startDate).startOf('date').valueOf(), + endTime: dayjs(dateRange.endDate).endOf('date').valueOf(), + }; + }), }; }; @@ -286,6 +288,9 @@ export const selectSteps = (state: RootState, organizationName: string, pipeline (pipeline) => pipeline.name === pipelineName && pipeline.orgName === organizationName, )?.steps ?? []; -export const selectPipelineCrews = (state: RootState) => state.config.pipelineTool.verifiedResponse.pipelineCrews; +export const selectPipelineCrews = (state: RootState) => { + const { pipelineList } = state.config.pipelineTool.verifiedResponse; + return uniqPipelineListCrews(pipelineList); +}; export default configSlice.reducer; diff --git a/frontend/src/context/config/pipelineTool/pipelineToolSlice.ts b/frontend/src/context/config/pipelineTool/pipelineToolSlice.ts index 2179ae7994..010826e2c4 100644 --- a/frontend/src/context/config/pipelineTool/pipelineToolSlice.ts +++ b/frontend/src/context/config/pipelineTool/pipelineToolSlice.ts @@ -3,7 +3,6 @@ import { PIPELINE_TOOL_TYPES } from '@src/constants/resources'; export interface IPipelineToolState { config: { type: string; token: string }; - isVerified: boolean; isShow: boolean; verifiedResponse: IPipelineToolVerifyResponse; } @@ -13,7 +12,6 @@ export const initialPipelineToolState: IPipelineToolState = { type: PIPELINE_TOOL_TYPES.BUILD_KITE, token: '', }, - isVerified: false, isShow: false, verifiedResponse: initialPipelineToolVerifiedResponseState, }; diff --git a/frontend/src/context/config/pipelineTool/verifyResponseSlice.ts b/frontend/src/context/config/pipelineTool/verifyResponseSlice.ts index f8ac16642a..30f288e765 100644 --- a/frontend/src/context/config/pipelineTool/verifyResponseSlice.ts +++ b/frontend/src/context/config/pipelineTool/verifyResponseSlice.ts @@ -1,9 +1,8 @@ export interface IPipelineToolVerifyResponse { - pipelineList: pipeline[]; - pipelineCrews: string[]; + pipelineList: IPipeline[]; } -export interface pipeline { +export interface IPipeline { id: string; name: string; orgId: string; @@ -11,9 +10,9 @@ export interface pipeline { repository: string; steps: string[]; branches: string[]; + crews: string[]; } export const initialPipelineToolVerifiedResponseState: IPipelineToolVerifyResponse = { pipelineList: [], - pipelineCrews: [], }; diff --git a/frontend/src/context/config/sourceControl/sourceControlSlice.ts b/frontend/src/context/config/sourceControl/sourceControlSlice.ts index 06cb9dae12..c5d0a3bbeb 100644 --- a/frontend/src/context/config/sourceControl/sourceControlSlice.ts +++ b/frontend/src/context/config/sourceControl/sourceControlSlice.ts @@ -3,7 +3,6 @@ import { SOURCE_CONTROL_TYPES } from '@src/constants/resources'; export interface ISourceControl { config: { type: string; token: string }; - isVerified: boolean; isShow: boolean; verifiedResponse: ISourceControlVerifyResponse; } @@ -13,7 +12,6 @@ export const initialSourceControlState: ISourceControl = { type: SOURCE_CONTROL_TYPES.GITHUB, token: '', }, - isVerified: false, isShow: false, verifiedResponse: initSourceControlVerifyResponseState, }; diff --git a/frontend/src/context/meta/metaSlice.tsx b/frontend/src/context/meta/metaSlice.tsx index 66c4db1e1b..8f23face44 100644 --- a/frontend/src/context/meta/metaSlice.tsx +++ b/frontend/src/context/meta/metaSlice.tsx @@ -53,12 +53,19 @@ export const metaSlice = createSlice({ initMetricsPipelineFormMeta: (state, action: PayloadAction) => { const id = action.payload; const branchesFormData = state.form.metrics.pipelines[id]; - if (!branchesFormData) state.form.metrics.pipelines[id] = { branches: [], }; }, + initSinglePipelineListBranches: (state, action: PayloadAction) => { + const id = action.payload; + const branchesFormData = state.form.metrics.pipelines[id]; + if (branchesFormData) + state.form.metrics.pipelines[id] = { + branches: [], + }; + }, clearMetricsPipelineFormMeta: (state) => { state.form.metrics.pipelines = {}; }, @@ -86,6 +93,7 @@ export const { resetFormMeta, updateFormMeta, initMetricsPipelineFormMeta, + initSinglePipelineListBranches, deleteMetricsPipelineFormMeta, updateMetricsPipelineBranchFormMeta, clearMetricsPipelineFormMeta, diff --git a/frontend/src/context/stepper/StepperSlice.tsx b/frontend/src/context/stepper/StepperSlice.tsx index 15051de290..f3d0913e96 100644 --- a/frontend/src/context/stepper/StepperSlice.tsx +++ b/frontend/src/context/stepper/StepperSlice.tsx @@ -2,16 +2,36 @@ import { createSlice } from '@reduxjs/toolkit'; import { ZERO } from '@src/constants/commons'; import type { RootState } from '@src/store'; +export interface IMetricsPageFailedDateRange { + isBoardInfoError?: boolean; + isPipelineInfoError?: boolean; + isPipelineStepError?: boolean; +} + +export interface IReportPageFailedDateRange { + isGainPollingUrlError?: boolean; + isPollingError?: boolean; +} + +export interface IPageFailedDateRangePayload { + startDate: string; + errors: T; +} + export interface StepState { stepNumber: number; timeStamp: number; - shouldMetricsLoad: boolean; + shouldMetricsLoaded: boolean; + metricsPageFailedTimeRangeInfos: Record; + reportPageFailedTimeRangeInfos: Record; } const initialState: StepState = { stepNumber: 0, timeStamp: 0, - shouldMetricsLoad: true, + shouldMetricsLoaded: true, + metricsPageFailedTimeRangeInfos: {}, + reportPageFailedTimeRangeInfos: {}, }; export const stepperSlice = createSlice({ @@ -23,23 +43,68 @@ export const stepperSlice = createSlice({ state.timeStamp = initialState.timeStamp; }, nextStep: (state) => { - state.shouldMetricsLoad = true; + if (state.shouldMetricsLoaded && state.stepNumber === 0) { + state.metricsPageFailedTimeRangeInfos = {}; + } + state.shouldMetricsLoaded = true; state.stepNumber += 1; }, backStep: (state) => { - state.shouldMetricsLoad = false; + state.shouldMetricsLoaded = false; state.stepNumber = state.stepNumber === ZERO ? ZERO : state.stepNumber - 1; }, + updateShouldMetricsLoaded: (state, action) => { + state.shouldMetricsLoaded = action.payload; + }, updateTimeStamp: (state, action) => { state.timeStamp = action.payload; }, + updateMetricsPageFailedTimeRangeInfos: (state, action) => { + const errorInfoList: IPageFailedDateRangePayload[] = action.payload; + + errorInfoList.forEach((singleTimeRangeInfo) => updateInfo(singleTimeRangeInfo)); + + function updateInfo(errorInfo: IPageFailedDateRangePayload) { + const { startDate, errors } = errorInfo; + state.metricsPageFailedTimeRangeInfos[startDate] = { + ...state.metricsPageFailedTimeRangeInfos[startDate], + ...errors, + }; + } + }, + + updateReportPageFailedTimeRangeInfos: (state, action) => { + const errorInfoList: IPageFailedDateRangePayload[] = action.payload; + + errorInfoList.forEach((singleTimeRangeInfo) => updateInfo(singleTimeRangeInfo)); + + function updateInfo(errorInfo: IPageFailedDateRangePayload) { + const { startDate, errors } = errorInfo; + state.reportPageFailedTimeRangeInfos[startDate] = { + ...state.reportPageFailedTimeRangeInfos[startDate], + ...errors, + }; + } + }, }, }); -export const { resetStep, nextStep, backStep, updateTimeStamp } = stepperSlice.actions; +export const { + resetStep, + nextStep, + backStep, + updateShouldMetricsLoaded, + updateTimeStamp, + updateMetricsPageFailedTimeRangeInfos, + updateReportPageFailedTimeRangeInfos, +} = stepperSlice.actions; export const selectStepNumber = (state: RootState) => state.stepper.stepNumber; export const selectTimeStamp = (state: RootState) => state.stepper.timeStamp; -export const shouldMetricsLoad = (state: RootState) => state.stepper.shouldMetricsLoad; +export const shouldMetricsLoaded = (state: RootState) => state.stepper.shouldMetricsLoaded; +export const selectMetricsPageFailedTimeRangeInfos = (state: RootState) => + state.stepper.metricsPageFailedTimeRangeInfos; + +export const selectReportPageFailedTimeRangeInfos = (state: RootState) => state.stepper.reportPageFailedTimeRangeInfos; export default stepperSlice.reducer; diff --git a/frontend/src/hooks/reportMapper/deploymentFrequency.ts b/frontend/src/hooks/reportMapper/deploymentFrequency.ts index df4689bd44..0445616891 100644 --- a/frontend/src/hooks/reportMapper/deploymentFrequency.ts +++ b/frontend/src/hooks/reportMapper/deploymentFrequency.ts @@ -1,30 +1,17 @@ -import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { ReportDataWithTwoColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; import { DeploymentFrequencyResponse } from '@src/clients/report/dto/response'; -import { DEPLOYMENT_FREQUENCY_NAME } from '@src/constants/resources'; -export const deploymentFrequencyMapper = ({ - avgDeploymentFrequency, - deploymentFrequencyOfPipelines, -}: DeploymentFrequencyResponse) => { - const mappedDeploymentFrequencyValue: ReportDataWithThreeColumns[] = []; +export const deploymentFrequencyMapper = ({ deploymentFrequencyOfPipelines }: DeploymentFrequencyResponse) => { + const mappedDeploymentFrequencyValue: ReportDataWithTwoColumns[] = []; deploymentFrequencyOfPipelines.map((item, index) => { - const deploymentFrequencyValue: ReportDataWithThreeColumns = { + const deploymentFrequencyValue: ReportDataWithTwoColumns = { id: index, name: `${item.name}/${item.step}`, - valuesList: [{ name: DEPLOYMENT_FREQUENCY_NAME, value: `${item.deploymentFrequency.toFixed(2)}` }], + valueList: [{ value: `${item.deploymentFrequency.toFixed(2)}` }], }; mappedDeploymentFrequencyValue.push(deploymentFrequencyValue); }); - mappedDeploymentFrequencyValue.push({ - id: mappedDeploymentFrequencyValue.length, - name: avgDeploymentFrequency.name, - valuesList: [ - { - name: DEPLOYMENT_FREQUENCY_NAME, - value: `${avgDeploymentFrequency.deploymentFrequency.toFixed(2)}`, - }, - ], - }); + return mappedDeploymentFrequencyValue; }; diff --git a/frontend/src/hooks/reportMapper/devChangeFailureRate.ts b/frontend/src/hooks/reportMapper/devChangeFailureRate.ts index 7dd9a0375b..682384b996 100644 --- a/frontend/src/hooks/reportMapper/devChangeFailureRate.ts +++ b/frontend/src/hooks/reportMapper/devChangeFailureRate.ts @@ -1,38 +1,20 @@ -import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { ReportDataWithTwoColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; import { DevChangeFailureRateResponse } from '@src/clients/report/dto/response'; -import { DEV_FAILURE_RATE_NAME } from '@src/constants/resources'; -export const devChangeFailureRateMapper = ({ - avgDevChangeFailureRate, - devChangeFailureRateOfPipelines, -}: DevChangeFailureRateResponse) => { - const mappedDevChangeFailureRateValue: ReportDataWithThreeColumns[] = []; +export const devChangeFailureRateMapper = ({ devChangeFailureRateOfPipelines }: DevChangeFailureRateResponse) => { + const mappedDevChangeFailureRateValue: ReportDataWithTwoColumns[] = []; devChangeFailureRateOfPipelines.map((item, index) => { - const devChangeFailureRateValue: ReportDataWithThreeColumns = { + const devChangeFailureRateValue: ReportDataWithTwoColumns = { id: index, name: `${item.name}/${item.step}`, - valuesList: [ + valueList: [ { - name: DEV_FAILURE_RATE_NAME, value: `${(item.failureRate * 100).toFixed(2)}%(${item.failedTimesOfPipeline}/${item.totalTimesOfPipeline})`, }, ], }; mappedDevChangeFailureRateValue.push(devChangeFailureRateValue); }); - mappedDevChangeFailureRateValue.push({ - id: mappedDevChangeFailureRateValue.length, - name: avgDevChangeFailureRate.name, - valuesList: [ - { - name: DEV_FAILURE_RATE_NAME, - value: `${(avgDevChangeFailureRate.failureRate * 100).toFixed(2)}%(${avgDevChangeFailureRate.totalFailedTimes}/${ - avgDevChangeFailureRate.totalTimes - })`, - }, - ], - }); - return mappedDevChangeFailureRateValue; }; diff --git a/frontend/src/hooks/reportMapper/devMeanTimeToRecovery.ts b/frontend/src/hooks/reportMapper/devMeanTimeToRecovery.ts index 086063de94..597b727173 100644 --- a/frontend/src/hooks/reportMapper/devMeanTimeToRecovery.ts +++ b/frontend/src/hooks/reportMapper/devMeanTimeToRecovery.ts @@ -1,11 +1,7 @@ -import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { ReportDataWithTwoColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; import { DevMeanTimeToRecoveryResponse } from '@src/clients/report/dto/response'; -import { DEV_MEAN_TIME_TO_RECOVERY_NAME } from '@src/constants/resources'; -export const devMeanTimeToRecoveryMapper = ({ - avgDevMeanTimeToRecovery, - devMeanTimeToRecoveryOfPipelines, -}: DevMeanTimeToRecoveryResponse) => { +export const devMeanTimeToRecoveryMapper = ({ devMeanTimeToRecoveryOfPipelines }: DevMeanTimeToRecoveryResponse) => { const minutesPerHour = 60; const milliscondMinute = 60000; const formatDuration = (duration: number) => { @@ -13,31 +9,20 @@ export const devMeanTimeToRecoveryMapper = ({ return (minutesDuration / minutesPerHour).toFixed(2); }; - const mappedDevMeanTimeToRecoveryValue: ReportDataWithThreeColumns[] = []; + const mappedDevMeanTimeToRecoveryValue: ReportDataWithTwoColumns[] = []; devMeanTimeToRecoveryOfPipelines.map((item, index) => { - const devMeanTimeToRecoveryValue: ReportDataWithThreeColumns = { + const devMeanTimeToRecoveryValue: ReportDataWithTwoColumns = { id: index, name: `${item.name}/${item.step}`, - valuesList: [ + valueList: [ { - name: DEV_MEAN_TIME_TO_RECOVERY_NAME, value: formatDuration(item.timeToRecovery), }, ], }; mappedDevMeanTimeToRecoveryValue.push(devMeanTimeToRecoveryValue); }); - mappedDevMeanTimeToRecoveryValue.push({ - id: mappedDevMeanTimeToRecoveryValue.length, - name: avgDevMeanTimeToRecovery.name, - valuesList: [ - { - name: DEV_MEAN_TIME_TO_RECOVERY_NAME, - value: formatDuration(avgDevMeanTimeToRecovery.timeToRecovery), - }, - ], - }); return mappedDevMeanTimeToRecoveryValue; }; diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index 009da1163c..5a70d4f814 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -1,118 +1,360 @@ +import { + IPageFailedDateRangePayload, + IReportPageFailedDateRange, + updateReportPageFailedTimeRangeInfos, +} from '@src/context/stepper/StepperSlice'; +import { ReportCallbackResponse, ReportResponseDTO } from '@src/clients/report/dto/response'; import { exportValidityTimeMapper } from '@src/hooks/reportMapper/exportValidityTime'; import { DATA_LOADING_FAILED, DEFAULT_MESSAGE } from '@src/constants/resources'; -import { ReportResponseDTO } from '@src/clients/report/dto/response'; +import { DateRangeList, selectConfig } from '@src/context/config/configSlice'; +import { IPollingRes, reportClient } from '@src/clients/report/ReportClient'; import { ReportRequestDTO } from '@src/clients/report/dto/request'; -import { reportClient } from '@src/clients/report/ReportClient'; +import { formatDateToTimestampString } from '@src/utils/util'; +import { useAppDispatch } from '@src/hooks/useAppDispatch'; import { TimeoutError } from '@src/errors/TimeoutError'; import { METRIC_TYPES } from '@src/constants/commons'; +import { useAppSelector } from '@src/hooks/index'; import { useRef, useState } from 'react'; +import get from 'lodash/get'; -export interface useGenerateReportEffectInterface { - startToRequestData: (params: ReportRequestDTO) => void; +export type PromiseSettledResultWithId = PromiseSettledResult & { + id: string; +}; + +export interface IUseGenerateReportEffect { + startToRequestData: (params: ReportRequestDTO) => Promise; stopPollingReports: () => void; - timeout4Board: string; - timeout4Dora: string; - timeout4Report: string; - generalError4Board: string; - generalError4Dora: string; - generalError4Report: string; + reportInfos: IReportInfo[]; + closeReportInfosErrorStatus: (id: string, errorKey: string) => void; + closeBoardMetricsError: (id: string) => void; + closePipelineMetricsError: (id: string) => void; + closeSourceControlMetricsError: (id: string) => void; + hasPollingStarted: boolean; +} + +interface IErrorInfo { + message: string; + shouldShow: boolean; +} + +export interface IReportError { + timeout4Board: IErrorInfo; + timeout4Dora: IErrorInfo; + timeout4Report: IErrorInfo; + generalError4Board: IErrorInfo; + generalError4Dora: IErrorInfo; + generalError4Report: IErrorInfo; +} + +export interface IReportInfo extends IReportError { + id: string; reportData: ReportResponseDTO | undefined; + shouldShowBoardMetricsError: boolean; + shouldShowPipelineMetricsError: boolean; + shouldShowSourceControlMetricsError: boolean; } -export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { +export const initReportInfo = (): IReportInfo => ({ + id: '', + timeout4Board: { message: DEFAULT_MESSAGE, shouldShow: true }, + timeout4Dora: { message: DEFAULT_MESSAGE, shouldShow: true }, + timeout4Report: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Board: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Dora: { message: DEFAULT_MESSAGE, shouldShow: true }, + generalError4Report: { message: DEFAULT_MESSAGE, shouldShow: true }, + shouldShowBoardMetricsError: true, + shouldShowPipelineMetricsError: true, + shouldShowSourceControlMetricsError: true, + reportData: undefined, +}); + +export const TimeoutErrorKey = { + [METRIC_TYPES.BOARD]: 'timeout4Board', + [METRIC_TYPES.DORA]: 'timeout4Dora', + [METRIC_TYPES.ALL]: 'timeout4Report', +}; + +export const GeneralErrorKey = { + [METRIC_TYPES.BOARD]: 'generalError4Board', + [METRIC_TYPES.DORA]: 'generalError4Dora', + [METRIC_TYPES.ALL]: 'generalError4Report', +}; + +const REJECTED = 'rejected'; +const FULFILLED = 'fulfilled'; + +const getErrorKey = (error: Error, source: METRIC_TYPES): string => { + return error instanceof TimeoutError ? TimeoutErrorKey[source] : GeneralErrorKey[source]; +}; + +export const useGenerateReportEffect = (): IUseGenerateReportEffect => { const reportPath = '/reports'; - const [timeout4Board, setTimeout4Board] = useState(DEFAULT_MESSAGE); - const [timeout4Dora, setTimeout4Dora] = useState(DEFAULT_MESSAGE); - const [timeout4Report, setTimeout4Report] = useState(DEFAULT_MESSAGE); - const [generalError4Board, setGeneralError4Board] = useState(DEFAULT_MESSAGE); - const [generalError4Dora, setGeneralError4Dora] = useState(DEFAULT_MESSAGE); - const [generalError4Report, setGeneralError4Report] = useState(DEFAULT_MESSAGE); - const [reportData, setReportData] = useState(); + const dispatch = useAppDispatch(); + const configData = useAppSelector(selectConfig); const timerIdRef = useRef(); - let hasPollingStarted = false; - - const startToRequestData = (params: ReportRequestDTO) => { + const dateRangeList: DateRangeList = get(configData, 'basic.dateRange', []); + const [reportInfos, setReportInfos] = useState( + dateRangeList.map((dateRange) => ({ ...initReportInfo(), id: dateRange.startDate as string })), + ); + const [hasPollingStarted, setHasPollingStarted] = useState(false); + let nextHasPollingStarted = false; + const startToRequestData = async (params: ReportRequestDTO) => { const { metricTypes } = params; resetTimeoutMessage(metricTypes); - reportClient - .retrieveByUrl(params, reportPath) - .then((res) => { - if (hasPollingStarted) return; - hasPollingStarted = true; - pollingReport(res.response.callbackUrl, res.response.interval); - }) - .catch((e) => { - const source: METRIC_TYPES = metricTypes.length === 2 ? METRIC_TYPES.ALL : metricTypes[0]; - handleError(e, source); - }); + if (hasPollingStarted || nextHasPollingStarted) return; + nextHasPollingStarted = true; + setHasPollingStarted(nextHasPollingStarted); + const res: PromiseSettledResult[] = await Promise.allSettled( + dateRangeList.map(({ startDate, endDate }) => + reportClient.retrieveByUrl( + { + ...params, + startTime: formatDateToTimestampString(startDate!), + endTime: formatDateToTimestampString(endDate!), + }, + reportPath, + ), + ), + ); + + updateErrorAfterFetchReport(res, metricTypes); + + const { pollingInfos, pollingInterval } = assemblePollingParams(res); + + await pollingReport({ pollingInfos, interval: pollingInterval }); }; - const resetTimeoutMessage = (metricTypes: string[]) => { - if (metricTypes.length === 2) { - setTimeout4Report(DEFAULT_MESSAGE); - } else if (metricTypes.includes(METRIC_TYPES.BOARD)) { - setTimeout4Board(DEFAULT_MESSAGE); - } else { - setTimeout4Dora(DEFAULT_MESSAGE); + function getReportInfosAfterPolling( + preReportInfos: IReportInfo[], + pollingResponsesWithId: PromiseSettledResultWithId[], + ) { + return preReportInfos.map((reportInfo) => { + const matchedRes = pollingResponsesWithId.find((singleRes) => singleRes.id === reportInfo.id); + if (!matchedRes) return reportInfo; + + if (matchedRes.status === FULFILLED) { + const { response } = matchedRes.value; + reportInfo.reportData = assembleReportData(response); + reportInfo.shouldShowBoardMetricsError = true; + reportInfo.shouldShowPipelineMetricsError = true; + reportInfo.shouldShowSourceControlMetricsError = true; + } else { + const errorKey = getErrorKey(matchedRes.reason, METRIC_TYPES.ALL) as keyof IReportError; + reportInfo[errorKey] = { message: DATA_LOADING_FAILED, shouldShow: true }; + } + return reportInfo; + }); + } + + const pollingReport = async ({ + pollingInfos, + interval, + }: { + pollingInfos: Record[]; + interval: number; + }) => { + const pollingIds: string[] = pollingInfos.map((pollingInfo) => pollingInfo.id); + initReportInfosTimeout4Report(pollingIds); + + const pollingQueue: Promise[] = pollingInfos.map((pollingInfo) => + reportClient.polling(pollingInfo.callbackUrl), + ); + const pollingResponses = await Promise.allSettled(pollingQueue); + const pollingResponsesWithId = assemblePollingResWithId(pollingResponses, pollingInfos); + + setReportInfos((preReportInfos) => getReportInfosAfterPolling(preReportInfos, pollingResponsesWithId)); + updateReportPageFailedTimeRangeInfosAfterPolling(pollingResponsesWithId); + + const nextPollingInfos = getNextPollingInfos(pollingResponsesWithId, pollingInfos); + if (nextPollingInfos.length === 0) { + stopPollingReports(); + return; } + timerIdRef.current = window.setTimeout(() => { + pollingReport({ pollingInfos: nextPollingInfos, interval }); + }, interval * 1000); }; - const handleTimeoutError = { - [METRIC_TYPES.BOARD]: setTimeout4Board, - [METRIC_TYPES.DORA]: setTimeout4Dora, - [METRIC_TYPES.ALL]: setTimeout4Report, + const stopPollingReports = () => { + window.clearTimeout(timerIdRef.current); + setHasPollingStarted(false); }; - const handleGeneralError = { - [METRIC_TYPES.BOARD]: setGeneralError4Board, - [METRIC_TYPES.DORA]: setGeneralError4Dora, - [METRIC_TYPES.ALL]: setGeneralError4Report, + const assembleReportData = (response: ReportResponseDTO): ReportResponseDTO => { + const exportValidityTime = exportValidityTimeMapper(response.exportValidityTime); + return { ...response, exportValidityTime: exportValidityTime }; }; - const handleError = (error: Error, source: METRIC_TYPES) => { - return error instanceof TimeoutError - ? handleTimeoutError[source](DATA_LOADING_FAILED) - : handleGeneralError[source](DATA_LOADING_FAILED); + const resetTimeoutMessage = (metricTypes: string[]) => { + setReportInfos((preReportInfos) => { + return preReportInfos.map((reportInfo) => { + if (metricTypes.length === 2) { + reportInfo.timeout4Report = { message: DEFAULT_MESSAGE, shouldShow: true }; + } else if (metricTypes.includes(METRIC_TYPES.BOARD)) { + reportInfo.timeout4Board = { message: DEFAULT_MESSAGE, shouldShow: true }; + } else { + reportInfo.timeout4Dora = { message: DEFAULT_MESSAGE, shouldShow: true }; + } + return reportInfo; + }); + }); }; - const pollingReport = (url: string, interval: number) => { - setTimeout4Report(DEFAULT_MESSAGE); - reportClient - .polling(url) - .then((res: { status: number; response: ReportResponseDTO }) => { - const response = res.response; - handleAndUpdateData(response); - if (response.allMetricsCompleted || !hasPollingStarted) { - stopPollingReports(); - } else { - timerIdRef.current = window.setTimeout(() => pollingReport(url, interval), interval * 1000); + const updateErrorAfterFetchReport = ( + res: PromiseSettledResult[], + metricTypes: METRIC_TYPES[], + ) => { + if (res.filter(({ status }) => status === REJECTED).length === 0) return; + + setReportInfos((preReportInfos: IReportInfo[]) => { + return preReportInfos.map((resInfo, index) => { + const currentRes = res[index]; + if (currentRes.status === REJECTED) { + const source: METRIC_TYPES = metricTypes.length === 2 ? METRIC_TYPES.ALL : metricTypes[0]; + const errorKey = getErrorKey(currentRes.reason, source) as keyof IReportError; + resInfo[errorKey] = { message: DATA_LOADING_FAILED, shouldShow: true }; } - }) - .catch((e) => { - handleError(e, METRIC_TYPES.ALL); - stopPollingReports(); + return resInfo; }); + }); + + updateReportPageFailedTimeRangeInfosAfterReport(res); }; - const stopPollingReports = () => { - window.clearTimeout(timerIdRef.current); - hasPollingStarted = false; + function updateReportPageFailedTimeRangeInfosAfterPolling( + pollingResponsesWithId: PromiseSettledResultWithId[], + ) { + const updateReportPageFailedTimeRangeInfosPayload: IPageFailedDateRangePayload[] = []; + pollingResponsesWithId.forEach((currentRes) => { + updateReportPageFailedTimeRangeInfosPayload.push({ + startDate: formatDateToTimestampString(currentRes.id), + errors: { isPollingError: currentRes.status === REJECTED }, + }); + }); + dispatch(updateReportPageFailedTimeRangeInfos(updateReportPageFailedTimeRangeInfosPayload)); + } + + function updateReportPageFailedTimeRangeInfosAfterReport(res: PromiseSettledResult[]) { + const updateReportPageFailedTimeRangeInfosPayload: IPageFailedDateRangePayload[] = []; + res.forEach((currentRes, index) => { + updateReportPageFailedTimeRangeInfosPayload.push({ + startDate: formatDateToTimestampString(reportInfos[index].id), + errors: { isGainPollingUrlError: currentRes.status === REJECTED }, + }); + }); + dispatch(updateReportPageFailedTimeRangeInfos(updateReportPageFailedTimeRangeInfosPayload)); + } + + const assemblePollingParams = (res: PromiseSettledResult[]) => { + const resWithIds: PromiseSettledResultWithId[] = res.map((item, index) => ({ + ...item, + id: reportInfos[index].id, + })); + + const fulfilledResponses: PromiseSettledResultWithId[] = resWithIds.filter( + ({ status }) => status === FULFILLED, + ); + + const pollingInfos: Record[] = fulfilledResponses.map((v) => { + return { callbackUrl: (v as PromiseFulfilledResult).value.callbackUrl, id: v.id }; + }); + + const pollingInterval = (fulfilledResponses[0] as PromiseFulfilledResult)?.value.interval; + return { pollingInfos, pollingInterval }; }; - const handleAndUpdateData = (response: ReportResponseDTO) => { - const exportValidityTime = exportValidityTimeMapper(response.exportValidityTime); - setReportData({ ...response, exportValidityTime: exportValidityTime }); + const assemblePollingResWithId = ( + pollingResponses: Array>>>, + pollingInfos: Record[], + ) => { + const pollingResponsesWithId: PromiseSettledResultWithId[] = pollingResponses.map( + (singleRes, index) => ({ + ...singleRes, + id: pollingInfos[index].id, + }), + ); + return pollingResponsesWithId; + }; + + const getNextPollingInfos = ( + pollingResponsesWithId: PromiseSettledResultWithId[], + pollingInfos: Record[], + ) => { + const nextPollingInfos: Record[] = pollingResponsesWithId + .filter( + (pollingResponseWithId) => + pollingResponseWithId.status === FULFILLED && + !pollingResponseWithId.value.response.allMetricsCompleted && + nextHasPollingStarted, + ) + .map((pollingResponseWithId) => pollingInfos.find((pollingInfo) => pollingInfo.id === pollingResponseWithId.id)!); + return nextPollingInfos; + }; + + const initReportInfosTimeout4Report = (pollingIds: string[]) => { + setReportInfos((preInfos) => { + return preInfos.map((info) => { + if (pollingIds.includes(info.id)) { + info.timeout4Report = { message: DEFAULT_MESSAGE, shouldShow: true }; + } + return info; + }); + }); + }; + + const closeReportInfosErrorStatus = (id: string, errorKey: string) => { + setReportInfos((preReportInfos) => { + return preReportInfos.map((reportInfo) => { + if (reportInfo.id === id) { + const key = errorKey as keyof IReportError; + reportInfo[key].shouldShow = false; + } + return reportInfo; + }); + }); + }; + + const closeBoardMetricsError = (id: string) => { + setReportInfos((preReportInfos) => { + return preReportInfos.map((reportInfo) => { + if (reportInfo.id === id) { + reportInfo.shouldShowBoardMetricsError = false; + } + return reportInfo; + }); + }); + }; + + const closePipelineMetricsError = (id: string) => { + setReportInfos((preReportInfos) => { + return preReportInfos.map((reportInfo) => { + if (reportInfo.id === id) { + reportInfo.shouldShowPipelineMetricsError = false; + } + return reportInfo; + }); + }); + }; + + const closeSourceControlMetricsError = (id: string) => { + setReportInfos((preReportInfos) => { + return preReportInfos.map((reportInfo) => { + if (reportInfo.id === id) { + reportInfo.shouldShowSourceControlMetricsError = false; + } + return reportInfo; + }); + }); }; return { startToRequestData, stopPollingReports, - reportData, - timeout4Board, - timeout4Dora, - timeout4Report, - generalError4Board, - generalError4Dora, - generalError4Report, + reportInfos, + closeReportInfosErrorStatus, + closeBoardMetricsError, + closePipelineMetricsError, + closeSourceControlMetricsError, + hasPollingStarted, }; }; diff --git a/frontend/src/hooks/useGetBoardInfo.ts b/frontend/src/hooks/useGetBoardInfo.ts index 9990f245c2..dfc172d706 100644 --- a/frontend/src/hooks/useGetBoardInfo.ts +++ b/frontend/src/hooks/useGetBoardInfo.ts @@ -1,85 +1,160 @@ -import { BOARD_CONFIG_INFO_ERROR, BOARD_CONFIG_INFO_TITLE } from '@src/constants/resources'; +import { AXIOS_REQUEST_ERROR_CODE, BOARD_CONFIG_INFO_ERROR, BOARD_CONFIG_INFO_TITLE } from '@src/constants/resources'; +import { updateMetricsPageFailedTimeRangeInfos } from '@src/context/stepper/StepperSlice'; import { boardInfoClient } from '@src/clients/board/BoardInfoClient'; -import { BoardInfoRequestDTO } from '@src/clients/board/dto/request'; -import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; -import { AxiosResponse, HttpStatusCode } from 'axios'; +import { BoardInfoConfigDTO } from '@src/clients/board/dto/request'; +import { METRICS_DATA_FAIL_STATUS } from '@src/constants/commons'; +import { formatDateToTimestampString } from '@src/utils/util'; +import { useAppDispatch } from '@src/hooks/index'; import { ReactNode, useState } from 'react'; +import { HttpStatusCode } from 'axios'; import get from 'lodash/get'; +import dayjs from 'dayjs'; export type JiraColumns = Record[]; export type TargetFields = Record[]; export type Users = string[]; + export interface BoardInfoResponse { jiraColumns: JiraColumns; targetFields: TargetFields; + ignoredTargetFields: TargetFields; users: Users; } + export interface useGetBoardInfoInterface { - getBoardInfo: (data: BoardInfoRequestDTO) => Promise>; + getBoardInfo: (data: BoardInfoConfigDTO) => Promise | undefined>; isLoading: boolean; errorMessage: Record; + boardInfoFailedStatus: METRICS_DATA_FAIL_STATUS; } -const codeMapping = (code: string | number) => { - const codes = { - [HttpStatusCode.BadRequest]: { - title: BOARD_CONFIG_INFO_TITLE.INVALID_INPUT, - message: BOARD_CONFIG_INFO_ERROR.NOT_FOUND, - code: HttpStatusCode.BadRequest, - }, - [HttpStatusCode.Unauthorized]: { - title: BOARD_CONFIG_INFO_TITLE.UNAUTHORIZED_REQUEST, - message: BOARD_CONFIG_INFO_ERROR.NOT_FOUND, - code: HttpStatusCode.Unauthorized, - }, - [HttpStatusCode.Forbidden]: { - title: BOARD_CONFIG_INFO_TITLE.FORBIDDEN_REQUEST, - message: BOARD_CONFIG_INFO_ERROR.FORBIDDEN, - code: HttpStatusCode.Forbidden, +const boardInfoPartialFailedStatusMapping = (code: string | number) => { + if (code == AXIOS_REQUEST_ERROR_CODE.TIMEOUT) { + return METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_TIMEOUT; + } + const numericCode = code as number; + if (numericCode >= HttpStatusCode.BadRequest && numericCode < HttpStatusCode.InternalServerError) { + return METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_4XX; + } + return METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_4XX; +}; + +const errorStatusMap = (status: METRICS_DATA_FAIL_STATUS) => { + const errorStatusMap = { + [METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_4XX]: { + errorMessage: { + title: BOARD_CONFIG_INFO_TITLE.GENERAL_ERROR, + message: BOARD_CONFIG_INFO_ERROR.GENERAL_ERROR, + code: HttpStatusCode.BadRequest, + }, + elevateStatus: METRICS_DATA_FAIL_STATUS.ALL_FAILED_4XX, }, - [HttpStatusCode.NotFound]: { - title: BOARD_CONFIG_INFO_TITLE.NOT_FOUND, - message: BOARD_CONFIG_INFO_ERROR.NOT_FOUND, - code: HttpStatusCode.NotFound, + [METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_TIMEOUT]: { + errorMessage: { + title: BOARD_CONFIG_INFO_TITLE.EMPTY, + message: BOARD_CONFIG_INFO_ERROR.RETRY, + code: AXIOS_REQUEST_ERROR_CODE.TIMEOUT, + }, + elevateStatus: METRICS_DATA_FAIL_STATUS.ALL_FAILED_TIMEOUT, }, - [AXIOS_REQUEST_ERROR_CODE.TIMEOUT]: { - title: BOARD_CONFIG_INFO_TITLE.EMPTY, - message: BOARD_CONFIG_INFO_ERROR.RETRY, - code: AXIOS_REQUEST_ERROR_CODE.TIMEOUT, + [METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_NO_CARDS]: { + errorMessage: { + title: BOARD_CONFIG_INFO_TITLE.NO_CONTENT, + message: BOARD_CONFIG_INFO_ERROR.NOT_CONTENT, + code: AXIOS_REQUEST_ERROR_CODE.NO_CARDS, + }, + elevateStatus: METRICS_DATA_FAIL_STATUS.ALL_FAILED_NO_CARDS, }, }; - return get(codes, code); + return get(errorStatusMap, status); }; export const useGetBoardInfoEffect = (): useGetBoardInfoInterface => { + const dispatch = useAppDispatch(); const [isLoading, setIsLoading] = useState(false); const [errorMessage, setErrorMessage] = useState({}); + const [boardInfoFailedStatus, setBoardInfoFailedStatus] = useState(METRICS_DATA_FAIL_STATUS.NOT_FAILED); - const getBoardInfo = (data: BoardInfoRequestDTO) => { + const getBoardInfo = async (data: BoardInfoConfigDTO) => { setIsLoading(true); setErrorMessage({}); - return boardInfoClient - .getBoardInfo(data) - .then((res) => { - if (!res.data) { - setErrorMessage({ - title: BOARD_CONFIG_INFO_TITLE.NO_CONTENT, - message: BOARD_CONFIG_INFO_ERROR.NOT_CONTENT, - code: HttpStatusCode.NoContent, + const localFailedTimeRangeList: string[] = []; + let errorCount = 0; + let localBoardInfoFailedStatus: METRICS_DATA_FAIL_STATUS; + + if (data.dateRanges) { + const dateRangeCopy = Array.from(data.dateRanges); + dateRangeCopy.sort((a, b) => dayjs(a.startDate).valueOf() - dayjs(b.startDate).valueOf()); + const allBoardData = dateRangeCopy.map((info) => { + const request = { + token: data.token, + type: data.type, + site: data.site, + email: data.email, + boardId: data.boardId, + projectKey: data.projectKey, + }; + const boardInfoRequest = { + ...request, + startTime: dayjs(info.startDate).valueOf().toString(), + endTime: dayjs(info.endDate).valueOf().toString(), + }; + + return boardInfoClient + .getBoardInfo(boardInfoRequest) + .then((res) => { + if (!res.data) { + errorCount++; + localBoardInfoFailedStatus = METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_NO_CARDS; + localFailedTimeRangeList.push(formatDateToTimestampString(info.startDate as string)); + setBoardInfoFailedStatus(METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_NO_CARDS); + } + return res; + }) + .catch((err) => { + errorCount++; + localBoardInfoFailedStatus = boardInfoPartialFailedStatusMapping(err?.code); + localFailedTimeRangeList.push(formatDateToTimestampString(info.startDate as string)); + setBoardInfoFailedStatus(localBoardInfoFailedStatus); + return err; }); - } - return res; - }) - .catch((err) => { - const { code } = err; - setErrorMessage(codeMapping(code)); - return err; - }) - .finally(() => setIsLoading(false)); + }); + + return Promise.all(allBoardData) + .then((res) => { + const config = errorStatusMap(localBoardInfoFailedStatus); + if (errorCount == res.length) { + if (config) { + setErrorMessage(config.errorMessage); + setBoardInfoFailedStatus(config.elevateStatus); + } + } else if (errorCount != 0) { + if (config) { + setErrorMessage(config.errorMessage); + } + } + const data = res.filter((r) => r.data); + return data?.map((r) => r.data); + }) + .finally(() => { + setIsLoading(false); + dispatch( + updateMetricsPageFailedTimeRangeInfos( + dateRangeCopy.map((dateRange) => ({ + startDate: formatDateToTimestampString(dateRange.startDate!), + errors: { + boardInfoError: localFailedTimeRangeList.includes(formatDateToTimestampString(dateRange.startDate!)), + }, + })), + ), + ); + }); + } }; return { getBoardInfo, errorMessage, isLoading, + boardInfoFailedStatus, }; }; diff --git a/frontend/src/hooks/useGetMetricsStepsEffect.ts b/frontend/src/hooks/useGetMetricsStepsEffect.ts index f73f7a0120..a371f7b49c 100644 --- a/frontend/src/hooks/useGetMetricsStepsEffect.ts +++ b/frontend/src/hooks/useGetMetricsStepsEffect.ts @@ -1,52 +1,113 @@ -import { getStepsParams, metricsClient } from '@src/clients/MetricsClient'; -import { MESSAGE } from '@src/constants/resources'; -import { DURATION } from '@src/constants/commons'; +import { updateMetricsPageFailedTimeRangeInfos } from '@src/context/stepper/StepperSlice'; +import { updateShouldRetryPipelineConfig } from '@src/context/Metrics/metricsSlice'; +import { IStepsParams, IStepsRes, metricsClient } from '@src/clients/MetricsClient'; +import { METRICS_DATA_FAIL_STATUS, DURATION } from '@src/constants/commons'; +import { FULFILLED, MESSAGE, REJECTED } from '@src/constants/resources'; +import { useAppDispatch } from '@src/hooks/useAppDispatch'; +import { TimeoutError } from '@src/errors/TimeoutError'; import { useState } from 'react'; export interface useGetMetricsStepsEffectInterface { getSteps: ( - params: getStepsParams, + params: IStepsParams[], organizationId: string, buildId: string, pipelineType: string, token: string, - ) => Promise< - | { - haveStep: boolean; - response: string[]; - branches: string[]; - pipelineCrews: string[]; - } - | undefined - >; + ) => Promise; isLoading: boolean; errorMessage: string; + stepFailedStatus: METRICS_DATA_FAIL_STATUS; +} + +const TIMEOUT = 'timeout'; + +function isAllTimeoutError(allStepsRes: PromiseSettledResult[]) { + return allStepsRes.every((stepInfo) => { + return (stepInfo as PromiseRejectedResult).reason instanceof TimeoutError; + }); } export const useGetMetricsStepsEffect = (): useGetMetricsStepsEffectInterface => { + const dispatch = useAppDispatch(); const [isLoading, setIsLoading] = useState(false); const [errorMessage, setErrorMessage] = useState(''); + const [stepFailedStatus, setStepFailedStatus] = useState(METRICS_DATA_FAIL_STATUS.NOT_FAILED); const getSteps = async ( - params: getStepsParams, + params: IStepsParams[], organizationId: string, buildId: string, pipelineType: string, token: string, ) => { setIsLoading(true); - try { - return await metricsClient.getSteps(params, organizationId, buildId, pipelineType, token); - } catch (e) { - const err = e as Error; - setErrorMessage(`${MESSAGE.GET_STEPS_FAILED} ${pipelineType} steps: ${err.message}`); - setTimeout(() => { - setErrorMessage(''); - }, DURATION.ERROR_MESSAGE_TIME); - } finally { - setIsLoading(false); + const allStepsRes = await Promise.allSettled( + params.map((param) => { + return metricsClient.getSteps(param, organizationId, buildId, pipelineType, token); + }), + ); + const hasRejected = allStepsRes.some((stepInfo) => stepInfo.status === REJECTED); + const hasFulfilled = allStepsRes.some((stepInfo) => stepInfo.status === FULFILLED); + + dispatch( + updateMetricsPageFailedTimeRangeInfos( + params.map((param, index) => { + return { + startDate: param.startTime, + errors: { pipelineStepError: allStepsRes[index].status === REJECTED }, + }; + }), + ), + ); + if (!hasRejected) { + setStepFailedStatus(METRICS_DATA_FAIL_STATUS.NOT_FAILED); + } else if (hasRejected && hasFulfilled) { + const rejectedStep = allStepsRes.find((stepInfo) => stepInfo.status === REJECTED); + if ((rejectedStep as PromiseRejectedResult).reason.code == 400) { + setStepFailedStatus(METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_4XX); + } else { + setStepFailedStatus(METRICS_DATA_FAIL_STATUS.PARTIAL_FAILED_TIMEOUT); + } } + setIsLoading(false); + if (allStepsRes.every((stepInfo) => stepInfo.status === REJECTED)) { + if (isAllTimeoutError(allStepsRes)) { + dispatch(updateShouldRetryPipelineConfig(true)); + setErrorMessageAndTime(pipelineType, TIMEOUT); + return; + } + setErrorMessageAndTime(pipelineType); + return; + } + + return allStepsRes + .filter((stepInfo) => stepInfo.status === FULFILLED) + .map((stepInfo) => (stepInfo as PromiseFulfilledResult).value) + .reduce( + (accumulator, currentValue) => { + return { + response: Array.from(new Set([...accumulator.response, ...currentValue.response])), + haveStep: accumulator.haveStep || currentValue.haveStep, + branches: Array.from(new Set([...accumulator.branches, ...currentValue.branches])), + pipelineCrews: Array.from(new Set([...accumulator.pipelineCrews, ...currentValue.pipelineCrews])), + }; + }, + { + response: [], + haveStep: false, + branches: [], + pipelineCrews: [], + } as IStepsRes, + ); + }; + + const setErrorMessageAndTime = (pipelineType: string, errReason?: string) => { + setErrorMessage(`${MESSAGE.GET_STEPS_FAILED} ${pipelineType} steps${errReason ? ': ' + errReason : ''}`); + setTimeout(() => { + setErrorMessage(''); + }, DURATION.ERROR_MESSAGE_TIME); }; - return { isLoading, getSteps, errorMessage }; + return { isLoading, getSteps, errorMessage, stepFailedStatus }; }; diff --git a/frontend/src/hooks/useGetPipelineToolInfoEffect.ts b/frontend/src/hooks/useGetPipelineToolInfoEffect.ts index 176a53865b..b7f20056e8 100644 --- a/frontend/src/hooks/useGetPipelineToolInfoEffect.ts +++ b/frontend/src/hooks/useGetPipelineToolInfoEffect.ts @@ -1,22 +1,23 @@ import { updatePipelineToolVerifyResponse, - isPipelineToolVerified, selectIsProjectCreated, selectPipelineTool, selectDateRange, } from '@src/context/config/configSlice'; +import { shouldMetricsLoaded, updateMetricsPageFailedTimeRangeInfos } from '@src/context/stepper/StepperSlice'; import { pipelineToolClient, IGetPipelineToolInfoResult } from '@src/clients/pipeline/PipelineToolClient'; import { selectShouldGetPipelineConfig, updatePipelineSettings } from '@src/context/Metrics/metricsSlice'; import { clearMetricsPipelineFormMeta } from '@src/context/meta/metaSlice'; -import { useEffect, useState, useRef, useCallback, useMemo } from 'react'; -import { shouldMetricsLoad } from '@src/context/stepper/StepperSlice'; +import { useEffect, useState, useRef, useCallback } from 'react'; +import { formatDateToTimestampString } from '@src/utils/util'; import { useAppDispatch, useAppSelector } from '@src/hooks'; -import { sortDateRanges } from '@src/utils/util'; +import { HttpStatusCode } from 'axios'; export interface IUseVerifyPipeLineToolStateInterface { result: IGetPipelineToolInfoResult; isLoading: boolean; apiCallFunc: () => void; + isFirstFetch: boolean; } export const useGetPipelineToolInfoEffect = (): IUseVerifyPipeLineToolStateInterface => { @@ -29,38 +30,42 @@ export const useGetPipelineToolInfoEffect = (): IUseVerifyPipeLineToolStateInter const [isLoading, setIsLoading] = useState(false); const apiTouchedRef = useRef(false); const [info, setInfo] = useState(defaultInfoStructure); - const pipelineToolVerified = useAppSelector(isPipelineToolVerified); const isProjectCreated = useAppSelector(selectIsProjectCreated); const restoredPipelineTool = useAppSelector(selectPipelineTool); - const dateRange = useAppSelector(selectDateRange); - const sortedDateRanges = useMemo(() => sortDateRanges(dateRange), [dateRange]); - const shouldLoad = useAppSelector(shouldMetricsLoad); + const shouldLoad = useAppSelector(shouldMetricsLoaded); const shouldGetPipelineConfig = useAppSelector(selectShouldGetPipelineConfig); + const dateRangeList = useAppSelector(selectDateRange); + const [isFirstFetch, setIsFirstFetch] = useState(shouldGetPipelineConfig); const getPipelineToolInfo = useCallback(async () => { const params = { type: restoredPipelineTool.type, token: restoredPipelineTool.token, - startTime: sortedDateRanges[0]?.startDate, - endTime: sortedDateRanges[0]?.endDate, }; setIsLoading(true); try { const response = await pipelineToolClient.getInfo(params); setInfo(response); - dispatch(updatePipelineToolVerifyResponse(response.data)); - pipelineToolVerified && dispatch(updatePipelineSettings({ ...response.data, isProjectCreated })); + if (response.code === HttpStatusCode.Ok) { + dispatch(updatePipelineToolVerifyResponse(response.data)); + dispatch(updatePipelineSettings({ ...response.data, isProjectCreated })); + } + dispatch( + updateMetricsPageFailedTimeRangeInfos( + dateRangeList.map((dateRange) => ({ + startDate: formatDateToTimestampString(dateRange.startDate!), + errors: { + pipelineInfoError: response.code !== HttpStatusCode.Ok, + }, + })), + ), + ); } finally { setIsLoading(false); + setIsFirstFetch(false); } - }, [ - dispatch, - isProjectCreated, - pipelineToolVerified, - restoredPipelineTool.type, - restoredPipelineTool.token, - sortedDateRanges, - ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dispatch, isProjectCreated, restoredPipelineTool.type, restoredPipelineTool.token]); useEffect(() => { if (!apiTouchedRef.current && !isLoading && shouldLoad && shouldGetPipelineConfig) { @@ -73,6 +78,7 @@ export const useGetPipelineToolInfoEffect = (): IUseVerifyPipeLineToolStateInter return { result: info, isLoading, + isFirstFetch, apiCallFunc: getPipelineToolInfo, }; }; diff --git a/frontend/src/hooks/useVerifyBoardEffect.ts b/frontend/src/hooks/useVerifyBoardEffect.ts index d97a3a6d17..6f5e3f4796 100644 --- a/frontend/src/hooks/useVerifyBoardEffect.ts +++ b/frontend/src/hooks/useVerifyBoardEffect.ts @@ -1,36 +1,40 @@ -import { BOARD_TYPES, AXIOS_REQUEST_ERROR_CODE, MESSAGE, UNKNOWN_ERROR_TITLE } from '@src/constants/resources'; -import { selectBoard, updateBoard, updateBoardVerifyState } from '@src/context/config/configSlice'; +import { AXIOS_REQUEST_ERROR_CODE, UNKNOWN_ERROR_TITLE } from '@src/constants/resources'; +import { BOARD_CONFIG_ERROR_MESSAGE } from '@src/containers/ConfigStep/Form/literal'; +import { useDefaultValues } from '@src/containers/ConfigStep/Form/useDefaultValues'; import { updateTreatFlagCardAsBlock } from '@src/context/Metrics/metricsSlice'; -import { findCaseInsensitiveType, getJiraBoardToken } from '@src/utils/util'; -import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch'; -import { DEFAULT_HELPER_TEXT, EMPTY_STRING } from '@src/constants/commons'; +import { updateShouldGetBoardConfig } from '@src/context/Metrics/metricsSlice'; +import { IBoardConfigData } from '@src/containers/ConfigStep/Form/schema'; +import { TBoardFieldKeys } from '@src/containers/ConfigStep/Form/type'; import { BoardRequestDTO } from '@src/clients/board/dto/request'; +import { updateBoard } from '@src/context/config/configSlice'; import { boardClient } from '@src/clients/board/BoardClient'; +import { useAppDispatch } from '@src/hooks/useAppDispatch'; +import { getJiraBoardToken } from '@src/utils/util'; import { IAppError } from '@src/errors/ErrorType'; -import { REGEX } from '@src/constants/regex'; +import { useFormContext } from 'react-hook-form'; import { isAppError } from '@src/errors'; import { HttpStatusCode } from 'axios'; import { useState } from 'react'; -export interface Field { - key: string; - value: string; - validateRule?: (value: string) => boolean; - validatedError: string; - verifiedError: string; +export enum FIELD_KEY { + TYPE = 0, + BOARD_ID = 1, + EMAIL = 2, + SITE = 3, + TOKEN = 4, +} + +export interface IField { + key: TBoardFieldKeys; col: number; + label: string; } export interface useVerifyBoardStateInterface { - isVerifyTimeOut: boolean; verifyJira: () => Promise; isLoading: boolean; - fields: Field[]; - updateField: (key: string, value: string) => void; - validateField: (key: string) => void; + fields: IField[]; resetFields: () => void; - setIsShowAlert: (value: boolean) => void; - isShowAlert: boolean; } const ERROR_INFO = { @@ -38,187 +42,85 @@ const ERROR_INFO = { BOARD_NOT_FOUND: 'boardId is incorrect', }; -const VALIDATOR = { - EMAIL: (value: string) => REGEX.EMAIL.test(value), - TOKEN: (value: string) => REGEX.BOARD_TOKEN.test(value), - BOARD_ID: (value: string) => REGEX.BOARD_ID.test(value), -}; - -export const KEYS = { - BOARD: 'Board', - BOARD_ID: 'Board Id', - EMAIL: 'Email', - SITE: 'Site', - TOKEN: 'Token', -}; - -const getValidatedError = (key: string, value: string, validateRule?: (value: string) => boolean) => { - if (!value) { - return `${key} is required!`; - } - if (validateRule && !validateRule(value)) { - return `${key} is invalid!`; - } - return DEFAULT_HELPER_TEXT; +export const KEYS: { [key: string]: TBoardFieldKeys } = { + BOARD: 'type', + BOARD_ID: 'boardId', + EMAIL: 'email', + SITE: 'site', + TOKEN: 'token', }; export const useVerifyBoardEffect = (): useVerifyBoardStateInterface => { const [isLoading, setIsLoading] = useState(false); - const [isVerifyTimeOut, setIsVerifyTimeOut] = useState(false); - const [isShowAlert, setIsShowAlert] = useState(false); - const boardFields = useAppSelector(selectBoard); const dispatch = useAppDispatch(); - const type = findCaseInsensitiveType(Object.values(BOARD_TYPES), boardFields.type); - const [fields, setFields] = useState([ + const { boardConfigOriginal } = useDefaultValues(); + const { reset, setError, getValues } = useFormContext(); + + const originalFields: IField[] = [ { key: KEYS.BOARD, - value: type, - validatedError: '', - verifiedError: '', col: 1, + label: 'Board', }, { key: KEYS.BOARD_ID, - value: boardFields.boardId, - validateRule: VALIDATOR.BOARD_ID, - validatedError: boardFields.boardId - ? getValidatedError(KEYS.BOARD_ID, boardFields.boardId, VALIDATOR.BOARD_ID) - : '', - verifiedError: '', col: 1, + label: 'Board Id', }, { key: KEYS.EMAIL, - value: boardFields.email, - validateRule: VALIDATOR.EMAIL, - validatedError: boardFields.email ? getValidatedError(KEYS.EMAIL, boardFields.email, VALIDATOR.EMAIL) : '', - verifiedError: '', col: 1, + label: 'Email', }, { key: KEYS.SITE, - value: boardFields.site, - validatedError: '', - verifiedError: '', col: 1, + label: 'Site', }, { key: KEYS.TOKEN, - value: boardFields.token, - validateRule: VALIDATOR.TOKEN, - validatedError: boardFields.token ? getValidatedError(KEYS.TOKEN, boardFields.token, VALIDATOR.TOKEN) : '', - verifiedError: '', col: 2, + label: 'Token', }, - ]); - - const getBoardInfo = (fields: Field[]) => { - const keys = ['type', 'boardId', 'email', 'site', 'token']; - return keys.reduce((board, key, index) => ({ ...board, [key]: fields[index].value }), {}); - }; + ]; - const handleUpdate = (fields: Field[]) => { - setFields(fields); - dispatch(updateBoardVerifyState(false)); - dispatch(updateBoard(getBoardInfo(fields))); + const persistReduxData = (shouldGetBoardConfig: boolean, boardInfo: IBoardConfigData & { projectKey?: string }) => { + dispatch(updateShouldGetBoardConfig(shouldGetBoardConfig)); + dispatch(updateBoard(boardInfo)); }; const resetFields = () => { - const newFields = fields.map((field) => - field.key === KEYS.BOARD - ? field - : { - ...field, - value: EMPTY_STRING, - validatedError: '', - verifiedError: '', - }, - ); - handleUpdate(newFields); - setIsShowAlert(false); - }; - - const getFieldsWithNoVerifiedError = (fields: Field[]) => - fields.map((field) => ({ - ...field, - verifiedError: '', - })); - - const updateField = (key: string, value: string) => { - const shouldClearVerifiedError = !!fields.find((field) => field.key === key)?.verifiedError; - const fieldsWithError = shouldClearVerifiedError ? getFieldsWithNoVerifiedError(fields) : fields; - const newFields = fieldsWithError.map((field) => - field.key === key - ? { - ...field, - value: value.trim(), - validatedError: getValidatedError(field.key, value.trim(), field.validateRule), - } - : field, - ); - handleUpdate(newFields); - }; - - const validateField = (key: string) => { - const newFields = fields.map((field) => - field.key === key - ? { - ...field, - validatedError: getValidatedError(field.key, field.value, field.validateRule), - } - : field, - ); - setFields(newFields); - }; - - const setVerifiedError = (keys: string[], messages: string[]) => { - setFields( - fields.map((field) => { - return keys.includes(field.key) - ? { - ...field, - validatedError: '', - verifiedError: messages[keys.findIndex((key) => key === field.key)], - } - : field; - }), - ); + reset(boardConfigOriginal); + persistReduxData(false, boardConfigOriginal); }; const verifyJira = async () => { setIsLoading(true); dispatch(updateTreatFlagCardAsBlock(true)); - const boardInfo = getBoardInfo(fields) as BoardRequestDTO; + const boardInfo = getValues() as BoardRequestDTO; try { const res: { response: Record } = await boardClient.getVerifyBoard({ ...boardInfo, token: getJiraBoardToken(boardInfo.token, boardInfo.email), }); if (res?.response) { - setIsShowAlert(false); - setIsVerifyTimeOut(false); - dispatch(updateBoardVerifyState(true)); - dispatch(updateBoard({ ...boardInfo, projectKey: res.response.projectKey })); + persistReduxData(true, { ...boardInfo, projectKey: res.response.projectKey }); + reset(boardConfigOriginal, { keepValues: true }); } } catch (e) { if (isAppError(e)) { const { description, code } = e as IAppError; - setIsVerifyTimeOut(false); - setIsShowAlert(false); if (code === HttpStatusCode.Unauthorized) { - setVerifiedError( - [KEYS.EMAIL, KEYS.TOKEN], - [MESSAGE.VERIFY_MAIL_FAILED_ERROR, MESSAGE.VERIFY_TOKEN_FAILED_ERROR], - ); + setError(KEYS.EMAIL, { message: BOARD_CONFIG_ERROR_MESSAGE.email.verifyFailed }); + setError(KEYS.TOKEN, { message: BOARD_CONFIG_ERROR_MESSAGE.token.verifyFailed }); } else if (code === HttpStatusCode.NotFound && description === ERROR_INFO.SITE_NOT_FOUND) { - setVerifiedError([KEYS.SITE], [MESSAGE.VERIFY_SITE_FAILED_ERROR]); + setError(KEYS.SITE, { message: BOARD_CONFIG_ERROR_MESSAGE.site.verifyFailed }); } else if (code === HttpStatusCode.NotFound && description === ERROR_INFO.BOARD_NOT_FOUND) { - setVerifiedError([KEYS.BOARD_ID], [MESSAGE.VERIFY_BOARD_FAILED_ERROR]); + setError(KEYS.BOARD_ID, { message: BOARD_CONFIG_ERROR_MESSAGE.boardId.verifyFailed }); } else if (code === AXIOS_REQUEST_ERROR_CODE.TIMEOUT) { - setIsVerifyTimeOut(true); - setIsShowAlert(true); + setError(KEYS.TOKEN, { message: BOARD_CONFIG_ERROR_MESSAGE.token.timeout }); } else { - setVerifiedError([KEYS.TOKEN], [UNKNOWN_ERROR_TITLE]); + setError(KEYS.TOKEN, { message: UNKNOWN_ERROR_TITLE }); } } } @@ -228,12 +130,7 @@ export const useVerifyBoardEffect = (): useVerifyBoardStateInterface => { return { verifyJira, isLoading, - fields, - updateField, - validateField, + fields: originalFields, resetFields, - isVerifyTimeOut, - isShowAlert, - setIsShowAlert, }; }; diff --git a/frontend/src/hooks/useVerifyPipelineToolEffect.ts b/frontend/src/hooks/useVerifyPipelineToolEffect.ts index f1301ab2a8..c07048512e 100644 --- a/frontend/src/hooks/useVerifyPipelineToolEffect.ts +++ b/frontend/src/hooks/useVerifyPipelineToolEffect.ts @@ -1,46 +1,70 @@ +import { PIPELINE_TOOL_ERROR_MESSAGE } from '@src/containers/ConfigStep/Form/literal'; +import { useDefaultValues } from '@src/containers/ConfigStep/Form/useDefaultValues'; import { initDeploymentFrequencySettings } from '@src/context/Metrics/metricsSlice'; -import { updatePipelineToolVerifyState } from '@src/context/config/configSlice'; +import { updateShouldGetPipelineConfig } from '@src/context/Metrics/metricsSlice'; import { pipelineToolClient } from '@src/clients/pipeline/PipelineToolClient'; +import { TPipelineToolFieldKeys } from '@src/containers/ConfigStep/Form/type'; import { IPipelineVerifyRequestDTO } from '@src/clients/pipeline/dto/request'; +import { IPipelineToolData } from '@src/containers/ConfigStep/Form/schema'; +import { updatePipelineTool } from '@src/context/config/configSlice'; import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; +import { useFormContext } from 'react-hook-form'; import { useAppDispatch } from '@src/hooks'; import { HttpStatusCode } from 'axios'; import { useState } from 'react'; +export enum FIELD_KEY { + TYPE = 0, + TOKEN = 1, +} +interface IField { + key: TPipelineToolFieldKeys; + label: string; +} + export const useVerifyPipelineToolEffect = () => { const [isLoading, setIsLoading] = useState(false); - const [verifiedError, setVerifiedError] = useState(''); const dispatch = useAppDispatch(); - const [isVerifyTimeOut, setIsVerifyTimeOut] = useState(false); - const [isShowAlert, setIsShowAlert] = useState(false); - const verifyPipelineTool = async (params: IPipelineVerifyRequestDTO): Promise => { + const { pipelineToolOriginal } = useDefaultValues(); + const fields: IField[] = [ + { key: 'type', label: 'Pipeline Tool' }, + { key: 'token', label: 'Token' }, + ]; + const { reset, setError, getValues } = useFormContext(); + const persistReduxData = (pipelineToolConfig: IPipelineToolData) => { + dispatch(updatePipelineTool(pipelineToolConfig)); + dispatch(updateShouldGetPipelineConfig(true)); + dispatch(initDeploymentFrequencySettings()); + }; + + const resetFields = () => { + reset(pipelineToolOriginal); + persistReduxData(pipelineToolOriginal); + }; + + const verifyPipelineTool = async (): Promise => { setIsLoading(true); - const response = await pipelineToolClient.verify(params); - setIsVerifyTimeOut(false); - setIsShowAlert(false); + const values = getValues() as IPipelineVerifyRequestDTO; + const response = await pipelineToolClient.verify(values); if (response.code === HttpStatusCode.NoContent) { - dispatch(updatePipelineToolVerifyState(true)); - dispatch(initDeploymentFrequencySettings()); + reset(pipelineToolOriginal, { keepValues: true }); + persistReduxData(values); } else if (response.code === AXIOS_REQUEST_ERROR_CODE.TIMEOUT) { - setIsVerifyTimeOut(true); - setIsShowAlert(true); + setError(fields[FIELD_KEY.TOKEN].key, { message: PIPELINE_TOOL_ERROR_MESSAGE.token.timeout }); + } else if (response.code === HttpStatusCode.Unauthorized) { + setError(fields[FIELD_KEY.TOKEN].key, { message: PIPELINE_TOOL_ERROR_MESSAGE.token.unauthorized }); + } else if (response.code === HttpStatusCode.Forbidden) { + setError(fields[FIELD_KEY.TOKEN].key, { message: PIPELINE_TOOL_ERROR_MESSAGE.token.forbidden }); } else { - setVerifiedError(response.errorTitle); + setError(fields[FIELD_KEY.TOKEN].key, { message: response.errorTitle }); } setIsLoading(false); }; - const clearVerifiedError = () => { - if (verifiedError) setVerifiedError(''); - }; - return { + fields, verifyPipelineTool, isLoading, - verifiedError, - clearVerifiedError, - isVerifyTimeOut, - isShowAlert, - setIsShowAlert, + resetFields, }; }; diff --git a/frontend/src/hooks/useVerifySourceControlTokenEffect.ts b/frontend/src/hooks/useVerifySourceControlTokenEffect.ts index 53729da0db..87bbbd13f6 100644 --- a/frontend/src/hooks/useVerifySourceControlTokenEffect.ts +++ b/frontend/src/hooks/useVerifySourceControlTokenEffect.ts @@ -1,46 +1,67 @@ +import { initDeploymentFrequencySettings, updateShouldGetPipelineConfig } from '@src/context/Metrics/metricsSlice'; +import { SOURCE_CONTROL_ERROR_MESSAGE } from '@src/containers/ConfigStep/Form/literal'; import { SourceControlVerifyRequestDTO } from '@src/clients/sourceControl/dto/request'; import { sourceControlClient } from '@src/clients/sourceControl/SourceControlClient'; -import { updateSourceControlVerifyState } from '@src/context/config/configSlice'; +import { useDefaultValues } from '@src/containers/ConfigStep/Form/useDefaultValues'; +import { TSourceControlFieldKeys } from '@src/containers/ConfigStep/Form/type'; +import { ISourceControlData } from '@src/containers/ConfigStep/Form/schema'; +import { updateSourceControl } from '@src/context/config/configSlice'; import { AXIOS_REQUEST_ERROR_CODE } from '@src/constants/resources'; import { useAppDispatch } from '@src/hooks/index'; -import { useCallback, useState } from 'react'; +import { useFormContext } from 'react-hook-form'; import { HttpStatusCode } from 'axios'; +import { useState } from 'react'; + +export enum FIELD_KEY { + TYPE = 0, + TOKEN = 1, +} + +interface IField { + key: TSourceControlFieldKeys; + label: string; +} export const useVerifySourceControlTokenEffect = () => { const dispatch = useAppDispatch(); const [isLoading, setIsLoading] = useState(false); - const [verifiedError, setVerifiedError] = useState(); - const [isVerifyTimeOut, setIsVerifyTimeOut] = useState(false); - const [isShowAlert, setIsShowAlert] = useState(false); - const verifyToken = async (params: SourceControlVerifyRequestDTO) => { + const fields: IField[] = [ + { key: 'type', label: 'Source Control' }, + { key: 'token', label: 'Token' }, + ]; + const { sourceControlOriginal } = useDefaultValues(); + const { reset, setError, getValues } = useFormContext(); + const persistReduxData = (sourceControlConfig: ISourceControlData) => { + dispatch(updateSourceControl(sourceControlConfig)); + dispatch(updateShouldGetPipelineConfig(true)); + dispatch(initDeploymentFrequencySettings()); + }; + const resetFields = () => { + reset(sourceControlOriginal); + }; + + const verifyToken = async () => { setIsLoading(true); - const response = await sourceControlClient.verifyToken(params); - setIsVerifyTimeOut(false); - setIsShowAlert(false); + const values = getValues() as SourceControlVerifyRequestDTO; + const response = await sourceControlClient.verifyToken(values); if (response.code === HttpStatusCode.NoContent) { - dispatch(updateSourceControlVerifyState(true)); + persistReduxData(values); + reset(sourceControlOriginal, { keepValues: true }); } else if (response.code === AXIOS_REQUEST_ERROR_CODE.TIMEOUT) { - setIsVerifyTimeOut(true); - setIsShowAlert(true); + setError(fields[FIELD_KEY.TOKEN].key, { message: SOURCE_CONTROL_ERROR_MESSAGE.token.timeout }); + } else if (response.code === HttpStatusCode.Unauthorized) { + setError(fields[FIELD_KEY.TOKEN].key, { message: SOURCE_CONTROL_ERROR_MESSAGE.token.unauthorized }); } else { - dispatch(updateSourceControlVerifyState(false)); - setVerifiedError(response.errorTitle); + setError(fields[FIELD_KEY.TOKEN].key, { message: response.errorTitle }); } setIsLoading(false); return response; }; - const clearVerifiedError = useCallback(() => { - setVerifiedError(''); - }, []); - return { verifyToken, isLoading, - verifiedError, - clearVerifiedError, - isVerifyTimeOut, - isShowAlert, - setIsShowAlert, + fields, + resetFields, }; }; diff --git a/frontend/src/theme.ts b/frontend/src/theme.ts index 1a7eb7e20e..ccf93ea355 100644 --- a/frontend/src/theme.ts +++ b/frontend/src/theme.ts @@ -59,6 +59,9 @@ declare module '@mui/material/styles' { borderColor: string; }; }; + downloadListLabel: { + backgroundColor: string; + }; }; } @@ -143,6 +146,9 @@ export const theme = createTheme({ borderColor: '#939DDA', }, }, + downloadListLabel: { + backgroundColor: '#4350AF1A', + }, }, typography: { button: { diff --git a/frontend/src/utils/util.ts b/frontend/src/utils/util.ts index 0d62c6c58e..23a47b04de 100644 --- a/frontend/src/utils/util.ts +++ b/frontend/src/utils/util.ts @@ -2,9 +2,11 @@ import { CYCLE_TIME_LIST, CYCLE_TIME_SETTINGS_TYPES, METRICS_CONSTANTS } from '@ import { CleanedBuildKiteEmoji, OriginBuildKiteEmoji } from '@src/constants/emojis/emoji'; import { ICycleTimeSetting, IPipelineConfig } from '@src/context/Metrics/metricsSlice'; import { ITargetFieldType } from '@src/components/Common/MultiAutoComplete/styles'; +import { IPipeline } from '@src/context/config/pipelineTool/verifyResponseSlice'; +import { includes, isEqual, sortBy, uniq, uniqBy } from 'lodash'; +import { DateRangeList } from '@src/context/config/configSlice'; +import { BoardInfoResponse } from '@src/hooks/useGetBoardInfo'; import { DATE_FORMAT_TEMPLATE } from '@src/constants/template'; -import { TDateRange } from '@src/context/config/configSlice'; -import { includes, isEqual, sortBy } from 'lodash'; import duration from 'dayjs/plugin/duration'; import dayjs from 'dayjs'; @@ -101,7 +103,7 @@ export const formatDateToTimestampString = (date: string) => { return dayjs(date).valueOf().toString(); }; -export const sortDateRanges = (dateRanges: TDateRange, descending = true) => { +export const sortDateRanges = (dateRanges: DateRangeList, descending = true) => { const result = [...dateRanges].sort((a, b) => { return dayjs(b.startDate as string).diff(dayjs(a.startDate as string)); }); @@ -141,10 +143,10 @@ export const onlyEmptyAndDoneState = (boardingMappingStates: string[]) => isEqual(boardingMappingStates, [METRICS_CONSTANTS.cycleTimeEmptyStr, METRICS_CONSTANTS.doneValue]) || isEqual(boardingMappingStates, [METRICS_CONSTANTS.doneValue, METRICS_CONSTANTS.cycleTimeEmptyStr]); -export function convertCycleTimeSettings( +export const convertCycleTimeSettings = ( cycleTimeSettingsType: CYCLE_TIME_SETTINGS_TYPES, cycleTimeSettings: ICycleTimeSetting[], -) { +) => { if (cycleTimeSettingsType === CYCLE_TIME_SETTINGS_TYPES.BY_COLUMN) { return ([...new Set(cycleTimeSettings.map(({ column }: ICycleTimeSetting) => column))] as string[]).map( (uniqueColumn) => ({ @@ -155,4 +157,43 @@ export function convertCycleTimeSettings( ); } return cycleTimeSettings?.map(({ status, value }: ICycleTimeSetting) => ({ [status]: value })); +}; + +export const updateResponseCrews = (organization: string, pipelineName: string, pipelineList: IPipeline[]) => { + return pipelineList.map((pipeline) => + pipeline.name === pipelineName && pipeline.orgName === organization + ? { + ...pipeline, + crews: [], + } + : pipeline, + ); +}; + +export const uniqPipelineListCrews = (pipelineList: IPipeline[]) => + uniq(pipelineList.flatMap(({ crews }) => crews)).filter((crew) => crew !== undefined); + +export function existBlockState(cycleTimeSettings: ICycleTimeSetting[]) { + return cycleTimeSettings.some(({ value }) => METRICS_CONSTANTS.blockValue === value); +} + +export function combineBoardInfo(boardInfoResponses: BoardInfoResponse[]) { + if (boardInfoResponses) { + const allUsers = [...new Set(boardInfoResponses.flatMap((result) => result.users))]; + const allTargetFields = uniqBy( + boardInfoResponses.flatMap((result) => result.targetFields), + (elem) => [elem.key, elem.name, elem.flag].join(), + ); + const allJiraColumns = boardInfoResponses[boardInfoResponses.length - 1].jiraColumns; + const allIgnoredTargetFields = uniqBy( + boardInfoResponses.flatMap((result) => result.ignoredTargetFields), + (elem) => [elem.key, elem.name, elem.flag].join(), + ); + return { + users: allUsers, + targetFields: allTargetFields, + ignoredTargetFields: allIgnoredTargetFields, + jiraColumns: allJiraColumns, + }; + } } diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 0a5bd5048b..2c55ace78a 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -23,6 +23,6 @@ "types": ["node", "vite-plugin-pwa/react", "jest"] }, "include": ["src", "__tests__", "e2e", "*.ts"], - "exclude": ["__tests__/__mocks__/svgTransformer.ts", "package.json", "pnpm-lock.yaml"], + "exclude": ["__tests__/__mocks__/svgTransformer.ts", "package.json", "pnpm-lock.yaml", "jest.polyfills.ts"], "references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.scripts.json" }] } diff --git a/ops/check.sh b/ops/check.sh index 4da9eb1c73..45455935f1 100755 --- a/ops/check.sh +++ b/ops/check.sh @@ -284,15 +284,39 @@ e2e_container_check() { } e2e_check() { - echo "start to run e2e" + local project="${E2E_PROJECT:-Google Chrome}" + echo "start to run e2e for project: ${project}" export TZ=Asia/Shanghai npm install -g pnpm cd frontend pnpm install --no-frozen-lockfile - pnpm exec playwright install - pnpm exec playwright install msedge - pnpm exec playwright install chrome - pnpm run e2e:ci + case "$project" in + "Google Chrome") + echo "Installing Chrome browser" + pnpm exec playwright install chrome + ;; + "Microsoft Edge") + echo "Installing Microsoft Edge browser" + pnpm exec playwright install msedge + ;; + "webkit") + echo "Installing WebKit browser" + pnpm exec playwright install webkit + ;; + "firefox") + echo "Installing WebKit browser" + pnpm exec playwright install firefox + ;; + "chromium") + echo "Installing WebKit browser" + pnpm exec playwright install chromium + ;; + *) + echo "No browser is found for $project type, install default browsers." + pnpm exec playwright install + ;; + esac + pnpm run e2e:ci --project="${project}" } buildkite_status_check() { diff --git a/ops/infra/Dockerfile.e2e b/ops/infra/Dockerfile.e2e index 2cd11b3c83..ab02a8cf1d 100644 --- a/ops/infra/Dockerfile.e2e +++ b/ops/infra/Dockerfile.e2e @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/playwright:latest +FROM jacoblincool/playwright:base COPY ./frontend /app ENV TZ=Asia/Shanghai diff --git a/ops/infra/cloudformation.yml b/ops/infra/cloudformation.yml index e5357992db..0baaaa2ba3 100644 --- a/ops/infra/cloudformation.yml +++ b/ops/infra/cloudformation.yml @@ -64,11 +64,8 @@ Resources: - Action: - ecr:GetAuthorizationToken - ecr:BatchCheckLayerAvailability - - ecr:GetDownloadUrlForLayer - - ecr:GetRepositoryPolicy - - ecr:DescribeRepositories - - ecr:ListImages - ecr:BatchGetImage + - ecr:GetDownloadUrlForLayer Effect: Allow Resource: "*" Version: "2012-10-17"