From 1555258b9ad18cda32cf209c8e13440cc026b065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BA=A7n=20V=C4=A9nh=20Thi=E1=BB=87n=20Ph=C3=BAc?= <143604440+tvtphuc-axonivy@users.noreply.github.com> Date: Wed, 29 May 2024 15:42:13 +0700 Subject: [PATCH 01/62] MARP-275 Create new BE project (#4) * MARP-275 Create new BE project * User MockitoJUnitRunner instead of SpringRunner * RHT-275 Handle For SonaQube * MARP-275 Add lombok.config --------- --- .github/workflows/ci-build.yml | 59 +++++++++++++++ .github/workflows/dev-build.yml | 5 +- .github/workflows/sonarqube.yml | 23 ------ .gitignore | 33 +++++++++ README.md | 35 +++++++++ lombok.config | 2 + pom.xml | 74 +++++++++++++++++++ .../market/MarketplaceServiceApplication.java | 13 ++++ .../axonivy/market/ServletInitializer.java | 13 ++++ .../market/config/HeaderInterceptor.java | 25 +++++++ .../axonivy/market/config/MongoConfig.java | 36 +++++++++ .../axonivy/market/config/OpenApiConfig.java | 26 +++++++ .../com/axonivy/market/config/WebConfig.java | 22 ++++++ .../market/constants/DocumentConstants.java | 5 ++ .../constants/ErrorMessageConstants.java | 5 ++ .../constants/RequestMappingConstants.java | 5 ++ .../market/controller/UserController.java | 28 +++++++ .../java/com/axonivy/market/entity/User.java | 21 ++++++ .../market/exceptions/ExceptionHandlers.java | 19 +++++ .../exceptions/MissingHeaderException.java | 12 +++ .../com/axonivy/market/model/ApiError.java | 15 ++++ .../market/repository/UserRepository.java | 9 +++ .../axonivy/market/service/UserService.java | 9 +++ .../market/service/impl/UserServiceImpl.java | 24 ++++++ src/main/resources/application.properties | 6 ++ .../market/service/UserServiceImplTest.java | 42 +++++++++++ 26 files changed, 539 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/ci-build.yml delete mode 100644 .github/workflows/sonarqube.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 lombok.config create mode 100644 pom.xml create mode 100644 src/main/java/com/axonivy/market/MarketplaceServiceApplication.java create mode 100644 src/main/java/com/axonivy/market/ServletInitializer.java create mode 100644 src/main/java/com/axonivy/market/config/HeaderInterceptor.java create mode 100644 src/main/java/com/axonivy/market/config/MongoConfig.java create mode 100644 src/main/java/com/axonivy/market/config/OpenApiConfig.java create mode 100644 src/main/java/com/axonivy/market/config/WebConfig.java create mode 100644 src/main/java/com/axonivy/market/constants/DocumentConstants.java create mode 100644 src/main/java/com/axonivy/market/constants/ErrorMessageConstants.java create mode 100644 src/main/java/com/axonivy/market/constants/RequestMappingConstants.java create mode 100644 src/main/java/com/axonivy/market/controller/UserController.java create mode 100644 src/main/java/com/axonivy/market/entity/User.java create mode 100644 src/main/java/com/axonivy/market/exceptions/ExceptionHandlers.java create mode 100644 src/main/java/com/axonivy/market/exceptions/MissingHeaderException.java create mode 100644 src/main/java/com/axonivy/market/model/ApiError.java create mode 100644 src/main/java/com/axonivy/market/repository/UserRepository.java create mode 100644 src/main/java/com/axonivy/market/service/UserService.java create mode 100644 src/main/java/com/axonivy/market/service/impl/UserServiceImpl.java create mode 100644 src/main/resources/application.properties create mode 100644 src/test/java/com/axonivy/market/service/UserServiceImplTest.java diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml new file mode 100644 index 000000000..04a0a85c7 --- /dev/null +++ b/.github/workflows/ci-build.yml @@ -0,0 +1,59 @@ +name: CI Build +run-name: Build on branch ${{github.ref_name}} triggered by ${{github.actor}} + +on: + push: + workflow_dispatch: + +jobs: + analysis: + name: Sonarqube analysis + runs-on: self-hosted + env: + SONAR_PROJECT_KEY: "AxonIvy-Market-Service" + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Build with Maven + run: mvn clean install + - uses: sonarsource/sonarqube-scan-action@master + env: + SONAR_TOKEN: ${{ env.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ env.SONAR_HOST_URL }} + with: + args: + -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }} + -Dsonar.java.binaries=target/classes + - name: SonarQube Quality Gate check + id: sonarqube-quality-gate-check + uses: sonarsource/sonarqube-quality-gate-action@master + timeout-minutes: 5 + env: + SONAR_TOKEN: ${{ env.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ env.SONAR_HOST_URL }} + with: + args: + -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }} + build: + name: Executes Tests + runs-on: self-hosted + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + - name: Tests with Maven + run: mvn -B test --file pom.xml diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index ca9c398ba..63418eb2b 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -8,7 +8,7 @@ on: jobs: build: - + name: Packge project and deploy to tomcat runs-on: self-hosted steps: @@ -22,6 +22,3 @@ jobs: - name: Build with Maven run: mvn -B package --file pom.xml - # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive - - name: Update dependency graph - uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml deleted file mode 100644 index 0c0d5b4e1..000000000 --- a/.github/workflows/sonarqube.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: SonarQube Build -run-name: Analyze and upload reports to Octopus-Sonar by ${{github.actor}} - -on: - push: - branches: [ "develop" ] - workflow_dispatch: - -jobs: - Analysis: - runs-on: self-hosted - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: sonarsource/sonarqube-scan-action@master - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} - with: - args: - -Dsonar.projectKey=AxonIvy-Market-Service diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..0384b84cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +/bin/ diff --git a/README.md b/README.md new file mode 100644 index 000000000..6682bcb6c --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# Getting Started + +### Reference Documentation +For further reference, please consider the following sections: + +* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) +* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.2.5/maven-plugin/reference/html/) +* [Spring Data MongoDB](https://docs.spring.io/spring-boot/docs/3.2.5/reference/htmlsingle/index.html#data.nosql.mongodb) +* [Spring Web](https://docs.spring.io/spring-boot/docs/3.2.5/reference/htmlsingle/index.html#web) + +### Guides +The following guides illustrate how to use some features concretely: + +* installing mongodb , and access it mongodb://localhost:27017/ in mongodb compass or studio3T +* run "mvn clean install" to build a project +* run "mvn test" to test all Test class + +### MongoDB's property configs +* We can set up properties in class application.properties and MongoConfig + +### Access Swagger URL: http://{your-host}/swagger-ui/index.html + +### Steps to set up: +* Installing mongodb, and access it as Url mongodb://localhost:27017/, and you can create and name whatever you want ,then you should put them to application.properties +* Run mvn clean install to build project +* Run mvn test to test all tests +* You can change the configuration in file “application.properties“ + +### In case of using eclipse you should install manually Lombok . +* Download lombok here Download +* run command java -jar lombok.jar and restart the eclipse then you can access file “eclipse.ini“ in eclipse folder where you install → there is a text like this: -javaagent:C:\Users\tvtphuc\eclipse\jee-2024-032\eclipse\lombok.jar → it means you are successful +* Import the project then in the eclipse , you should run the command “mvn clean install“ +* After that you go to class MarketplaceServiceApplication → right click to main method → click run as → choose Java Application +* Then you can send a request in postman :) +* If you want to run single test in class UserServiceImplTest. You can right-click to method testFindAllUser and right click → select Run as → choose JUnit Test \ No newline at end of file diff --git a/lombok.config b/lombok.config new file mode 100644 index 000000000..a23edb413 --- /dev/null +++ b/lombok.config @@ -0,0 +1,2 @@ +config.stopBubbling = true +lombok.addLombokGeneratedAnnotation = true \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..bf1326f7b --- /dev/null +++ b/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.5 + + + com.axonivy.market + marketplace-service + 0.0.1-SNAPSHOT + war + marketplace-service + marketplace-service + + 17 + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + org.springframework.boot + spring-boot-starter-web + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-tomcat + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.jupiter + junit-jupiter-engine + 5.9.2 + test + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.5.0 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/src/main/java/com/axonivy/market/MarketplaceServiceApplication.java b/src/main/java/com/axonivy/market/MarketplaceServiceApplication.java new file mode 100644 index 000000000..f390c8fa3 --- /dev/null +++ b/src/main/java/com/axonivy/market/MarketplaceServiceApplication.java @@ -0,0 +1,13 @@ +package com.axonivy.market; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MarketplaceServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(MarketplaceServiceApplication.class, args); + } + +} diff --git a/src/main/java/com/axonivy/market/ServletInitializer.java b/src/main/java/com/axonivy/market/ServletInitializer.java new file mode 100644 index 000000000..378959608 --- /dev/null +++ b/src/main/java/com/axonivy/market/ServletInitializer.java @@ -0,0 +1,13 @@ +package com.axonivy.market; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +public class ServletInitializer extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(MarketplaceServiceApplication.class); + } + +} diff --git a/src/main/java/com/axonivy/market/config/HeaderInterceptor.java b/src/main/java/com/axonivy/market/config/HeaderInterceptor.java new file mode 100644 index 000000000..56a845abb --- /dev/null +++ b/src/main/java/com/axonivy/market/config/HeaderInterceptor.java @@ -0,0 +1,25 @@ +package com.axonivy.market.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import com.axonivy.market.exceptions.MissingHeaderException; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Component +public class HeaderInterceptor implements HandlerInterceptor { + + @Value("${request.header}") + private String requestHeader; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if (!requestHeader.equals(request.getHeader("X-Requested-By"))) { + throw new MissingHeaderException(); + } + return true; + } +} diff --git a/src/main/java/com/axonivy/market/config/MongoConfig.java b/src/main/java/com/axonivy/market/config/MongoConfig.java new file mode 100644 index 000000000..36b7b6f1d --- /dev/null +++ b/src/main/java/com/axonivy/market/config/MongoConfig.java @@ -0,0 +1,36 @@ +package com.axonivy.market.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; + +@Configuration +@EnableMongoRepositories(basePackages = "com.axonivy.market.repository") +public class MongoConfig extends AbstractMongoClientConfiguration { + + @Value("${spring.data.mongodb.host}") + private String host; + + @Value("${spring.data.mongodb.database}") + private String databaseName; + + @Override + protected String getDatabaseName() { + return databaseName; + } + + @Override + public MongoClient mongoClient() { + ConnectionString connectionString = new ConnectionString(host); + MongoClientSettings mongoClientSettings = MongoClientSettings.builder().applyConnectionString(connectionString) + .build(); + + return MongoClients.create(mongoClientSettings); + } +} diff --git a/src/main/java/com/axonivy/market/config/OpenApiConfig.java b/src/main/java/com/axonivy/market/config/OpenApiConfig.java new file mode 100644 index 000000000..df02c02f3 --- /dev/null +++ b/src/main/java/com/axonivy/market/config/OpenApiConfig.java @@ -0,0 +1,26 @@ +package com.axonivy.market.config; + +import org.springdoc.core.customizers.OpenApiCustomizer; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.swagger.v3.oas.models.media.StringSchema; +import io.swagger.v3.oas.models.parameters.Parameter; + +@Configuration +public class OpenApiConfig { + @Bean + public GroupedOpenApi customHeaderOpenApi() { + return GroupedOpenApi.builder().group("default").addOpenApiCustomizer(customGlobalHeaders()).build(); + } + + private OpenApiCustomizer customGlobalHeaders() { + return openApi -> openApi.getPaths().values().forEach(pathItem -> pathItem.readOperations().forEach(operation -> { + Parameter headerParameter = new Parameter().in("header").schema(new StringSchema()).name("X-Requested-By") + .description("ivy").required(true); + + operation.addParametersItem(headerParameter); + })); + } +} diff --git a/src/main/java/com/axonivy/market/config/WebConfig.java b/src/main/java/com/axonivy/market/config/WebConfig.java new file mode 100644 index 000000000..f26adc247 --- /dev/null +++ b/src/main/java/com/axonivy/market/config/WebConfig.java @@ -0,0 +1,22 @@ +package com.axonivy.market.config; + +import static com.axonivy.market.constants.RequestMappingConstants.USER_MAPPING; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + private final HeaderInterceptor headerInterceptor; + + public WebConfig(HeaderInterceptor headerInterceptor) { + this.headerInterceptor = headerInterceptor; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(headerInterceptor).addPathPatterns(USER_MAPPING); + } +} diff --git a/src/main/java/com/axonivy/market/constants/DocumentConstants.java b/src/main/java/com/axonivy/market/constants/DocumentConstants.java new file mode 100644 index 000000000..ef95831f2 --- /dev/null +++ b/src/main/java/com/axonivy/market/constants/DocumentConstants.java @@ -0,0 +1,5 @@ +package com.axonivy.market.constants; + +public class DocumentConstants { + public static final String USER_DOCUMENT = "User"; +} diff --git a/src/main/java/com/axonivy/market/constants/ErrorMessageConstants.java b/src/main/java/com/axonivy/market/constants/ErrorMessageConstants.java new file mode 100644 index 000000000..7796172fd --- /dev/null +++ b/src/main/java/com/axonivy/market/constants/ErrorMessageConstants.java @@ -0,0 +1,5 @@ +package com.axonivy.market.constants; + +public class ErrorMessageConstants { + public static final String INVALID_MISSING_HEADER_ERROR_MESSAGE = "Invalid or missing header"; +} diff --git a/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java b/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java new file mode 100644 index 000000000..a0f9d15ac --- /dev/null +++ b/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java @@ -0,0 +1,5 @@ +package com.axonivy.market.constants; + +public class RequestMappingConstants { + public static final String USER_MAPPING = "/user"; +} diff --git a/src/main/java/com/axonivy/market/controller/UserController.java b/src/main/java/com/axonivy/market/controller/UserController.java new file mode 100644 index 000000000..f94d46920 --- /dev/null +++ b/src/main/java/com/axonivy/market/controller/UserController.java @@ -0,0 +1,28 @@ +package com.axonivy.market.controller; + +import static com.axonivy.market.constants.RequestMappingConstants.USER_MAPPING; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.axonivy.market.entity.User; +import com.axonivy.market.service.UserService; + +@RestController +@RequestMapping(USER_MAPPING) +public class UserController { + private final UserService userService; + + public UserController(UserService userService) { + this.userService = userService; + } + + @GetMapping + public ResponseEntity> getAllUser() { + return ResponseEntity.ok(userService.getAllUsers()); + } +} diff --git a/src/main/java/com/axonivy/market/entity/User.java b/src/main/java/com/axonivy/market/entity/User.java new file mode 100644 index 000000000..f6035ac00 --- /dev/null +++ b/src/main/java/com/axonivy/market/entity/User.java @@ -0,0 +1,21 @@ +package com.axonivy.market.entity; + +import static com.axonivy.market.constants.DocumentConstants.USER_DOCUMENT; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@Document(USER_DOCUMENT) +public class User { + @Id + private String id; + private String username; + private String password; +} diff --git a/src/main/java/com/axonivy/market/exceptions/ExceptionHandlers.java b/src/main/java/com/axonivy/market/exceptions/ExceptionHandlers.java new file mode 100644 index 000000000..7359bd18b --- /dev/null +++ b/src/main/java/com/axonivy/market/exceptions/ExceptionHandlers.java @@ -0,0 +1,19 @@ +package com.axonivy.market.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import com.axonivy.market.model.ApiError; + +@ControllerAdvice +public class ExceptionHandlers extends ResponseEntityExceptionHandler { + + @ExceptionHandler(MissingHeaderException.class) + protected ResponseEntity handleMissingServletRequestParameter(MissingHeaderException missingHeaderException) { + ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, missingHeaderException.getMessage()); + return new ResponseEntity<>(apiError, apiError.getStatus()); + } +} diff --git a/src/main/java/com/axonivy/market/exceptions/MissingHeaderException.java b/src/main/java/com/axonivy/market/exceptions/MissingHeaderException.java new file mode 100644 index 000000000..7c14e8380 --- /dev/null +++ b/src/main/java/com/axonivy/market/exceptions/MissingHeaderException.java @@ -0,0 +1,12 @@ +package com.axonivy.market.exceptions; + +import static com.axonivy.market.constants.ErrorMessageConstants.INVALID_MISSING_HEADER_ERROR_MESSAGE; + +public class MissingHeaderException extends Exception { + + private static final long serialVersionUID = 1L; + + public MissingHeaderException() { + super(INVALID_MISSING_HEADER_ERROR_MESSAGE); + } +} diff --git a/src/main/java/com/axonivy/market/model/ApiError.java b/src/main/java/com/axonivy/market/model/ApiError.java new file mode 100644 index 000000000..c07b0d1a8 --- /dev/null +++ b/src/main/java/com/axonivy/market/model/ApiError.java @@ -0,0 +1,15 @@ +package com.axonivy.market.model; + +import org.springframework.http.HttpStatus; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class ApiError { + private HttpStatus status; + private String message; +} diff --git a/src/main/java/com/axonivy/market/repository/UserRepository.java b/src/main/java/com/axonivy/market/repository/UserRepository.java new file mode 100644 index 000000000..6a011e9c6 --- /dev/null +++ b/src/main/java/com/axonivy/market/repository/UserRepository.java @@ -0,0 +1,9 @@ +package com.axonivy.market.repository; + +import com.axonivy.market.entity.User; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserRepository extends MongoRepository { +} diff --git a/src/main/java/com/axonivy/market/service/UserService.java b/src/main/java/com/axonivy/market/service/UserService.java new file mode 100644 index 000000000..99c08f031 --- /dev/null +++ b/src/main/java/com/axonivy/market/service/UserService.java @@ -0,0 +1,9 @@ +package com.axonivy.market.service; + +import java.util.List; + +import com.axonivy.market.entity.User; + +public interface UserService { + List getAllUsers(); +} diff --git a/src/main/java/com/axonivy/market/service/impl/UserServiceImpl.java b/src/main/java/com/axonivy/market/service/impl/UserServiceImpl.java new file mode 100644 index 000000000..74ac69382 --- /dev/null +++ b/src/main/java/com/axonivy/market/service/impl/UserServiceImpl.java @@ -0,0 +1,24 @@ +package com.axonivy.market.service.impl; + +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.axonivy.market.entity.User; +import com.axonivy.market.repository.UserRepository; +import com.axonivy.market.service.UserService; + +@Service +public class UserServiceImpl implements UserService { + + private final UserRepository userRepository; + + public UserServiceImpl(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public List getAllUsers() { + return userRepository.findAll(); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 000000000..adda83b24 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,6 @@ +spring.application.name=marketplace-service +spring.data.mongodb.host= mongodb://localhost:27017/ +spring.data.mongodb.database=marketWeb +server.port=8080 +logging.level.org.springframework.web=info +request.header=ivy \ No newline at end of file diff --git a/src/test/java/com/axonivy/market/service/UserServiceImplTest.java b/src/test/java/com/axonivy/market/service/UserServiceImplTest.java new file mode 100644 index 000000000..102c6f3c7 --- /dev/null +++ b/src/test/java/com/axonivy/market/service/UserServiceImplTest.java @@ -0,0 +1,42 @@ +package com.axonivy.market.service; + +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.axonivy.market.entity.User; +import com.axonivy.market.repository.UserRepository; +import com.axonivy.market.service.impl.UserServiceImpl; + +@ExtendWith(MockitoExtension.class) +public class UserServiceImplTest { + + @InjectMocks + private UserServiceImpl employeeService; + + @Mock + private UserRepository userRepository; + + @Test + public void testFindAllUser() { + // Mock data and service + User mockUser = new User(); + mockUser.setId("123"); + mockUser.setUsername("tvtTest"); + mockUser.setPassword("12345"); + List mockResultReturn = List.of(mockUser); + Mockito.when(userRepository.findAll()).thenReturn(mockResultReturn); + + // exercise + List result = employeeService.getAllUsers(); + + // Verify + Assertions.assertEquals(result, mockResultReturn); + } +} From 7c73ea1b5ade51a7f34cf94d706c302519fd5ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BA=A7n=20V=C4=A9nh=20Thi=E1=BB=87n=20Ph=C3=BAc?= Date: Thu, 30 May 2024 14:45:21 +0700 Subject: [PATCH 02/62] MARP-275 update readme and log level --- README.md | 5 +++-- .../com/axonivy/market/config/OpenApiConfig.java | 13 ++++++++----- src/main/resources/application.properties | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6682bcb6c..68876d06f 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,9 @@ The following guides illustrate how to use some features concretely: * You can change the configuration in file “application.properties“ ### In case of using eclipse you should install manually Lombok . -* Download lombok here Download -* run command java -jar lombok.jar and restart the eclipse then you can access file “eclipse.ini“ in eclipse folder where you install → there is a text like this: -javaagent:C:\Users\tvtphuc\eclipse\jee-2024-032\eclipse\lombok.jar → it means you are successful +* Download lombok here https://projectlombok.org/download +* run command "java -jar lombok.jar" then you can access file “eclipse.ini“ in eclipse folder where you install → there is a text like this: -javaagent:C:\Users\tvtphuc\eclipse\jee-2024-032\eclipse\lombok.jar → it means you are successful +* Start eclipse * Import the project then in the eclipse , you should run the command “mvn clean install“ * After that you go to class MarketplaceServiceApplication → right click to main method → click run as → choose Java Application * Then you can send a request in postman :) diff --git a/src/main/java/com/axonivy/market/config/OpenApiConfig.java b/src/main/java/com/axonivy/market/config/OpenApiConfig.java index df02c02f3..3c175c1c8 100644 --- a/src/main/java/com/axonivy/market/config/OpenApiConfig.java +++ b/src/main/java/com/axonivy/market/config/OpenApiConfig.java @@ -5,6 +5,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.media.StringSchema; import io.swagger.v3.oas.models.parameters.Parameter; @@ -16,11 +17,13 @@ public GroupedOpenApi customHeaderOpenApi() { } private OpenApiCustomizer customGlobalHeaders() { - return openApi -> openApi.getPaths().values().forEach(pathItem -> pathItem.readOperations().forEach(operation -> { - Parameter headerParameter = new Parameter().in("header").schema(new StringSchema()).name("X-Requested-By") - .description("ivy").required(true); + return openApi -> openApi.getPaths().values().forEach(pathItem -> { + for (Operation operation : pathItem.readOperations()) { + Parameter headerParameter = new Parameter().in("header").schema(new StringSchema()).name("X-Requested-By") + .description("ivy").required(true); - operation.addParametersItem(headerParameter); - })); + operation.addParametersItem(headerParameter); + } + }); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index adda83b24..05bc0242f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,5 +2,5 @@ spring.application.name=marketplace-service spring.data.mongodb.host= mongodb://localhost:27017/ spring.data.mongodb.database=marketWeb server.port=8080 -logging.level.org.springframework.web=info +logging.level.org.springframework.web=warn request.header=ivy \ No newline at end of file From e8bc03725e0e9592c5857cd57b0a908eea302577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BA=A7n=20V=C4=A9nh=20Thi=E1=BB=87n=20Ph=C3=BAc?= Date: Thu, 30 May 2024 14:52:12 +0700 Subject: [PATCH 03/62] MARP-275 add specific type for pathItem --- src/main/java/com/axonivy/market/config/OpenApiConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/axonivy/market/config/OpenApiConfig.java b/src/main/java/com/axonivy/market/config/OpenApiConfig.java index 3c175c1c8..be0f4cced 100644 --- a/src/main/java/com/axonivy/market/config/OpenApiConfig.java +++ b/src/main/java/com/axonivy/market/config/OpenApiConfig.java @@ -6,6 +6,7 @@ import org.springframework.context.annotation.Configuration; import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; import io.swagger.v3.oas.models.media.StringSchema; import io.swagger.v3.oas.models.parameters.Parameter; @@ -17,7 +18,7 @@ public GroupedOpenApi customHeaderOpenApi() { } private OpenApiCustomizer customGlobalHeaders() { - return openApi -> openApi.getPaths().values().forEach(pathItem -> { + return openApi -> openApi.getPaths().values().forEach((PathItem pathItem) -> { for (Operation operation : pathItem.readOperations()) { Parameter headerParameter = new Parameter().in("header").schema(new StringSchema()).name("X-Requested-By") .description("ivy").required(true); From 7c43b6bf9488a992539131fdb6a544a4c271651c Mon Sep 17 00:00:00 2001 From: Hoan Nguyen <83745591+nqhoan-axonivy@users.noreply.github.com> Date: Fri, 31 May 2024 08:58:09 +0700 Subject: [PATCH 04/62] Feature/MARP-277 setup infrastructure for our dev server (#6) * Update mongoDB * Added cros feature * Add forward * Handle feedback --- .github/workflows/ci-build.yml | 6 +++--- .github/workflows/dev-build.yml | 18 ++++++++++++++++-- .../com/axonivy/market/config/WebConfig.java | 9 +++++++++ src/main/resources/application.properties | 7 ++++--- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 04a0a85c7..4068d13e9 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -10,7 +10,7 @@ jobs: name: Sonarqube analysis runs-on: self-hosted env: - SONAR_PROJECT_KEY: "AxonIvy-Market-Service" + SONAR_PROJECT_KEY: ${{ secrets.SONAR_PROJECT_KEY }} SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} @@ -23,8 +23,8 @@ jobs: with: java-version: '17' distribution: 'temurin' - - name: Build with Maven - run: mvn clean install + - name: Build and test with Maven + run: mvn clean test - uses: sonarsource/sonarqube-scan-action@master env: SONAR_TOKEN: ${{ env.SONAR_TOKEN }} diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index 63418eb2b..db32f52fe 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -19,6 +19,20 @@ jobs: java-version: '17' distribution: 'temurin' cache: maven + - name: Update MongoDB in application.properties + env: + MONGODB_HOST: ${{ secrets.MONGODB_HOST }} + MONGODB_DATABASE: ${{ secrets.MONGODB_DATABASE }} + run: | + sed -i "s/^spring.data.mongodb.host=.*$/spring.data.mongodb.host=$MONGODB_HOST/" src/main/resources/application.properties + sed -i "s/^spring.data.mongodb.database=.*$/spring.data.mongodb.database=$MONGODB_DATABASE/" src/main/resources/application.properties - name: Build with Maven - run: mvn -B package --file pom.xml - + run: mvn clean package -DskipTests + - name: Prepare deployment directory + run: mkdir -p deployment && cp target/*.war deployment/ + - name: Copy WAR to Tomcat server + run: sudo cp deployment/*.war /opt/tomcat/webapps/marketplace-server.war + - name: Restart Tomcat server + run: | + sudo systemctl stop tomcat + sudo systemctl start tomcat diff --git a/src/main/java/com/axonivy/market/config/WebConfig.java b/src/main/java/com/axonivy/market/config/WebConfig.java index f26adc247..3ac974686 100644 --- a/src/main/java/com/axonivy/market/config/WebConfig.java +++ b/src/main/java/com/axonivy/market/config/WebConfig.java @@ -3,6 +3,7 @@ import static com.axonivy.market.constants.RequestMappingConstants.USER_MAPPING; import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -19,4 +20,12 @@ public WebConfig(HeaderInterceptor headerInterceptor) { public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(headerInterceptor).addPathPatterns(USER_MAPPING); } + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("*") + .allowedMethods("GET", "POST", "PUT", "DELETE") + .allowedHeaders("*"); + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 05bc0242f..bf73652c4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,7 @@ spring.application.name=marketplace-service -spring.data.mongodb.host= mongodb://localhost:27017/ -spring.data.mongodb.database=marketWeb +spring.data.mongodb.host=mongodb://localhost:27017/ +spring.data.mongodb.database=marketplace server.port=8080 logging.level.org.springframework.web=warn -request.header=ivy \ No newline at end of file +request.header=ivy +server.forward-headers-strategy=framework \ No newline at end of file From a98c65f59b28e883fe32ef5c3375be25e4c49921 Mon Sep 17 00:00:00 2001 From: Hoan Nguyen <83745591+nqhoan-axonivy@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:39:01 +0700 Subject: [PATCH 05/62] MARP-277 sonar for spring boot (#12) * Add sonar profile to pom * Add sonar dependency * Fix jacoco report * Add sonar property --- .github/workflows/ci-build.yml | 59 +++-- LICENSE | 201 ++++++++++++++++++ SECURITY.md | 25 +++ pom.xml | 170 +++++++++------ sonar-project.properties | 5 + .../market/constants/DocumentConstants.java | 4 + .../constants/ErrorMessageConstants.java | 4 + .../constants/RequestMappingConstants.java | 4 + .../market/service/impl/UserServiceImpl.java | 1 + 9 files changed, 369 insertions(+), 104 deletions(-) create mode 100644 LICENSE create mode 100644 SECURITY.md create mode 100644 sonar-project.properties diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 4068d13e9..86a8ff9ed 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -6,54 +6,45 @@ on: workflow_dispatch: jobs: + build: + name: Executes Tests + runs-on: self-hosted + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + - name: Tests with Maven + run: mvn clean install analysis: name: Sonarqube analysis + needs: build runs-on: self-hosted env: - SONAR_PROJECT_KEY: ${{ secrets.SONAR_PROJECT_KEY }} SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - + SONAR_PROJECT_KEY : ${{ secrets.SONAR_PROJECT_KEY }} steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' - - name: Build and test with Maven - run: mvn clean test - - uses: sonarsource/sonarqube-scan-action@master - env: - SONAR_TOKEN: ${{ env.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ env.SONAR_HOST_URL }} - with: - args: - -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }} - -Dsonar.java.binaries=target/classes + - name: Run SonarQube Scanner + run: | + mvn -B verify sonar:sonar \ + -Dsonar.host.url=${{ env.SONAR_HOST_URL }} \ + -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }} \ + -Dsonar.projectName="AxonIvy Market Service" \ + -Dsonar.token=${{ env.SONAR_TOKEN }} \ - name: SonarQube Quality Gate check id: sonarqube-quality-gate-check uses: sonarsource/sonarqube-quality-gate-action@master timeout-minutes: 5 - env: - SONAR_TOKEN: ${{ env.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ env.SONAR_HOST_URL }} with: - args: - -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }} - build: - name: Executes Tests - runs-on: self-hosted - - steps: - - uses: actions/checkout@v4 - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'temurin' - cache: maven - - name: Tests with Maven - run: mvn -B test --file pom.xml + scanMetadataReportFile: target/sonar/report-task.txt + args: -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }} diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..1d4c06f71 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,25 @@ +## Reporting a Vulnerability + +At Axon Ivy, we take security seriously. If you believe you've found a security vulnerability in our software, we encourage you to let us know right away. We investigate all reported vulnerabilities promptly. + +To report a vulnerability, please send an email to [security@axonivy.com](mailto:security@axonivy.com) with the following information: + +- Description of the vulnerability +- Steps to reproduce the vulnerability +- Any additional information or context that may be helpful + +Please refrain from publicly disclosing the vulnerability until it has been addressed by our team. + +## Response Time + +We strive to respond to security vulnerability reports as quickly as possible. Upon receiving your report, we will acknowledge it within 72 hours and we will release a patch as soon as possible depending on complexity, but historically within a few days. +Please report (suspected) security vulnerabilities at https://support.axonivy.com/. + + +## Responsible Disclosure + +We encourage responsible disclosure of security vulnerabilities. We believe that working together with security researchers and the broader community helps us improve the security of our software for everyone. + +## Contact + +For any questions or concerns regarding security, please contact us at [security@axonivy.com](mailto:security@axonivy.com). diff --git a/pom.xml b/pom.xml index bf1326f7b..e35317a0e 100644 --- a/pom.xml +++ b/pom.xml @@ -1,74 +1,104 @@ - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.2.5 - - - com.axonivy.market - marketplace-service - 0.0.1-SNAPSHOT - war - marketplace-service - marketplace-service - - 17 - - - - org.springframework.boot - spring-boot-starter-data-mongodb - - - org.springframework.boot - spring-boot-starter-web - - - org.projectlombok - lombok - true - - - org.springframework.boot - spring-boot-starter-tomcat - provided - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.jupiter - junit-jupiter-engine - 5.9.2 - test - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.5.0 - - + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.5 + + + com.axonivy.market + marketplace-service + 0.0.1-SNAPSHOT + war + marketplace-service + marketplace-service + + 17 + - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - - - + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + org.springframework.boot + spring-boot-starter-web + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-tomcat + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.jupiter + junit-jupiter-engine + 5.9.2 + test + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.5.0 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + org.sonarsource.scanner.maven + sonar-maven-plugin + 4.0.0.4121 + + + org.jacoco + jacoco-maven-plugin + 0.8.11 + + + + prepare-agent + + + + report + verify + + report + + + + + + diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 000000000..950647ff3 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,5 @@ +sonar.sources=src/main/java +sonar.tests=src/test/java +sonar.java.binaries=target/classes +sonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml +sonar.exclusions=**/test/**,**/tests/**,**/src/test/**,**/src/**/test/** \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/constants/DocumentConstants.java b/src/main/java/com/axonivy/market/constants/DocumentConstants.java index ef95831f2..be5449ceb 100644 --- a/src/main/java/com/axonivy/market/constants/DocumentConstants.java +++ b/src/main/java/com/axonivy/market/constants/DocumentConstants.java @@ -1,5 +1,9 @@ package com.axonivy.market.constants; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class DocumentConstants { public static final String USER_DOCUMENT = "User"; } diff --git a/src/main/java/com/axonivy/market/constants/ErrorMessageConstants.java b/src/main/java/com/axonivy/market/constants/ErrorMessageConstants.java index 7796172fd..4ad9f831a 100644 --- a/src/main/java/com/axonivy/market/constants/ErrorMessageConstants.java +++ b/src/main/java/com/axonivy/market/constants/ErrorMessageConstants.java @@ -1,5 +1,9 @@ package com.axonivy.market.constants; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class ErrorMessageConstants { public static final String INVALID_MISSING_HEADER_ERROR_MESSAGE = "Invalid or missing header"; } diff --git a/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java b/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java index a0f9d15ac..1293eaba4 100644 --- a/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java +++ b/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java @@ -1,5 +1,9 @@ package com.axonivy.market.constants; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class RequestMappingConstants { public static final String USER_MAPPING = "/user"; } diff --git a/src/main/java/com/axonivy/market/service/impl/UserServiceImpl.java b/src/main/java/com/axonivy/market/service/impl/UserServiceImpl.java index 74ac69382..dc9990949 100644 --- a/src/main/java/com/axonivy/market/service/impl/UserServiceImpl.java +++ b/src/main/java/com/axonivy/market/service/impl/UserServiceImpl.java @@ -21,4 +21,5 @@ public UserServiceImpl(UserRepository userRepository) { public List getAllUsers() { return userRepository.findAll(); } + } From 81dbbc7085cffb6e4d0eb83ec9b77014931310f7 Mon Sep 17 00:00:00 2001 From: Hoan Nguyen <83745591+nqhoan-axonivy@users.noreply.github.com> Date: Mon, 24 Jun 2024 17:41:48 +0700 Subject: [PATCH 06/62] MARP-394 MP APIs to fetch all artifacts and search (#15) * Update dependence * Update git ignore * Use repsentation model * Update paging and message * Update common GitHub Service * Unify codes * Add OPTIONS to check * Introduce test * Fix sonar --- .gitignore | 3 + pom.xml | 10 + sonar-project.properties | 3 +- .../market/MarketplaceServiceApplication.java | 37 +++ .../assembler/ProductModelAssembler.java | 38 +++ .../config/MarketApiDocumentConfig.java | 40 +++ ...ptor.java => MarketHeaderInterceptor.java} | 11 +- .../axonivy/market/config/OpenApiConfig.java | 30 -- .../com/axonivy/market/config/WebConfig.java | 27 +- .../market/constants/CommonConstants.java | 14 + .../market/constants/DocumentConstants.java | 9 - .../market/constants/EntityConstants.java | 11 + .../market/constants/GitHubConstants.java | 11 + .../constants/RequestMappingConstants.java | 6 + .../market/controller/AppController.java | 43 +++ .../market/controller/ProductController.java | 80 +++++ .../controller/ProductDetailsController.java | 22 ++ .../axonivy/market/entity/GitHubRepoMeta.java | 20 ++ .../com/axonivy/market/entity/Product.java | 67 +++++ .../java/com/axonivy/market/entity/User.java | 4 +- .../com/axonivy/market/enums/ErrorCode.java | 26 ++ .../com/axonivy/market/enums/FileStatus.java | 25 ++ .../com/axonivy/market/enums/FileType.java | 25 ++ .../com/axonivy/market/enums/SortOption.java | 28 ++ .../com/axonivy/market/enums/TypeOption.java | 30 ++ .../market/exceptions/ExceptionHandlers.java | 28 +- .../model/InvalidParamException.java | 24 ++ .../{ => model}/MissingHeaderException.java | 2 +- .../exceptions/model/NotFoundException.java | 30 ++ .../market/factory/ProductFactory.java | 93 ++++++ .../market/github/model/GitHubFile.java | 22 ++ .../market/github/model/MavenArtifact.java | 23 ++ .../com/axonivy/market/github/model/Meta.java | 37 +++ .../service/GHAxonIvyMarketRepoService.java | 21 ++ .../service/GHAxonIvyProductRepoService.java | 14 + .../market/github/service/GitHubService.java | 22 ++ .../impl/GHAxonIvyMarketRepoServiceImpl.java | 139 +++++++++ .../impl/GHAxonIvyProductRepoServiceImpl.java | 49 ++++ .../service/impl/GitHubServiceImpl.java | 52 ++++ .../market/github/util/GitHubUtils.java | 49 ++++ .../com/axonivy/market/model/ApiError.java | 15 - .../com/axonivy/market/model/Message.java | 16 + .../axonivy/market/model/ProductModel.java | 42 +++ .../repository/GitHubRepoMetaRepository.java | 10 + .../market/repository/ProductRepository.java | 26 ++ .../market/schedulingtask/ScheduledTasks.java | 28 ++ .../market/service/ProductService.java | 12 + .../service/impl/ProductServiceImpl.java | 250 ++++++++++++++++ src/main/resources/application.properties | 6 +- src/main/resources/github.token | 1 + .../market/controller/AppControllerTest.java | 26 ++ .../controller/ProductControllerTest.java | 105 +++++++ .../ProductDetailsControllerTest.java | 23 ++ .../market/controller/UserControllerTest.java | 27 ++ .../market/factory/ProductFactoryTest.java | 50 ++++ .../market/handler/ExceptionHandlersTest.java | 57 ++++ .../GHAxonIvyMarketRepoServiceImplTest.java | 115 ++++++++ .../GHAxonIvyProductRepoServiceImplTest.java | 65 +++++ .../market/service/GitHubServiceImplTest.java | 56 ++++ .../service/ProductServiceImplTest.java | 276 ++++++++++++++++++ .../market/service/SchedulingTasksTest.java | 26 ++ .../market/service/UserServiceImplTest.java | 4 +- src/test/resources/meta.json | 24 ++ 63 files changed, 2409 insertions(+), 76 deletions(-) create mode 100644 src/main/java/com/axonivy/market/assembler/ProductModelAssembler.java create mode 100644 src/main/java/com/axonivy/market/config/MarketApiDocumentConfig.java rename src/main/java/com/axonivy/market/config/{HeaderInterceptor.java => MarketHeaderInterceptor.java} (57%) delete mode 100644 src/main/java/com/axonivy/market/config/OpenApiConfig.java create mode 100644 src/main/java/com/axonivy/market/constants/CommonConstants.java delete mode 100644 src/main/java/com/axonivy/market/constants/DocumentConstants.java create mode 100644 src/main/java/com/axonivy/market/constants/EntityConstants.java create mode 100644 src/main/java/com/axonivy/market/constants/GitHubConstants.java create mode 100644 src/main/java/com/axonivy/market/controller/AppController.java create mode 100644 src/main/java/com/axonivy/market/controller/ProductController.java create mode 100644 src/main/java/com/axonivy/market/controller/ProductDetailsController.java create mode 100644 src/main/java/com/axonivy/market/entity/GitHubRepoMeta.java create mode 100644 src/main/java/com/axonivy/market/entity/Product.java create mode 100644 src/main/java/com/axonivy/market/enums/ErrorCode.java create mode 100644 src/main/java/com/axonivy/market/enums/FileStatus.java create mode 100644 src/main/java/com/axonivy/market/enums/FileType.java create mode 100644 src/main/java/com/axonivy/market/enums/SortOption.java create mode 100644 src/main/java/com/axonivy/market/enums/TypeOption.java create mode 100644 src/main/java/com/axonivy/market/exceptions/model/InvalidParamException.java rename src/main/java/com/axonivy/market/exceptions/{ => model}/MissingHeaderException.java (87%) create mode 100644 src/main/java/com/axonivy/market/exceptions/model/NotFoundException.java create mode 100644 src/main/java/com/axonivy/market/factory/ProductFactory.java create mode 100644 src/main/java/com/axonivy/market/github/model/GitHubFile.java create mode 100644 src/main/java/com/axonivy/market/github/model/MavenArtifact.java create mode 100644 src/main/java/com/axonivy/market/github/model/Meta.java create mode 100644 src/main/java/com/axonivy/market/github/service/GHAxonIvyMarketRepoService.java create mode 100644 src/main/java/com/axonivy/market/github/service/GHAxonIvyProductRepoService.java create mode 100644 src/main/java/com/axonivy/market/github/service/GitHubService.java create mode 100644 src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java create mode 100644 src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java create mode 100644 src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java create mode 100644 src/main/java/com/axonivy/market/github/util/GitHubUtils.java delete mode 100644 src/main/java/com/axonivy/market/model/ApiError.java create mode 100644 src/main/java/com/axonivy/market/model/Message.java create mode 100644 src/main/java/com/axonivy/market/model/ProductModel.java create mode 100644 src/main/java/com/axonivy/market/repository/GitHubRepoMetaRepository.java create mode 100644 src/main/java/com/axonivy/market/repository/ProductRepository.java create mode 100644 src/main/java/com/axonivy/market/schedulingtask/ScheduledTasks.java create mode 100644 src/main/java/com/axonivy/market/service/ProductService.java create mode 100644 src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java create mode 100644 src/main/resources/github.token create mode 100644 src/test/java/com/axonivy/market/controller/AppControllerTest.java create mode 100644 src/test/java/com/axonivy/market/controller/ProductControllerTest.java create mode 100644 src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java create mode 100644 src/test/java/com/axonivy/market/controller/UserControllerTest.java create mode 100644 src/test/java/com/axonivy/market/factory/ProductFactoryTest.java create mode 100644 src/test/java/com/axonivy/market/handler/ExceptionHandlersTest.java create mode 100644 src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java create mode 100644 src/test/java/com/axonivy/market/service/GHAxonIvyProductRepoServiceImplTest.java create mode 100644 src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java create mode 100644 src/test/java/com/axonivy/market/service/ProductServiceImplTest.java create mode 100644 src/test/java/com/axonivy/market/service/SchedulingTasksTest.java create mode 100644 src/test/resources/meta.json diff --git a/.gitignore b/.gitignore index 0384b84cd..3ccbd3e32 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,6 @@ build/ ### VS Code ### .vscode/ /bin/ + +### Token +*.token diff --git a/pom.xml b/pom.xml index e35317a0e..947e700d7 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,16 @@ springdoc-openapi-starter-webmvc-ui 2.5.0 + + org.springframework.hateoas + spring-hateoas + 2.3.0 + + + org.kohsuke + github-api + 1.321 + diff --git a/sonar-project.properties b/sonar-project.properties index 950647ff3..6e8a7ea99 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,4 +2,5 @@ sonar.sources=src/main/java sonar.tests=src/test/java sonar.java.binaries=target/classes sonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml -sonar.exclusions=**/test/**,**/tests/**,**/src/test/**,**/src/**/test/** \ No newline at end of file +sonar.exclusions=src/test/java/** +sonar.coverage.exclusions=src/test/java/**, src/main/java/**/config/**, src/main/java/**/constants/**, pom.xml, src/main/**/model/** \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/MarketplaceServiceApplication.java b/src/main/java/com/axonivy/market/MarketplaceServiceApplication.java index f390c8fa3..06660037c 100644 --- a/src/main/java/com/axonivy/market/MarketplaceServiceApplication.java +++ b/src/main/java/com/axonivy/market/MarketplaceServiceApplication.java @@ -1,13 +1,50 @@ package com.axonivy.market; +import org.apache.commons.lang3.time.StopWatch; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import com.axonivy.market.service.ProductService; + +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@EnableAsync +@EnableScheduling @SpringBootApplication public class MarketplaceServiceApplication { + private ProductService productService; + + public MarketplaceServiceApplication(ProductService productService) { + this.productService = productService; + } + public static void main(String[] args) { SpringApplication.run(MarketplaceServiceApplication.class, args); } + @Async + @EventListener(ApplicationStartedEvent.class) + public void startInitializeSystem() { + syncProductData(); + } + + private void syncProductData() { + var watch = new StopWatch(); + log.warn("Synchronizing Market repo: Started synchronizing data for Axon Ivy Market repo"); + watch.start(); + if (productService.syncLatestDataFromMarketRepo()) { + log.warn("Synchronizing Market repo: Data is already up to date"); + } else { + watch.stop(); + log.warn("Synchronizing Market repo: Finished synchronizing data for Axon Ivy Market repo in [{}] milliseconds", + watch.getTime()); + } + } } diff --git a/src/main/java/com/axonivy/market/assembler/ProductModelAssembler.java b/src/main/java/com/axonivy/market/assembler/ProductModelAssembler.java new file mode 100644 index 000000000..6b36a9d07 --- /dev/null +++ b/src/main/java/com/axonivy/market/assembler/ProductModelAssembler.java @@ -0,0 +1,38 @@ +package com.axonivy.market.assembler; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + +import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport; +import org.springframework.stereotype.Component; + +import com.axonivy.market.controller.ProductDetailsController; +import com.axonivy.market.entity.Product; +import com.axonivy.market.model.ProductModel; + +@Component +public class ProductModelAssembler extends RepresentationModelAssemblerSupport { + + public ProductModelAssembler() { + super(ProductDetailsController.class, ProductModel.class); + } + + @Override + public ProductModel toModel(Product product) { + ProductModel resource = new ProductModel(); + resource.add(linkTo(methodOn(ProductDetailsController.class).findProduct(product.getId(), product.getType())) + .withSelfRel()); + return createResource(resource, product); + } + + private ProductModel createResource(ProductModel model, Product product) { + model.setId(product.getId()); + model.setName(product.getName()); + model.setShortDescription(product.getShortDescription()); + model.setType(product.getType()); + model.setTags(product.getTags()); + model.setLogoUrl(product.getLogoUrl()); + return model; + } + +} diff --git a/src/main/java/com/axonivy/market/config/MarketApiDocumentConfig.java b/src/main/java/com/axonivy/market/config/MarketApiDocumentConfig.java new file mode 100644 index 000000000..a92282a64 --- /dev/null +++ b/src/main/java/com/axonivy/market/config/MarketApiDocumentConfig.java @@ -0,0 +1,40 @@ +package com.axonivy.market.config; + +import org.springdoc.core.customizers.OpenApiCustomizer; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.media.StringSchema; +import io.swagger.v3.oas.models.parameters.Parameter; +import static com.axonivy.market.constants.CommonConstants.*; + +@Configuration +public class MarketApiDocumentConfig { + private static final String DEFAULT_DOC_GROUP = "api"; + private static final String PATH_PATTERN = "/api/**"; + private static final String DEFAULT_PARAM = "ivy"; + private static final String HEADER_PARAM = "header"; + + @Bean + public GroupedOpenApi buildMarketCustomHeader() { + return GroupedOpenApi.builder() + .group(DEFAULT_DOC_GROUP) + .addOpenApiCustomizer(customMarketHeaders()) + .pathsToMatch(PATH_PATTERN) + .build(); + } + + private OpenApiCustomizer customMarketHeaders() { + return openApi -> openApi.getPaths().values().forEach((PathItem pathItem) -> { + for (Operation operation : pathItem.readOperations()) { + Parameter headerParameter = new Parameter().in(HEADER_PARAM) + .schema(new StringSchema()).name(REQUESTED_BY) + .description(DEFAULT_PARAM).required(true); + operation.addParametersItem(headerParameter); + } + }); + } +} diff --git a/src/main/java/com/axonivy/market/config/HeaderInterceptor.java b/src/main/java/com/axonivy/market/config/MarketHeaderInterceptor.java similarity index 57% rename from src/main/java/com/axonivy/market/config/HeaderInterceptor.java rename to src/main/java/com/axonivy/market/config/MarketHeaderInterceptor.java index 56a845abb..1d35cb9cc 100644 --- a/src/main/java/com/axonivy/market/config/HeaderInterceptor.java +++ b/src/main/java/com/axonivy/market/config/MarketHeaderInterceptor.java @@ -4,20 +4,25 @@ import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; -import com.axonivy.market.exceptions.MissingHeaderException; +import com.axonivy.market.constants.CommonConstants; +import com.axonivy.market.exceptions.model.MissingHeaderException; +import io.swagger.v3.oas.models.PathItem.HttpMethod; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @Component -public class HeaderInterceptor implements HandlerInterceptor { +public class MarketHeaderInterceptor implements HandlerInterceptor { @Value("${request.header}") private String requestHeader; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - if (!requestHeader.equals(request.getHeader("X-Requested-By"))) { + if (HttpMethod.OPTIONS.name().equalsIgnoreCase(request.getMethod())) { + return true; + } + if (!requestHeader.equals(request.getHeader(CommonConstants.REQUESTED_BY))) { throw new MissingHeaderException(); } return true; diff --git a/src/main/java/com/axonivy/market/config/OpenApiConfig.java b/src/main/java/com/axonivy/market/config/OpenApiConfig.java deleted file mode 100644 index be0f4cced..000000000 --- a/src/main/java/com/axonivy/market/config/OpenApiConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.axonivy.market.config; - -import org.springdoc.core.customizers.OpenApiCustomizer; -import org.springdoc.core.models.GroupedOpenApi; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import io.swagger.v3.oas.models.Operation; -import io.swagger.v3.oas.models.PathItem; -import io.swagger.v3.oas.models.media.StringSchema; -import io.swagger.v3.oas.models.parameters.Parameter; - -@Configuration -public class OpenApiConfig { - @Bean - public GroupedOpenApi customHeaderOpenApi() { - return GroupedOpenApi.builder().group("default").addOpenApiCustomizer(customGlobalHeaders()).build(); - } - - private OpenApiCustomizer customGlobalHeaders() { - return openApi -> openApi.getPaths().values().forEach((PathItem pathItem) -> { - for (Operation operation : pathItem.readOperations()) { - Parameter headerParameter = new Parameter().in("header").schema(new StringSchema()).name("X-Requested-By") - .description("ivy").required(true); - - operation.addParametersItem(headerParameter); - } - }); - } -} diff --git a/src/main/java/com/axonivy/market/config/WebConfig.java b/src/main/java/com/axonivy/market/config/WebConfig.java index 3ac974686..b885a477d 100644 --- a/src/main/java/com/axonivy/market/config/WebConfig.java +++ b/src/main/java/com/axonivy/market/config/WebConfig.java @@ -1,7 +1,6 @@ package com.axonivy.market.config; -import static com.axonivy.market.constants.RequestMappingConstants.USER_MAPPING; - +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; @@ -10,22 +9,34 @@ @Configuration public class WebConfig implements WebMvcConfigurer { - private final HeaderInterceptor headerInterceptor; + private static final String[] EXCLUDE_PATHS = { "/", "/swagger-ui/**", "/api-docs/**" }; + private static final String[] ALLOWED_HEADERS = { "Accept-Language", "Content-Type", "Authorization", + "X-Requested-By", "x-requested-with", "X-Forwarded-Host" }; + private static final String[] ALLOWED_METHODS = { "GET", "POST", "PUT", "DELETE", "OPTIONS" }; + + private final MarketHeaderInterceptor headerInterceptor; + + @Value("${market.cors.allowed.origin.patterns}") + private String marketCorsAllowedOriginPatterns; + + @Value("${market.cors.allowed.origin.maxAge}") + private int marketCorsAllowedOriginMaxAge; - public WebConfig(HeaderInterceptor headerInterceptor) { + public WebConfig(MarketHeaderInterceptor headerInterceptor) { this.headerInterceptor = headerInterceptor; } @Override public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(headerInterceptor).addPathPatterns(USER_MAPPING); + registry.addInterceptor(headerInterceptor).excludePathPatterns(EXCLUDE_PATHS); } @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOrigins("*") - .allowedMethods("GET", "POST", "PUT", "DELETE") - .allowedHeaders("*"); + .allowedOriginPatterns(marketCorsAllowedOriginPatterns) + .allowedMethods(ALLOWED_METHODS) + .allowedHeaders(ALLOWED_HEADERS) + .maxAge(marketCorsAllowedOriginMaxAge); } } diff --git a/src/main/java/com/axonivy/market/constants/CommonConstants.java b/src/main/java/com/axonivy/market/constants/CommonConstants.java new file mode 100644 index 000000000..d0e28028e --- /dev/null +++ b/src/main/java/com/axonivy/market/constants/CommonConstants.java @@ -0,0 +1,14 @@ +package com.axonivy.market.constants; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CommonConstants { + public static final int INITIAL_PAGE = 1; + public static final int INITIAL_PAGE_SIZE = 10; + public static final String SLASH = "/"; + public static final String REQUESTED_BY = "X-Requested-By"; + public static final String META_FILE = "meta.json"; + public static final String LOGO_FILE = "logo.png"; +} diff --git a/src/main/java/com/axonivy/market/constants/DocumentConstants.java b/src/main/java/com/axonivy/market/constants/DocumentConstants.java deleted file mode 100644 index be5449ceb..000000000 --- a/src/main/java/com/axonivy/market/constants/DocumentConstants.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.axonivy.market.constants; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class DocumentConstants { - public static final String USER_DOCUMENT = "User"; -} diff --git a/src/main/java/com/axonivy/market/constants/EntityConstants.java b/src/main/java/com/axonivy/market/constants/EntityConstants.java new file mode 100644 index 000000000..eaf213b1d --- /dev/null +++ b/src/main/java/com/axonivy/market/constants/EntityConstants.java @@ -0,0 +1,11 @@ +package com.axonivy.market.constants; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class EntityConstants { + public static final String USER = "User"; + public static final String PRODUCT = "Product"; + public static final String GH_REPO_META = "GitHubRepoMeta"; +} diff --git a/src/main/java/com/axonivy/market/constants/GitHubConstants.java b/src/main/java/com/axonivy/market/constants/GitHubConstants.java new file mode 100644 index 000000000..22ce4a3bf --- /dev/null +++ b/src/main/java/com/axonivy/market/constants/GitHubConstants.java @@ -0,0 +1,11 @@ +package com.axonivy.market.constants; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class GitHubConstants { + public static final String AXONIVY_MARKET_ORGANIZATION_NAME = "axonivy-market"; + public static final String AXONIVY_MARKETPLACE_REPO_NAME = "market"; + public static final String AXONIVY_MARKETPLACE_PATH = "market"; +} diff --git a/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java b/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java index 1293eaba4..b3c02f12c 100644 --- a/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java +++ b/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java @@ -5,5 +5,11 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public class RequestMappingConstants { + public static final String ROOT = "/"; + public static final String API = ROOT + "api"; + public static final String SYNC = ROOT + "sync"; public static final String USER_MAPPING = "/user"; + public static final String PRODUCT = API + "/product"; + public static final String PRODUCT_DETAILS = API + "/product-details"; + public static final String SWAGGER_URL = "/swagger-ui/index.html"; } diff --git a/src/main/java/com/axonivy/market/controller/AppController.java b/src/main/java/com/axonivy/market/controller/AppController.java new file mode 100644 index 000000000..60523b72a --- /dev/null +++ b/src/main/java/com/axonivy/market/controller/AppController.java @@ -0,0 +1,43 @@ +package com.axonivy.market.controller; + +import static com.axonivy.market.constants.RequestMappingConstants.ROOT; +import static com.axonivy.market.constants.RequestMappingConstants.SWAGGER_URL; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.axonivy.market.enums.ErrorCode; +import com.axonivy.market.model.Message; + +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@RestController +@RequestMapping(ROOT) +public class AppController { + + @GetMapping + public ResponseEntity root() { + var message = new Message(); + message.setHelpCode(ErrorCode.SUCCESSFUL.getCode()); + message.setMessageDetails( + "Marketplace API is a REST APIs for Marketplace website. Try with %s" + .formatted(extractSwaggerUrl())); + message.setHelpText(ErrorCode.SUCCESSFUL.getHelpText()); + return new ResponseEntity<>(message, HttpStatus.OK); + } + + private String extractSwaggerUrl() { + var swaggerURL = SWAGGER_URL; + try { + swaggerURL = ServletUriComponentsBuilder.fromCurrentContextPath().path(SWAGGER_URL).toUriString(); + } catch (Exception e) { + log.error("Cannot get Swagger Url", e); + } + return swaggerURL; + } +} diff --git a/src/main/java/com/axonivy/market/controller/ProductController.java b/src/main/java/com/axonivy/market/controller/ProductController.java new file mode 100644 index 000000000..3f126c9f7 --- /dev/null +++ b/src/main/java/com/axonivy/market/controller/ProductController.java @@ -0,0 +1,80 @@ +package com.axonivy.market.controller; + +import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT; +import static com.axonivy.market.constants.RequestMappingConstants.SYNC; + +import org.apache.commons.lang3.time.StopWatch; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.hateoas.PagedModel; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.axonivy.market.assembler.ProductModelAssembler; +import com.axonivy.market.entity.Product; +import com.axonivy.market.enums.ErrorCode; +import com.axonivy.market.model.Message; +import com.axonivy.market.model.ProductModel; +import com.axonivy.market.service.ProductService; + +import io.swagger.v3.oas.annotations.Operation; + +@RestController +@RequestMapping(PRODUCT) +public class ProductController { + + private final ProductService service; + private final ProductModelAssembler assembler; + private final PagedResourcesAssembler pagedResourcesAssembler; + + public ProductController(ProductService service, ProductModelAssembler assembler, + PagedResourcesAssembler pagedResourcesAssembler) { + this.service = service; + this.assembler = assembler; + this.pagedResourcesAssembler = pagedResourcesAssembler; + } + + @Operation(summary = "Find all products", description = "Be default system will finds product by type as 'all'") + @GetMapping() + public ResponseEntity> findProducts(@RequestParam(required = true) String type, + @RequestParam(required = false) String keyword, Pageable pageable) { + Page results = service.findProducts(type, keyword, pageable); + if (results.isEmpty()) { + return generateEmptyPagedModel(); + } + var responseContent = new PageImpl(results.getContent(), pageable, results.getTotalElements()); + var pageResources = pagedResourcesAssembler.toModel(responseContent, assembler); + return new ResponseEntity<>(pageResources, HttpStatus.OK); + } + + @PutMapping(SYNC) + public ResponseEntity syncProducts() { + var stopWatch = new StopWatch(); + stopWatch.start(); + var isAlreadyUpToDate = service.syncLatestDataFromMarketRepo(); + var message = new Message(); + message.setHelpCode(ErrorCode.SUCCESSFUL.getCode()); + message.setHelpText(ErrorCode.SUCCESSFUL.getHelpText()); + if (isAlreadyUpToDate) { + message.setMessageDetails("Data is already up to date, nothing to sync"); + } else { + stopWatch.stop(); + message.setMessageDetails(String.format("Finished sync data in [%s] milliseconds", stopWatch.getTime())); + } + return new ResponseEntity<>(message, HttpStatus.OK); + } + + @SuppressWarnings("unchecked") + private ResponseEntity> generateEmptyPagedModel() { + var emptyPagedModel = (PagedModel) pagedResourcesAssembler + .toEmptyModel(Page.empty(), ProductModel.class); + return new ResponseEntity<>(emptyPagedModel, HttpStatus.OK); + } +} diff --git a/src/main/java/com/axonivy/market/controller/ProductDetailsController.java b/src/main/java/com/axonivy/market/controller/ProductDetailsController.java new file mode 100644 index 000000000..a9e8f3647 --- /dev/null +++ b/src/main/java/com/axonivy/market/controller/ProductDetailsController.java @@ -0,0 +1,22 @@ +package com.axonivy.market.controller; + +import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_DETAILS; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(PRODUCT_DETAILS) +public class ProductDetailsController { + + @GetMapping("/{id}") + public ResponseEntity findProduct(@PathVariable("id") String key, + @RequestParam(name = "type", required = false) String type) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } +} diff --git a/src/main/java/com/axonivy/market/entity/GitHubRepoMeta.java b/src/main/java/com/axonivy/market/entity/GitHubRepoMeta.java new file mode 100644 index 000000000..2e0770816 --- /dev/null +++ b/src/main/java/com/axonivy/market/entity/GitHubRepoMeta.java @@ -0,0 +1,20 @@ +package com.axonivy.market.entity; + +import static com.axonivy.market.constants.EntityConstants.GH_REPO_META; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Document(GH_REPO_META) +public class GitHubRepoMeta { + @Id + private String repoURL; + private String repoName; + private Long lastChange; + private String lastSHA1; +} diff --git a/src/main/java/com/axonivy/market/entity/Product.java b/src/main/java/com/axonivy/market/entity/Product.java new file mode 100644 index 000000000..75cba5e35 --- /dev/null +++ b/src/main/java/com/axonivy/market/entity/Product.java @@ -0,0 +1,67 @@ +package com.axonivy.market.entity; + +import static com.axonivy.market.constants.EntityConstants.PRODUCT; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Document(PRODUCT) +public class Product implements Serializable { + + private static final long serialVersionUID = -8770801877877277258L; + @Id + private String id; + private String marketDirectory; + private String name; + private String version; + private String shortDescription; + private String logoUrl; + private Boolean listed; + private String type; + private List tags; + private String vendor; + private String vendorImage; + private String vendorUrl; + private String platformReview; + private String cost; + private String repositoryName; + private String sourceUrl; + private String statusBadgeUrl; + private String language; + private String industry; + private String compatibility; + private Boolean validate; + private Boolean contactUs; + private Integer installationCount; + private Date newestPublishedDate; + private String newestReleaseVersion; + + @Override + public int hashCode() { + return new HashCodeBuilder().append(id).hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } + return new EqualsBuilder().append(id, ((Product) obj).getId()).isEquals(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/entity/User.java b/src/main/java/com/axonivy/market/entity/User.java index f6035ac00..1b88f1095 100644 --- a/src/main/java/com/axonivy/market/entity/User.java +++ b/src/main/java/com/axonivy/market/entity/User.java @@ -1,6 +1,6 @@ package com.axonivy.market.entity; -import static com.axonivy.market.constants.DocumentConstants.USER_DOCUMENT; +import static com.axonivy.market.constants.EntityConstants.USER; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -12,7 +12,7 @@ @Getter @Setter @NoArgsConstructor -@Document(USER_DOCUMENT) +@Document(USER) public class User { @Id private String id; diff --git a/src/main/java/com/axonivy/market/enums/ErrorCode.java b/src/main/java/com/axonivy/market/enums/ErrorCode.java new file mode 100644 index 000000000..de9d54c28 --- /dev/null +++ b/src/main/java/com/axonivy/market/enums/ErrorCode.java @@ -0,0 +1,26 @@ +package com.axonivy.market.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + + +/** + * @fo {@link ErrorCode} is a presentation for a system code during proceeding + * data It has format cseo - 0000 c present for controller s present for + * service e present for entity o present for other And 0000 is a successful + * code + */ + +@Getter +@AllArgsConstructor +@NoArgsConstructor +public enum ErrorCode { + SUCCESSFUL("0000", "SUCCESSFUL"), PRODUCT_FILTER_INVALID("1101", "PRODUCT_FILTER_INVALID"), + PRODUCT_SORT_INVALID("1102", "PRODUCT_SORT_INVALID"), + GH_FILE_STATUS_INVALID("0201", "GIT_HUB_FILE_STATUS_INVALID"), + GH_FILE_TYPE_INVALID("0202", "GIT_HUB_FILE_TYPE_INVALID"); + + String code; + String helpText; +} diff --git a/src/main/java/com/axonivy/market/enums/FileStatus.java b/src/main/java/com/axonivy/market/enums/FileStatus.java new file mode 100644 index 000000000..d75ca9f54 --- /dev/null +++ b/src/main/java/com/axonivy/market/enums/FileStatus.java @@ -0,0 +1,25 @@ +package com.axonivy.market.enums; + +import org.apache.commons.lang3.StringUtils; + +import com.axonivy.market.exceptions.model.NotFoundException; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum FileStatus { + MODIFIED("modified"), ADDED("added"), REMOVED("removed"); + + private String code; + + public static FileStatus of(String code) { + for (var status : values()) { + if (StringUtils.equalsIgnoreCase(code, status.code)) { + return status; + } + } + throw new NotFoundException(ErrorCode.GH_FILE_STATUS_INVALID, "FileStatus: " + code); + } +} diff --git a/src/main/java/com/axonivy/market/enums/FileType.java b/src/main/java/com/axonivy/market/enums/FileType.java new file mode 100644 index 000000000..75bb5beb9 --- /dev/null +++ b/src/main/java/com/axonivy/market/enums/FileType.java @@ -0,0 +1,25 @@ +package com.axonivy.market.enums; + +import org.apache.commons.lang3.StringUtils; + +import com.axonivy.market.exceptions.model.NotFoundException; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum FileType { + META("meta.json"), LOGO("logo.png"); + + private String fileName; + + public static FileType of(String name) { + for (var type : values()) { + if (StringUtils.endsWithIgnoreCase(name, type.getFileName())) { + return type; + } + } + throw new NotFoundException(ErrorCode.GH_FILE_TYPE_INVALID, "FileType: " + name); + } +} diff --git a/src/main/java/com/axonivy/market/enums/SortOption.java b/src/main/java/com/axonivy/market/enums/SortOption.java new file mode 100644 index 000000000..308a01bd3 --- /dev/null +++ b/src/main/java/com/axonivy/market/enums/SortOption.java @@ -0,0 +1,28 @@ +package com.axonivy.market.enums; + +import org.apache.commons.lang3.StringUtils; + +import com.axonivy.market.exceptions.model.InvalidParamException; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum SortOption { + POPULARITY("popularity", "installationCount"), ALPHABETICALLY("alphabetically", "name"), + RECENT("recent", "newestPublishedDate"); + + private String option; + private String code; + + public static SortOption of(String option) { + option = StringUtils.isBlank(option) ? option : option.trim(); + for (var sortOption : values()) { + if (StringUtils.equalsIgnoreCase(sortOption.option, option)) { + return sortOption; + } + } + throw new InvalidParamException(ErrorCode.PRODUCT_SORT_INVALID, "SortOption: " + option); + } +} diff --git a/src/main/java/com/axonivy/market/enums/TypeOption.java b/src/main/java/com/axonivy/market/enums/TypeOption.java new file mode 100644 index 000000000..3b513ea4a --- /dev/null +++ b/src/main/java/com/axonivy/market/enums/TypeOption.java @@ -0,0 +1,30 @@ +package com.axonivy.market.enums; + +import org.apache.commons.lang3.StringUtils; + +import com.axonivy.market.exceptions.model.InvalidParamException; + +import lombok.Getter; + +@Getter +public enum TypeOption { + ALL("all", ""), CONNECTORS("connectors", "connector"), UTILITIES("utilities", "util"), SOLUTIONS("solutions", "solution"), DEMOS("demos", "demo"); + + private String option; + private String code; + + private TypeOption(String option, String code) { + this.option = option; + this.code = code; + } + + public static TypeOption of(String option) { + option = StringUtils.isBlank(option) ? option : option.trim(); + for (var filter : values()) { + if (StringUtils.equalsIgnoreCase(filter.option, option)) { + return filter; + } + } + throw new InvalidParamException(ErrorCode.PRODUCT_FILTER_INVALID, "TypeOption: " + option); + } +} diff --git a/src/main/java/com/axonivy/market/exceptions/ExceptionHandlers.java b/src/main/java/com/axonivy/market/exceptions/ExceptionHandlers.java index 7359bd18b..3dc315608 100644 --- a/src/main/java/com/axonivy/market/exceptions/ExceptionHandlers.java +++ b/src/main/java/com/axonivy/market/exceptions/ExceptionHandlers.java @@ -6,14 +6,34 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; -import com.axonivy.market.model.ApiError; +import com.axonivy.market.exceptions.model.InvalidParamException; +import com.axonivy.market.exceptions.model.MissingHeaderException; +import com.axonivy.market.exceptions.model.NotFoundException; +import com.axonivy.market.model.Message; @ControllerAdvice public class ExceptionHandlers extends ResponseEntityExceptionHandler { @ExceptionHandler(MissingHeaderException.class) - protected ResponseEntity handleMissingServletRequestParameter(MissingHeaderException missingHeaderException) { - ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, missingHeaderException.getMessage()); - return new ResponseEntity<>(apiError, apiError.getStatus()); + public ResponseEntity handleMissingServletRequestParameter(MissingHeaderException missingHeaderException) { + var errorMessage = new Message(); + errorMessage.setMessageDetails(missingHeaderException.getMessage()); + return new ResponseEntity<>(errorMessage, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(NotFoundException.class) + public ResponseEntity handleNotFoundException(NotFoundException notFoundException) { + var errorMessage = new Message(); + errorMessage.setHelpCode(notFoundException.getCode()); + errorMessage.setMessageDetails(notFoundException.getMessage()); + return new ResponseEntity<>(errorMessage, HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(InvalidParamException.class) + public ResponseEntity handleInvalidException(InvalidParamException invalidDataException) { + var errorMessage = new Message(); + errorMessage.setHelpCode(invalidDataException.getCode()); + errorMessage.setMessageDetails(invalidDataException.getMessage()); + return new ResponseEntity<>(errorMessage, HttpStatus.BAD_REQUEST); } } diff --git a/src/main/java/com/axonivy/market/exceptions/model/InvalidParamException.java b/src/main/java/com/axonivy/market/exceptions/model/InvalidParamException.java new file mode 100644 index 000000000..8a82188fa --- /dev/null +++ b/src/main/java/com/axonivy/market/exceptions/model/InvalidParamException.java @@ -0,0 +1,24 @@ +package com.axonivy.market.exceptions.model; + +import com.axonivy.market.enums.ErrorCode; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class InvalidParamException extends NotFoundException { + private static final long serialVersionUID = 1L; + + public InvalidParamException(String code, String message) { + super(code, message); + } + + public InvalidParamException(ErrorCode errorCode) { + super(errorCode); + } + + public InvalidParamException(ErrorCode errorCode, String additionalMessage) { + super(errorCode, additionalMessage); + } +} diff --git a/src/main/java/com/axonivy/market/exceptions/MissingHeaderException.java b/src/main/java/com/axonivy/market/exceptions/model/MissingHeaderException.java similarity index 87% rename from src/main/java/com/axonivy/market/exceptions/MissingHeaderException.java rename to src/main/java/com/axonivy/market/exceptions/model/MissingHeaderException.java index 7c14e8380..4b5b158c6 100644 --- a/src/main/java/com/axonivy/market/exceptions/MissingHeaderException.java +++ b/src/main/java/com/axonivy/market/exceptions/model/MissingHeaderException.java @@ -1,4 +1,4 @@ -package com.axonivy.market.exceptions; +package com.axonivy.market.exceptions.model; import static com.axonivy.market.constants.ErrorMessageConstants.INVALID_MISSING_HEADER_ERROR_MESSAGE; diff --git a/src/main/java/com/axonivy/market/exceptions/model/NotFoundException.java b/src/main/java/com/axonivy/market/exceptions/model/NotFoundException.java new file mode 100644 index 000000000..e1c917749 --- /dev/null +++ b/src/main/java/com/axonivy/market/exceptions/model/NotFoundException.java @@ -0,0 +1,30 @@ +package com.axonivy.market.exceptions.model; + +import com.axonivy.market.enums.ErrorCode; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class NotFoundException extends RuntimeException { + + private static final long serialVersionUID = 1L; + private static final String SEPARATOR = "-"; + + private final String code; + private final String message; + + public NotFoundException(ErrorCode errorCode) { + this.code = errorCode.getCode(); + this.message = errorCode.getHelpText(); + } + + public NotFoundException(ErrorCode errorCode, String additionalMessage) { + this.code = errorCode.getCode(); + this.message = errorCode.getHelpText() + SEPARATOR + additionalMessage; + } + +} diff --git a/src/main/java/com/axonivy/market/factory/ProductFactory.java b/src/main/java/com/axonivy/market/factory/ProductFactory.java new file mode 100644 index 000000000..da55fbf09 --- /dev/null +++ b/src/main/java/com/axonivy/market/factory/ProductFactory.java @@ -0,0 +1,93 @@ +package com.axonivy.market.factory; + +import static com.axonivy.market.constants.CommonConstants.LOGO_FILE; +import static com.axonivy.market.constants.CommonConstants.META_FILE; +import static com.axonivy.market.constants.CommonConstants.SLASH; +import static org.apache.commons.lang3.StringUtils.EMPTY; + +import java.io.IOException; + +import org.apache.commons.lang3.StringUtils; +import org.kohsuke.github.GHContent; + +import com.axonivy.market.entity.Product; +import com.axonivy.market.github.model.Meta; +import com.axonivy.market.github.util.GitHubUtils; +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ProductFactory { + private static final ObjectMapper MAPPER = new ObjectMapper(); + + public static Product mappingByGHContent(Product product, GHContent content) { + if (content == null) { + return product; + } + + var contentName = content.getName(); + if (StringUtils.endsWith(contentName, META_FILE)) { + mappingByMetaJSONFile(product, content); + } + if (StringUtils.endsWith(contentName, LOGO_FILE)) { + product.setLogoUrl(GitHubUtils.getDownloadUrl(content)); + } + return product; + } + + public static Product mappingByMetaJSONFile(Product product, GHContent ghContent) { + Meta meta = null; + try { + meta = jsonDecode(ghContent); + } catch (Exception e) { + log.error("Mapping from Meta file by GHContent failed", e); + return product; + } + + product.setId(meta.getId()); + product.setName(meta.getName()); + product.setMarketDirectory(extractParentDirectory(ghContent)); + product.setListed(meta.getListed()); + product.setType(meta.getType()); + product.setTags(meta.getTags()); + product.setVersion(meta.getVersion()); + product.setShortDescription(meta.getDescription()); + product.setVendor(meta.getVendor()); + product.setVendorImage(meta.getVendorImage()); + product.setVendorUrl(meta.getVendorUrl()); + product.setPlatformReview(meta.getPlatformReview()); + product.setStatusBadgeUrl(meta.getStatusBadgeUrl()); + product.setLanguage(meta.getLanguage()); + product.setIndustry(meta.getIndustry()); + extractSourceUrl(product, meta); + return product; + } + + private static String extractParentDirectory(GHContent ghContent) { + var path = StringUtils.defaultIfEmpty(ghContent.getPath(), EMPTY); + return path.replace(ghContent.getName(), EMPTY); + } + + private static void extractSourceUrl(Product product, Meta meta) { + var sourceUrl = meta.getSourceUrl(); + if (StringUtils.isBlank(sourceUrl)) { + return; + } + String[] tokens = sourceUrl.split(SLASH); + var tokensLength = tokens.length; + var repositoryPath = sourceUrl; + if (tokensLength > 1) { + repositoryPath = String.join(SLASH, tokens[tokensLength - 2], tokens[tokensLength - 1]); + } + product.setRepositoryName(repositoryPath); + product.setSourceUrl(sourceUrl); + } + + private static Meta jsonDecode(GHContent ghContent) throws IOException { + return MAPPER.readValue(ghContent.read().readAllBytes(), Meta.class); + } +} diff --git a/src/main/java/com/axonivy/market/github/model/GitHubFile.java b/src/main/java/com/axonivy/market/github/model/GitHubFile.java new file mode 100644 index 000000000..9586f0886 --- /dev/null +++ b/src/main/java/com/axonivy/market/github/model/GitHubFile.java @@ -0,0 +1,22 @@ +package com.axonivy.market.github.model; + +import java.util.Date; + +import com.axonivy.market.enums.FileStatus; +import com.axonivy.market.enums.FileType; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class GitHubFile { + private String fileName; + private String previousFilename; + private String path; + private FileType type; + private FileStatus status; + private Date commitDate; +} diff --git a/src/main/java/com/axonivy/market/github/model/MavenArtifact.java b/src/main/java/com/axonivy/market/github/model/MavenArtifact.java new file mode 100644 index 000000000..14af356b8 --- /dev/null +++ b/src/main/java/com/axonivy/market/github/model/MavenArtifact.java @@ -0,0 +1,23 @@ +package com.axonivy.market.github.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class MavenArtifact { + private String repoUrl; + private String name; + private String groupId; + private String artifactId; + private String type; +} diff --git a/src/main/java/com/axonivy/market/github/model/Meta.java b/src/main/java/com/axonivy/market/github/model/Meta.java new file mode 100644 index 000000000..4ef988820 --- /dev/null +++ b/src/main/java/com/axonivy/market/github/model/Meta.java @@ -0,0 +1,37 @@ +package com.axonivy.market.github.model; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Meta { + @JsonProperty("$schema") + private String schema; + private String id; + private String name; + private String description; + private String type; + private String platformReview; + private String sourceUrl; + private String statusBadgeUrl; + private String language; + private String industry; + private Boolean listed; + private String version; + private String vendor; + private String vendorImage; + private String vendorUrl; + private List tags; + private List mavenArtifacts; +} diff --git a/src/main/java/com/axonivy/market/github/service/GHAxonIvyMarketRepoService.java b/src/main/java/com/axonivy/market/github/service/GHAxonIvyMarketRepoService.java new file mode 100644 index 000000000..a669fac2a --- /dev/null +++ b/src/main/java/com/axonivy/market/github/service/GHAxonIvyMarketRepoService.java @@ -0,0 +1,21 @@ +package com.axonivy.market.github.service; + +import java.util.List; +import java.util.Map; + +import org.kohsuke.github.GHCommit; +import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHRepository; + +import com.axonivy.market.github.model.GitHubFile; + +public interface GHAxonIvyMarketRepoService { + + public Map> fetchAllMarketItems(); + + public GHCommit getLastCommit(long lastCommitTime); + + public List fetchMarketItemsBySHA1Range(String fromSHA1, String toSHA1); + + public GHRepository getRepository(); +} diff --git a/src/main/java/com/axonivy/market/github/service/GHAxonIvyProductRepoService.java b/src/main/java/com/axonivy/market/github/service/GHAxonIvyProductRepoService.java new file mode 100644 index 000000000..1665799de --- /dev/null +++ b/src/main/java/com/axonivy/market/github/service/GHAxonIvyProductRepoService.java @@ -0,0 +1,14 @@ +package com.axonivy.market.github.service; + +import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHTag; + +import java.io.IOException; +import java.util.List; + +public interface GHAxonIvyProductRepoService { + + GHContent getContentFromGHRepoAndTag(String repoName, String filePath, String tagVersion); + + List getAllTagsFromRepoName(String repoName) throws IOException; +} diff --git a/src/main/java/com/axonivy/market/github/service/GitHubService.java b/src/main/java/com/axonivy/market/github/service/GitHubService.java new file mode 100644 index 000000000..2f4fb7e16 --- /dev/null +++ b/src/main/java/com/axonivy/market/github/service/GitHubService.java @@ -0,0 +1,22 @@ +package com.axonivy.market.github.service; + +import java.io.IOException; +import java.util.List; + +import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHOrganization; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHub; + +public interface GitHubService { + + public GitHub getGitHub() throws IOException; + + public GHOrganization getOrganization(String orgName) throws IOException; + + public GHRepository getRepository(String repositoryPath) throws IOException; + + public List getDirectoryContent(GHRepository ghRepository, String path) throws IOException; + + public GHContent getGHContent(GHRepository ghRepository, String path) throws IOException; +} diff --git a/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java b/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java new file mode 100644 index 000000000..0d5202c18 --- /dev/null +++ b/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java @@ -0,0 +1,139 @@ +package com.axonivy.market.github.service.impl; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.kohsuke.github.GHCommit; +import org.kohsuke.github.GHCommitQueryBuilder; +import org.kohsuke.github.GHCompare; +import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHOrganization; +import org.kohsuke.github.GHRepository; +import org.springframework.stereotype.Service; + +import com.axonivy.market.constants.GitHubConstants; +import com.axonivy.market.enums.FileStatus; +import com.axonivy.market.enums.FileType; +import com.axonivy.market.github.model.GitHubFile; +import com.axonivy.market.github.service.GHAxonIvyMarketRepoService; +import com.axonivy.market.github.service.GitHubService; +import com.axonivy.market.github.util.GitHubUtils; + +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@Service +public class GHAxonIvyMarketRepoServiceImpl implements GHAxonIvyMarketRepoService { + private static final String DEFAULT_BRANCH = "master"; + private static final LocalDateTime INITIAL_COMMIT_DATE = LocalDateTime.of(2020, 10, 30, 0, 0); + private GHOrganization organization; + private GHRepository repository; + + private final GitHubService gitHubService; + + public GHAxonIvyMarketRepoServiceImpl(GitHubService gitHubService) { + this.gitHubService = gitHubService; + } + + @Override + public Map> fetchAllMarketItems() { + Map> ghContentMap = new HashMap<>(); + try { + List directoryContent = gitHubService.getDirectoryContent(getRepository(), + GitHubConstants.AXONIVY_MARKETPLACE_PATH); + for (var content : directoryContent) { + extractFileInDirectoryContent(content, ghContentMap); + } + } catch (Exception e) { + log.error("Cannot fetch GHContent: ", e); + } + return ghContentMap; + } + + private void extractFileInDirectoryContent(GHContent content, Map> ghContentMap) + throws IOException { + if (content != null && content.isDirectory()) { + for (var childContent : GitHubUtils.mapPagedIteratorToList(content.listDirectoryContent())) { + if (childContent.isFile()) { + var contents = ghContentMap.getOrDefault(content.getPath(), new ArrayList<>()); + contents.add(childContent); + ghContentMap.putIfAbsent(content.getPath(), contents); + } else { + extractFileInDirectoryContent(childContent, ghContentMap); + } + } + } + } + + @Override + public GHCommit getLastCommit(long lastCommitTime) { + if (lastCommitTime == 0l) { + lastCommitTime = INITIAL_COMMIT_DATE.atZone(ZoneId.systemDefault()).toEpochSecond(); + } + try { + GHCommitQueryBuilder commitBuilder = createQueryCommitsBuilder(lastCommitTime); + return GitHubUtils.mapPagedIteratorToList(commitBuilder.list()).stream().findFirst().orElse(null); + } catch (Exception e) { + log.error("Cannot query GHCommit: ", e); + } + return null; + } + + private GHCommitQueryBuilder createQueryCommitsBuilder(long lastCommitTime) { + return getRepository().queryCommits().since(lastCommitTime).from(DEFAULT_BRANCH); + } + + @Override + public List fetchMarketItemsBySHA1Range(String fromSHA1, String toSHA1) { + Map gitHubFileMap = new HashMap<>(); + try { + GHCompare compareResult = getRepository().getCompare(fromSHA1, toSHA1); + for (var commit : GitHubUtils.mapPagedIteratorToList(compareResult.listCommits())) { + var listFiles = commit.listFiles(); + if (listFiles == null) { + continue; + } + GitHubUtils.mapPagedIteratorToList(listFiles).forEach(file -> { + String fullPathName = file.getFileName(); + if (FileType.of(fullPathName) != null) { + var gitHubFile = new GitHubFile(); + gitHubFile.setFileName(fullPathName); + gitHubFile.setPath(file.getRawUrl().getPath()); + gitHubFile.setStatus(FileStatus.of(file.getStatus())); + gitHubFile.setType(FileType.of(fullPathName)); + gitHubFile.setPreviousFilename(file.getPreviousFilename()); + gitHubFileMap.put(fullPathName, gitHubFile); + } + }); + } + } catch (Exception e) { + log.error("Cannot get GH compare: ", e); + } + return new ArrayList<>(gitHubFileMap.values()); + } + + private GHOrganization getOrganization() throws IOException { + if (organization == null) { + organization = gitHubService.getOrganization(GitHubConstants.AXONIVY_MARKET_ORGANIZATION_NAME); + } + return organization; + } + + @Override + public GHRepository getRepository() { + if (repository == null) { + try { + repository = getOrganization().getRepository(GitHubConstants.AXONIVY_MARKETPLACE_REPO_NAME); + } catch (IOException e) { + log.error("Get AxonIvy Market repo failed: ", e); + } + } + return repository; + } + +} diff --git a/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java b/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java new file mode 100644 index 000000000..def4bd9b4 --- /dev/null +++ b/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java @@ -0,0 +1,49 @@ +package com.axonivy.market.github.service.impl; + +import java.io.IOException; +import java.util.List; + +import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHOrganization; +import org.kohsuke.github.GHTag; +import org.springframework.stereotype.Service; + +import com.axonivy.market.constants.GitHubConstants; +import com.axonivy.market.github.service.GHAxonIvyProductRepoService; +import com.axonivy.market.github.service.GitHubService; + +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@Service +public class GHAxonIvyProductRepoServiceImpl implements GHAxonIvyProductRepoService { + private GHOrganization organization; + + private final GitHubService gitHubService; + + public GHAxonIvyProductRepoServiceImpl(GitHubService gitHubService) { + this.gitHubService = gitHubService; + } + + @Override + public GHContent getContentFromGHRepoAndTag(String repoName, String filePath, String tagVersion) { + try { + return getOrganization().getRepository(repoName).getFileContent(filePath, tagVersion); + } catch (IOException e) { + log.error("Cannot Get Content From File Directory", e); + return null; + } + } + + private GHOrganization getOrganization() throws IOException { + if (organization == null) { + organization = gitHubService.getOrganization(GitHubConstants.AXONIVY_MARKET_ORGANIZATION_NAME); + } + return organization; + } + + @Override + public List getAllTagsFromRepoName(String repoName) throws IOException { + return getOrganization().getRepository(repoName).listTags().toList(); + } +} diff --git a/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java b/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java new file mode 100644 index 000000000..a5bea0728 --- /dev/null +++ b/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java @@ -0,0 +1,52 @@ +package com.axonivy.market.github.service.impl; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; + +import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHOrganization; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHub; +import org.kohsuke.github.GitHubBuilder; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.springframework.util.ResourceUtils; + +import com.axonivy.market.github.service.GitHubService; + +@Service +public class GitHubServiceImpl implements GitHubService { + + private static final String GITHUB_TOKEN_FILE = "classpath:github.token"; + + @Override + public GitHub getGitHub() throws IOException { + File gitHubToken = ResourceUtils.getFile(GITHUB_TOKEN_FILE); + var token = Files.readString(gitHubToken.toPath()); + return new GitHubBuilder().withOAuthToken(token).build(); + } + + @Override + public GHOrganization getOrganization(String orgName) throws IOException { + return getGitHub().getOrganization(orgName); + } + + @Override + public List getDirectoryContent(GHRepository ghRepository, String path) throws IOException { + Assert.notNull(ghRepository, "Repository must not be null"); + return ghRepository.getDirectoryContent(path); + } + + @Override + public GHRepository getRepository(String repositoryPath) throws IOException { + return getGitHub().getRepository(repositoryPath); + } + + @Override + public GHContent getGHContent(GHRepository ghRepository, String path) throws IOException { + Assert.notNull(ghRepository, "Repository must not be null"); + return ghRepository.getFileContent(path); + } +} diff --git a/src/main/java/com/axonivy/market/github/util/GitHubUtils.java b/src/main/java/com/axonivy/market/github/util/GitHubUtils.java new file mode 100644 index 000000000..8d66b4649 --- /dev/null +++ b/src/main/java/com/axonivy/market/github/util/GitHubUtils.java @@ -0,0 +1,49 @@ +package com.axonivy.market.github.util; + +import java.io.IOException; +import java.util.List; + +import org.kohsuke.github.GHCommit; +import org.kohsuke.github.GHContent; +import org.kohsuke.github.PagedIterable; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class GitHubUtils { + + public static long getGHCommitDate(GHCommit commit) { + long commitTime = 0l; + if (commit != null) { + try { + commitTime = commit.getCommitDate().getTime(); + } catch (Exception e) { + log.error("Check last commit failed", e); + } + } + return commitTime; + } + + public static String getDownloadUrl(GHContent content) { + try { + return content.getDownloadUrl(); + } catch (IOException e) { + log.error("Cannot get DownloadURl from GHContent: ", e); + } + return ""; + } + + public static List mapPagedIteratorToList(PagedIterable paged) { + if (paged != null) { + try { + return paged.toList(); + } catch (IOException e) { + log.error("Cannot parse to list for pagediterable: ", e); + } + } + return List.of(); + } +} diff --git a/src/main/java/com/axonivy/market/model/ApiError.java b/src/main/java/com/axonivy/market/model/ApiError.java deleted file mode 100644 index c07b0d1a8..000000000 --- a/src/main/java/com/axonivy/market/model/ApiError.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.axonivy.market.model; - -import org.springframework.http.HttpStatus; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -@AllArgsConstructor -public class ApiError { - private HttpStatus status; - private String message; -} diff --git a/src/main/java/com/axonivy/market/model/Message.java b/src/main/java/com/axonivy/market/model/Message.java new file mode 100644 index 000000000..45754acaf --- /dev/null +++ b/src/main/java/com/axonivy/market/model/Message.java @@ -0,0 +1,16 @@ +package com.axonivy.market.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class Message { + private String helpCode; + private String helpText; + private String messageDetails; +} diff --git a/src/main/java/com/axonivy/market/model/ProductModel.java b/src/main/java/com/axonivy/market/model/ProductModel.java new file mode 100644 index 000000000..e088f1230 --- /dev/null +++ b/src/main/java/com/axonivy/market/model/ProductModel.java @@ -0,0 +1,42 @@ +package com.axonivy.market.model; + +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.hateoas.server.core.Relation; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@Relation(collectionRelation = "products", itemRelation = "product") +@JsonInclude(Include.NON_NULL) +public class ProductModel extends RepresentationModel { + private String id; + private String name; + private String shortDescription; + private String logoUrl; + private String type; + private List tags; + + @Override + public int hashCode() { + return new HashCodeBuilder().append(id).hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } + return new EqualsBuilder().append(id, ((ProductModel) obj).getId()).isEquals(); + } +} diff --git a/src/main/java/com/axonivy/market/repository/GitHubRepoMetaRepository.java b/src/main/java/com/axonivy/market/repository/GitHubRepoMetaRepository.java new file mode 100644 index 000000000..49424d63c --- /dev/null +++ b/src/main/java/com/axonivy/market/repository/GitHubRepoMetaRepository.java @@ -0,0 +1,10 @@ +package com.axonivy.market.repository; + +import org.springframework.data.mongodb.repository.MongoRepository; + +import com.axonivy.market.entity.GitHubRepoMeta; + +public interface GitHubRepoMetaRepository extends MongoRepository { + + GitHubRepoMeta findByRepoName(String repoName); +} diff --git a/src/main/java/com/axonivy/market/repository/ProductRepository.java b/src/main/java/com/axonivy/market/repository/ProductRepository.java new file mode 100644 index 000000000..238cd774f --- /dev/null +++ b/src/main/java/com/axonivy/market/repository/ProductRepository.java @@ -0,0 +1,26 @@ +package com.axonivy.market.repository; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; + +import com.axonivy.market.entity.Product; + +@Repository +public interface ProductRepository extends MongoRepository { + + Page findByType(String type, Pageable pageable); + + Product findByLogoUrl(String logoUrl); + + @Query("{'marketDirectory': {$regex : ?0, $options: 'i'}}") + Product findByMarketDirectoryRegex(String search); + + @Query("{ $and: [ { $or: [ { 'name': { $regex: ?0, $options: 'i' } }, { 'shortDescription': { $regex: ?0, $options: 'i' } } ] }, { 'type': ?1 } ] }") + Page searchByKeywordAndType(String keyword, String type, Pageable unifiedPageabe); + + @Query("{ $or: [ { 'name': { $regex: ?0, $options: 'i' } }, { 'shortDescription': { $regex: ?0, $options: 'i' } } ] }") + Page searchByNameOrShortDescriptionRegex(String keyword, Pageable unifiedPageabe); +} diff --git a/src/main/java/com/axonivy/market/schedulingtask/ScheduledTasks.java b/src/main/java/com/axonivy/market/schedulingtask/ScheduledTasks.java new file mode 100644 index 000000000..96153ba39 --- /dev/null +++ b/src/main/java/com/axonivy/market/schedulingtask/ScheduledTasks.java @@ -0,0 +1,28 @@ +package com.axonivy.market.schedulingtask; + +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import com.axonivy.market.service.ProductService; + +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@Component +public class ScheduledTasks { + + private static final String SCHEDULING_TASK_PRODUCTS_CRON = "0 0 0/1 ? * *"; + + private ProductService productService; + + public ScheduledTasks(ProductService productService) { + this.productService = productService; + } + + @Scheduled(cron = SCHEDULING_TASK_PRODUCTS_CRON) + public void syncDataForProductFromGitHubRepo() { + log.warn("Started sync data for product from GitHub repo"); + productService.syncLatestDataFromMarketRepo(); + } + +} diff --git a/src/main/java/com/axonivy/market/service/ProductService.java b/src/main/java/com/axonivy/market/service/ProductService.java new file mode 100644 index 000000000..0a3f5529f --- /dev/null +++ b/src/main/java/com/axonivy/market/service/ProductService.java @@ -0,0 +1,12 @@ +package com.axonivy.market.service; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import com.axonivy.market.entity.Product; + +public interface ProductService { + Page findProducts(String type, String keyword, Pageable pageable); + + boolean syncLatestDataFromMarketRepo(); +} diff --git a/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java b/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java new file mode 100644 index 000000000..44de63b60 --- /dev/null +++ b/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java @@ -0,0 +1,250 @@ +package com.axonivy.market.service.impl; + +import static java.util.Optional.ofNullable; +import static org.apache.commons.lang3.StringUtils.EMPTY; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; +import org.kohsuke.github.GHCommit; +import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHTag; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Order; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import com.axonivy.market.constants.GitHubConstants; +import com.axonivy.market.entity.GitHubRepoMeta; +import com.axonivy.market.entity.Product; +import com.axonivy.market.enums.FileType; +import com.axonivy.market.enums.SortOption; +import com.axonivy.market.enums.TypeOption; +import com.axonivy.market.factory.ProductFactory; +import com.axonivy.market.github.model.GitHubFile; +import com.axonivy.market.github.service.GHAxonIvyMarketRepoService; +import com.axonivy.market.github.service.GitHubService; +import com.axonivy.market.github.util.GitHubUtils; +import com.axonivy.market.repository.GitHubRepoMetaRepository; +import com.axonivy.market.repository.ProductRepository; +import com.axonivy.market.service.ProductService; + +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@Service +public class ProductServiceImpl implements ProductService { + + private final ProductRepository productRepository; + private final GHAxonIvyMarketRepoService axonIvyMarketRepoService; + private final GitHubRepoMetaRepository gitHubRepoMetaRepository; + private final GitHubService gitHubService; + + private GHCommit lastGHCommit; + private GitHubRepoMeta marketRepoMeta; + + public ProductServiceImpl(ProductRepository productRepository, GHAxonIvyMarketRepoService axonIvyMarketRepoService, + GitHubRepoMetaRepository gitHubRepoMetaRepository, GitHubService gitHubService) { + this.productRepository = productRepository; + this.axonIvyMarketRepoService = axonIvyMarketRepoService; + this.gitHubRepoMetaRepository = gitHubRepoMetaRepository; + this.gitHubService = gitHubService; + } + + @Override + public Page findProducts(String type, String keyword, Pageable pageable) { + final var typeOption = TypeOption.of(type); + final var searchPageable = refinePagination(pageable); + Page result = Page.empty(); + switch (typeOption) { + case ALL: + if (StringUtils.isBlank(keyword)) { + result = productRepository.findAll(searchPageable); + } else { + result = productRepository.searchByNameOrShortDescriptionRegex(keyword, searchPageable); + } + break; + case CONNECTORS, UTILITIES, SOLUTIONS: + if (StringUtils.isBlank(keyword)) { + result = productRepository.findByType(typeOption.getCode(), searchPageable); + } else { + result = productRepository.searchByKeywordAndType(keyword, typeOption.getCode(), searchPageable); + } + break; + default: + break; + } + return result; + } + + @Override + public boolean syncLatestDataFromMarketRepo() { + var isAlreadyUpToDate = isLastGithubCommitCovered(); + if (!isAlreadyUpToDate) { + if (marketRepoMeta == null) { + syncProductsFromGitHubRepo(); + marketRepoMeta = new GitHubRepoMeta(); + } else { + updateLatestChangeToProductsFromGithubRepo(); + } + syncRepoMetaDataStatus(); + } + return isAlreadyUpToDate; + } + + private void syncRepoMetaDataStatus() { + if (lastGHCommit == null) { + return; + } + String repoURL = Optional.ofNullable(lastGHCommit.getOwner()).map(GHRepository::getUrl).map(URL::getPath) + .orElse(EMPTY); + marketRepoMeta.setRepoURL(repoURL); + marketRepoMeta.setRepoName(GitHubConstants.AXONIVY_MARKETPLACE_REPO_NAME); + marketRepoMeta.setLastSHA1(lastGHCommit.getSHA1()); + marketRepoMeta.setLastChange(GitHubUtils.getGHCommitDate(lastGHCommit)); + gitHubRepoMetaRepository.save(marketRepoMeta); + marketRepoMeta = null; + } + + private void updateLatestChangeToProductsFromGithubRepo() { + var fromSHA1 = marketRepoMeta.getLastSHA1(); + var toSHA1 = ofNullable(lastGHCommit).map(GHCommit::getSHA1).orElse(""); + log.warn("**ProductService: synchronize products from SHA1 {} to SHA1 {}", fromSHA1, toSHA1); + List gitHubFileChanges = axonIvyMarketRepoService.fetchMarketItemsBySHA1Range(fromSHA1, toSHA1); + Map> groupGitHubFiles = new HashMap<>(); + for (var file : gitHubFileChanges) { + String filePath = file.getFileName(); + var parentPath = filePath.replace(FileType.META.getFileName(), EMPTY).replace(FileType.LOGO.getFileName(), EMPTY); + var files = groupGitHubFiles.getOrDefault(parentPath, new ArrayList<>()); + files.add(file); + groupGitHubFiles.putIfAbsent(parentPath, files); + } + + groupGitHubFiles.entrySet().forEach(ghFileEntity -> { + for (var file : ghFileEntity.getValue()) { + Product product = new Product(); + GHContent fileContent; + try { + fileContent = gitHubService.getGHContent(axonIvyMarketRepoService.getRepository(), file.getFileName()); + } catch (IOException e) { + log.error("Get GHContent failed: ", e); + continue; + } + + ProductFactory.mappingByGHContent(product, fileContent); + updateLatestReleaseDateForProduct(product); + if (FileType.META == file.getType()) { + modifyProductByMetaContent(file, product); + } else { + modifyProductLogo(ghFileEntity.getKey(), file, product, fileContent); + } + } + }); + } + + private void modifyProductLogo(String parentPath, GitHubFile file, Product product, GHContent fileContent) { + Product result = null; + switch (file.getStatus()) { + case MODIFIED, ADDED: + result = productRepository.findByMarketDirectoryRegex(parentPath); + if (result != null) { + result.setLogoUrl(GitHubUtils.getDownloadUrl(fileContent)); + productRepository.save(result); + } + break; + case REMOVED: + result = productRepository.findByLogoUrl(product.getLogoUrl()); + if (result != null) { + productRepository.deleteById(result.getId()); + } + break; + default: + break; + } + } + + private void modifyProductByMetaContent(GitHubFile file, Product product) { + switch (file.getStatus()) { + case MODIFIED, ADDED: + productRepository.save(product); + break; + case REMOVED: + productRepository.deleteById(product.getId()); + break; + default: + break; + } + } + + private Pageable refinePagination(Pageable pageable) { + PageRequest pageRequest = (PageRequest) pageable; + if (pageable != null && pageable.getSort() != null) { + List orders = new ArrayList<>(); + for (var sort : pageable.getSort()) { + final var sortOption = SortOption.of(sort.getProperty()); + Order order = new Order(sort.getDirection(), sortOption.getCode()); + orders.add(order); + } + pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(orders)); + } + return pageRequest; + } + + private boolean isLastGithubCommitCovered() { + boolean isLastCommitCovered = false; + long lastCommitTime = 0l; + marketRepoMeta = gitHubRepoMetaRepository.findByRepoName(GitHubConstants.AXONIVY_MARKETPLACE_REPO_NAME); + if (marketRepoMeta != null) { + lastCommitTime = marketRepoMeta.getLastChange(); + } + lastGHCommit = axonIvyMarketRepoService.getLastCommit(lastCommitTime); + if (lastGHCommit != null && marketRepoMeta != null + && StringUtils.equals(lastGHCommit.getSHA1(), marketRepoMeta.getLastSHA1())) { + isLastCommitCovered = true; + } + return isLastCommitCovered; + } + + private Page syncProductsFromGitHubRepo() { + log.warn("**ProductService: synchronize products from scratch based on the Market repo"); + var gitHubContentMap = axonIvyMarketRepoService.fetchAllMarketItems(); + List products = new ArrayList<>(); + gitHubContentMap.entrySet().forEach(ghContentEntity -> { + Product product = new Product(); + for (var content : ghContentEntity.getValue()) { + ProductFactory.mappingByGHContent(product, content); + updateLatestReleaseDateForProduct(product); + } + products.add(product); + }); + if (!products.isEmpty()) { + productRepository.saveAll(products); + } + return new PageImpl<>(products); + } + + private void updateLatestReleaseDateForProduct(Product product) { + if (StringUtils.isBlank(product.getRepositoryName())) { + return; + } + try { + GHRepository productRepo = gitHubService.getRepository(product.getRepositoryName()); + GHTag lastTag = CollectionUtils.firstElement(productRepo.listTags().toList()); + product.setNewestPublishedDate(lastTag.getCommit().getCommitDate()); + product.setNewestReleaseVersion(lastTag.getName()); + } catch (Exception e) { + log.error("Cannot find repository by path {} {}", product.getRepositoryName(), e); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index bf73652c4..458102046 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,4 +4,8 @@ spring.data.mongodb.database=marketplace server.port=8080 logging.level.org.springframework.web=warn request.header=ivy -server.forward-headers-strategy=framework \ No newline at end of file +server.forward-headers-strategy=framework +springdoc.api-docs.path=/api-docs +springdoc.swagger-ui.path=/swagger-ui.html +market.cors.allowed.origin.patterns=http://localhost:[*], http://10.193.8.78:[*], http://marketplace.server.ivy-cloud.com:[*] +market.cors.allowed.origin.maxAge=3600 diff --git a/src/main/resources/github.token b/src/main/resources/github.token new file mode 100644 index 000000000..f0208d200 --- /dev/null +++ b/src/main/resources/github.token @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/test/java/com/axonivy/market/controller/AppControllerTest.java b/src/test/java/com/axonivy/market/controller/AppControllerTest.java new file mode 100644 index 000000000..b46637a6e --- /dev/null +++ b/src/test/java/com/axonivy/market/controller/AppControllerTest.java @@ -0,0 +1,26 @@ +package com.axonivy.market.controller; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +class AppControllerTest { + + @InjectMocks + private AppController appController; + + @Test + void testRoot() throws Exception { + var response = appController.root(); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertTrue(response.hasBody()); + assertTrue(response.getBody().getMessageDetails().contains("/swagger-ui/index.html")); + } + +} diff --git a/src/test/java/com/axonivy/market/controller/ProductControllerTest.java b/src/test/java/com/axonivy/market/controller/ProductControllerTest.java new file mode 100644 index 000000000..68e846755 --- /dev/null +++ b/src/test/java/com/axonivy/market/controller/ProductControllerTest.java @@ -0,0 +1,105 @@ +package com.axonivy.market.controller; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Order; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.hateoas.PagedModel; +import org.springframework.hateoas.PagedModel.PageMetadata; +import org.springframework.http.HttpStatus; + +import com.axonivy.market.assembler.ProductModelAssembler; +import com.axonivy.market.entity.Product; +import com.axonivy.market.enums.ErrorCode; +import com.axonivy.market.enums.SortOption; +import com.axonivy.market.enums.TypeOption; +import com.axonivy.market.service.ProductService; + +@ExtendWith(MockitoExtension.class) +class ProductControllerTest { + private static final String PRODUCT_NAME_SAMPLE = "Amazon Comprehend"; + private static final String PRODUCT_DESC_SAMPLE = "Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data."; + + @Mock + private ProductService service; + + @Mock + private ProductModelAssembler assembler; + + @Mock + private PagedResourcesAssembler pagedResourcesAssembler; + + @Mock + private PagedModel> pagedProductModel; + + @InjectMocks + private ProductController productController; + + @BeforeEach + void setup() { + assembler = new ProductModelAssembler(); + } + + @Test + void testFindProductsAsEmpty() { + PageRequest pageable = PageRequest.of(0, 20); + Page mockProducts = new PageImpl(List.of(), pageable, 0); + when(service.findProducts(any(), any(), any())).thenReturn(mockProducts); + when(pagedResourcesAssembler.toEmptyModel(any(), any())).thenReturn(PagedModel.empty()); + var result = productController.findProducts(TypeOption.ALL.getOption(), null, pageable); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertTrue(result.hasBody()); + assertEquals(0, result.getBody().getContent().size()); + } + + @Test + void testFindProducts() { + PageRequest pageable = PageRequest.of(0, 20, Sort.by(Order.by(SortOption.ALPHABETICALLY.getOption()))); + Product mockProduct = createProductMock(); + + Page mockProducts = new PageImpl(List.of(mockProduct), pageable, 1); + when(service.findProducts(any(), any(), any())).thenReturn(mockProducts); + assembler = new ProductModelAssembler(); + var mockProductModel = assembler.toModel(mockProduct); + var mockPagedModel = PagedModel.of(List.of(mockProductModel), new PageMetadata(1, 0, 1)); + when(pagedResourcesAssembler.toModel(any(), any(ProductModelAssembler.class))).thenReturn(mockPagedModel); + var result = productController.findProducts(TypeOption.ALL.getOption(), null, pageable); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertTrue(result.hasBody()); + assertEquals(1, result.getBody().getContent().size()); + assertEquals(PRODUCT_NAME_SAMPLE, result.getBody().getContent().iterator().next().getName()); + } + + @Test + void testSyncProducts() { + var response = productController.syncProducts(); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertTrue(response.hasBody()); + assertEquals(ErrorCode.SUCCESSFUL.getCode(), response.getBody().getHelpCode()); + } + + private Product createProductMock() { + Product mockProduct = new Product(); + mockProduct.setId("amazon-comprehend"); + mockProduct.setName(PRODUCT_NAME_SAMPLE); + mockProduct.setShortDescription(PRODUCT_DESC_SAMPLE); + mockProduct.setType("connector"); + mockProduct.setTags(List.of("AI")); + return mockProduct; + } +} diff --git a/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java b/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java new file mode 100644 index 000000000..b8babd511 --- /dev/null +++ b/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java @@ -0,0 +1,23 @@ +package com.axonivy.market.controller; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; + +@ExtendWith(MockitoExtension.class) +class ProductDetailsControllerTest { + + @InjectMocks + private ProductDetailsController productDetailsController; + + @Test + void testFindProduct() { + var result = productDetailsController.findProduct("", ""); + assertEquals(HttpStatus.NOT_FOUND, result.getStatusCode()); + } + +} diff --git a/src/test/java/com/axonivy/market/controller/UserControllerTest.java b/src/test/java/com/axonivy/market/controller/UserControllerTest.java new file mode 100644 index 000000000..5886b6473 --- /dev/null +++ b/src/test/java/com/axonivy/market/controller/UserControllerTest.java @@ -0,0 +1,27 @@ +package com.axonivy.market.controller; + +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.axonivy.market.service.UserService; + +@ExtendWith(MockitoExtension.class) +class UserControllerTest { + + @Mock + UserService userService; + + @InjectMocks + UserController userController; + + @Test + void testGetAllUser() { + var result = userController.getAllUser(); + assertNotEquals(null, result); + } +} diff --git a/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java b/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java new file mode 100644 index 000000000..7c8099b91 --- /dev/null +++ b/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java @@ -0,0 +1,50 @@ +package com.axonivy.market.factory; + +import static com.axonivy.market.constants.CommonConstants.META_FILE; +import static com.axonivy.market.constants.CommonConstants.SLASH; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.io.InputStream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.kohsuke.github.GHContent; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.axonivy.market.constants.CommonConstants; +import com.axonivy.market.entity.Product; + +@ExtendWith(MockitoExtension.class) +class ProductFactoryTest { + private static final String DUMMY_LOGO_URL = "https://raw.githubusercontent.com/axonivy-market/market/master/market/connector/amazon-comprehend-connector/logo.png"; + + @Test + void testMappingByGHContent() throws IOException { + Product product = new Product(); + GHContent mockContent = mock(GHContent.class); + when(mockContent.getName()).thenReturn(CommonConstants.META_FILE); + InputStream inputStream = this.getClass().getResourceAsStream(SLASH.concat(META_FILE)); + when(mockContent.read()).thenReturn(inputStream); + var result = ProductFactory.mappingByGHContent(product, mockContent); + assertNotEquals(null, result); + assertEquals("Amazon Comprehend", result.getName()); + } + + @Test + void testMappingLogo() throws IOException { + Product product = new Product(); + GHContent content = mock(GHContent.class); + when(content.getName()).thenReturn(CommonConstants.LOGO_FILE); + var result = ProductFactory.mappingByGHContent(product, content); + assertNotEquals(null, result); + + when(content.getName()).thenReturn(CommonConstants.LOGO_FILE); + when(content.getDownloadUrl()).thenReturn(DUMMY_LOGO_URL); + result = ProductFactory.mappingByGHContent(product, content); + assertNotEquals(null, result); + } +} diff --git a/src/test/java/com/axonivy/market/handler/ExceptionHandlersTest.java b/src/test/java/com/axonivy/market/handler/ExceptionHandlersTest.java new file mode 100644 index 000000000..3bbe6f80b --- /dev/null +++ b/src/test/java/com/axonivy/market/handler/ExceptionHandlersTest.java @@ -0,0 +1,57 @@ +package com.axonivy.market.handler; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; + +import com.axonivy.market.exceptions.ExceptionHandlers; +import com.axonivy.market.exceptions.model.InvalidParamException; +import com.axonivy.market.exceptions.model.MissingHeaderException; +import com.axonivy.market.exceptions.model.NotFoundException; +import com.axonivy.market.model.Message; + +@ExtendWith(MockitoExtension.class) +class ExceptionHandlersTest { + + @InjectMocks + private ExceptionHandlers exceptionHandlers; + + @BeforeEach + public void setUp() { + exceptionHandlers = new ExceptionHandlers(); + } + + @Test + void testHandleMissingServletRequestParameter() { + var errorMessageText = "Missing header"; + var missingHeaderException = mock(MissingHeaderException.class); + when(missingHeaderException.getMessage()).thenReturn(errorMessageText); + + var responseEntity = exceptionHandlers.handleMissingServletRequestParameter(missingHeaderException); + + assertEquals(HttpStatus.BAD_REQUEST, responseEntity.getStatusCode()); + Message errorMessage = (Message) responseEntity.getBody(); + assertEquals(errorMessageText, errorMessage.getMessageDetails()); + } + + @Test + void testHandleNotFoundException() { + var notFoundException = mock(NotFoundException.class); + var responseEntity = exceptionHandlers.handleNotFoundException(notFoundException); + assertEquals(HttpStatus.NOT_FOUND, responseEntity.getStatusCode()); + } + + @Test + void testHandleInvalidException() { + var invalidParamException = mock(InvalidParamException.class); + var responseEntity = exceptionHandlers.handleInvalidException(invalidParamException); + assertEquals(HttpStatus.BAD_REQUEST, responseEntity.getStatusCode()); + } +} diff --git a/src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java b/src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java new file mode 100644 index 000000000..d8b06b074 --- /dev/null +++ b/src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java @@ -0,0 +1,115 @@ +package com.axonivy.market.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.kohsuke.github.GHCommit.File; +import org.kohsuke.github.GHCompare; +import org.kohsuke.github.GHCompare.Commit; +import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHOrganization; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.PagedIterable; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.axonivy.market.github.service.GitHubService; +import com.axonivy.market.github.service.impl.GHAxonIvyMarketRepoServiceImpl; + +@ExtendWith(MockitoExtension.class) +class GHAxonIvyMarketRepoServiceImplTest { + + @Mock + GHOrganization ghOrganization; + + @Mock + GHRepository ghRepository; + + @Mock + PagedIterable pagedGHContent; + + @Mock + PagedIterable pagedCommit; + + @Mock + PagedIterable pagedFile; + + @Mock + GitHubService gitHubService; + + @InjectMocks + GHAxonIvyMarketRepoServiceImpl axonIvyMarketRepoServiceImpl; + + @BeforeEach + void setup() throws IOException { + when(ghOrganization.getRepository(any())).thenReturn(ghRepository); + when(gitHubService.getOrganization(anyString())).thenReturn(ghOrganization); + } + + @Test + void testFetchAllMarketItems() throws IOException { + // Empty due to missing token + var ghContentMap = axonIvyMarketRepoServiceImpl.fetchAllMarketItems(); + assertEquals(0, ghContentMap.values().size()); + + // Has one record from Github-repo + var mockGHFileContent = mock(GHContent.class); + var mockGHContent = mock(GHContent.class); + when(mockGHContent.isDirectory()).thenReturn(true); + when(mockGHContent.listDirectoryContent()).thenReturn(pagedGHContent); + List mockGhContents = new ArrayList<>(); + mockGhContents.add(mockGHContent); + when(mockGHFileContent.isFile()).thenReturn(true); + when(pagedGHContent.toList()).thenReturn(List.of(mockGHFileContent)); + when(gitHubService.getDirectoryContent(any(), any())).thenReturn(mockGhContents); + + ghContentMap = axonIvyMarketRepoServiceImpl.fetchAllMarketItems(); + assertEquals(1, ghContentMap.values().size()); + } + + @Test + void testFetchMarketItemsBySHA1Range() throws IOException { + final String startSHA1 = "2f415c725b049655c6c100448b8aeed59514023b"; + final String endSHA1 = "c57259288e208feea7e18fdb2fd483081bb69fb4"; + final String fileName = "test-meta.json"; + + var mockCommit = mock(Commit.class); + var mockGHCompare = mock(GHCompare.class); + when(mockGHCompare.listCommits()).thenReturn(pagedCommit); + when(pagedCommit.toList()).thenReturn(List.of(mockCommit)); + when(ghRepository.getCompare(anyString(), anyString())).thenReturn(mockGHCompare); + + var gitHubFiles = axonIvyMarketRepoServiceImpl.fetchMarketItemsBySHA1Range(startSHA1, endSHA1); + assertEquals(0, gitHubFiles.size()); + + when(mockCommit.listFiles()).thenReturn(pagedFile); + var mockFile = mock(File.class); + when(mockFile.getFileName()).thenReturn(fileName); + when(mockFile.getRawUrl()).thenReturn(new URL("http://github/test-repo-url/test-meta.json")); + when(mockFile.getStatus()).thenReturn("added"); + when(mockFile.getPreviousFilename()).thenReturn("test-prev-meta.json"); + when(pagedFile.toList()).thenReturn(List.of(mockFile)); + + gitHubFiles = axonIvyMarketRepoServiceImpl.fetchMarketItemsBySHA1Range(startSHA1, endSHA1); + assertEquals(1, gitHubFiles.size()); + assertEquals(fileName, gitHubFiles.get(0).getFileName()); + } + + @Test + void testGetLastCommit() throws IOException { + var lastCommit = axonIvyMarketRepoServiceImpl.getLastCommit(0l); + assertEquals(null, lastCommit); + } +} diff --git a/src/test/java/com/axonivy/market/service/GHAxonIvyProductRepoServiceImplTest.java b/src/test/java/com/axonivy/market/service/GHAxonIvyProductRepoServiceImplTest.java new file mode 100644 index 000000000..d5d8ba716 --- /dev/null +++ b/src/test/java/com/axonivy/market/service/GHAxonIvyProductRepoServiceImplTest.java @@ -0,0 +1,65 @@ +package com.axonivy.market.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.kohsuke.github.GHOrganization; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHTag; +import org.kohsuke.github.PagedIterable; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.axonivy.market.github.service.GitHubService; +import com.axonivy.market.github.service.impl.GHAxonIvyProductRepoServiceImpl; + +@ExtendWith(MockitoExtension.class) +class GHAxonIvyProductRepoServiceImplTest { + + private static final String DUMMY_TAG = "v1.0.0"; + + @Mock + PagedIterable listTags; + + @Mock + GHRepository ghRepository; + + @Mock + GitHubService gitHubService; + + @InjectMocks + private GHAxonIvyProductRepoServiceImpl axonivyProductRepoServiceImpl; + + @BeforeEach + void setup() throws IOException { + var mockGHOrganization = mock(GHOrganization.class); + when(mockGHOrganization.getRepository(any())).thenReturn(ghRepository); + when(gitHubService.getOrganization(any())).thenReturn(mockGHOrganization); + } + + @Test + void testAllTagsFromRepoName() throws IOException { + var mockTag = mock(GHTag.class); + when(mockTag.getName()).thenReturn(DUMMY_TAG); + when(listTags.toList()).thenReturn(List.of(mockTag)); + when(ghRepository.listTags()).thenReturn(listTags); + var result = axonivyProductRepoServiceImpl.getAllTagsFromRepoName(""); + assertEquals(1, result.size()); + assertEquals(DUMMY_TAG, result.get(0).getName()); + } + + @Test + void testContentFromGHRepoAndTag() { + var result = axonivyProductRepoServiceImpl.getContentFromGHRepoAndTag("", null, null); + assertEquals(null, result); + } +} diff --git a/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java b/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java new file mode 100644 index 000000000..159c43683 --- /dev/null +++ b/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java @@ -0,0 +1,56 @@ +package com.axonivy.market.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHub; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.axonivy.market.github.service.impl.GitHubServiceImpl; + +@ExtendWith(MockitoExtension.class) +class GitHubServiceImplTest { + private static final String DUMMY_API_URL = "https://api.github.com"; + + @Mock + GitHub gitHub; + + @Mock + GHRepository ghRepository; + + @InjectMocks + private GitHubServiceImpl gitHubService; + + @Test + void testGetGithub() throws IOException { + var result = gitHubService.getGitHub(); + assertEquals(DUMMY_API_URL, result.getApiUrl()); + } + + @Test + void testGetGithubContent() throws IOException { + var mockGHContent = mock(GHContent.class); + final String dummryURL = DUMMY_API_URL.concat("/dummry-content"); + when(mockGHContent.getUrl()).thenReturn(dummryURL); + when(ghRepository.getFileContent(any())).thenReturn(mockGHContent); + var result = gitHubService.getGHContent(ghRepository, ""); + assertEquals(dummryURL, result.getUrl()); + } + + @Test + void testGetDirectoryContent() throws IOException { + var result = gitHubService.getDirectoryContent(ghRepository, ""); + assertEquals(0, result.size()); + } + +} diff --git a/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java b/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java new file mode 100644 index 000000000..5671a21c5 --- /dev/null +++ b/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java @@ -0,0 +1,276 @@ +package com.axonivy.market.service; + +import static com.axonivy.market.constants.CommonConstants.LOGO_FILE; +import static com.axonivy.market.constants.CommonConstants.META_FILE; +import static com.axonivy.market.constants.CommonConstants.SLASH; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.kohsuke.github.GHCommit; +import org.kohsuke.github.GHContent; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +import com.axonivy.market.constants.GitHubConstants; +import com.axonivy.market.entity.GitHubRepoMeta; +import com.axonivy.market.entity.Product; +import com.axonivy.market.enums.FileStatus; +import com.axonivy.market.enums.FileType; +import com.axonivy.market.enums.SortOption; +import com.axonivy.market.enums.TypeOption; +import com.axonivy.market.github.model.GitHubFile; +import com.axonivy.market.github.service.GHAxonIvyMarketRepoService; +import com.axonivy.market.github.service.GitHubService; +import com.axonivy.market.repository.GitHubRepoMetaRepository; +import com.axonivy.market.repository.ProductRepository; +import com.axonivy.market.service.impl.ProductServiceImpl; + +@ExtendWith(MockitoExtension.class) +class ProductServiceImplTest { + + private static final String SAMPLE_PRODUCT_ID = "amazon-comprehend"; + private static final String SAMPLE_PRODUCT_NAME = "Amazon Comprehend"; + private static final long LAST_CHANGE_TIME = 1718096290000l; + private static final Pageable PAGEABLE = PageRequest.of(0, 20, + Sort.by(SortOption.ALPHABETICALLY.getOption()).descending()); + private static final String SHA1_SAMPLE = "35baa89091b2452b77705da227f1a964ecabc6c8"; + private String keyword; + private Page mockResultReturn; + + @Mock + private ProductRepository productRepository; + + @Mock + private GHAxonIvyMarketRepoService marketRepoService; + + @Mock + private GitHubRepoMetaRepository repoMetaRepository; + + @Mock + private GitHubService gitHubService; + + @InjectMocks + private ProductServiceImpl productService; + + @BeforeEach + public void setup() { + mockResultReturn = createPageProductsMock(); + } + + @Test + void testFindProducts() { + // Start testing by All + when(productRepository.findAll(any(Pageable.class))).thenReturn(mockResultReturn); + // Executes + var result = productService.findProducts(TypeOption.ALL.getOption(), keyword, PAGEABLE); + assertEquals(mockResultReturn, result); + + // Start testing by Connector + when(productRepository.findByType(any(), any(Pageable.class))).thenReturn(mockResultReturn); + // Executes + result = productService.findProducts(TypeOption.CONNECTORS.getOption(), keyword, PAGEABLE); + assertEquals(mockResultReturn, result); + + // Start testing by Other + // Executes + result = productService.findProducts(TypeOption.DEMOS.getOption(), keyword, PAGEABLE); + assertEquals(0, result.getSize()); + } + + @Test + void testSyncProductsAsUpdateMetaJSONFromGitHub() throws IOException { + // Start testing by adding new meta + mockMarketRepoMetaStatus(); + var mockCommit = mockGHCommitHasSHA1(UUID.randomUUID().toString()); + when(mockCommit.getCommitDate()).thenReturn(new Date()); + when(marketRepoService.getLastCommit(anyLong())).thenReturn(mockCommit); + + var mockGithubFile = new GitHubFile(); + mockGithubFile.setFileName(META_FILE); + mockGithubFile.setType(FileType.META); + mockGithubFile.setStatus(FileStatus.ADDED); + when(marketRepoService.fetchMarketItemsBySHA1Range(any(), any())).thenReturn(List.of(mockGithubFile)); + var mockGHContent = mockGHContentAsMetaJSON(); + when(gitHubService.getGHContent(any(), anyString())).thenReturn(mockGHContent); + + // Executes + var result = productService.syncLatestDataFromMarketRepo(); + assertEquals(false, result); + + // Start testing by deleting new meta + mockCommit = mockGHCommitHasSHA1(UUID.randomUUID().toString()); + when(mockCommit.getCommitDate()).thenReturn(new Date()); + when(marketRepoService.getLastCommit(anyLong())).thenReturn(mockCommit); + mockGithubFile.setStatus(FileStatus.REMOVED); + // Executes + result = productService.syncLatestDataFromMarketRepo(); + assertEquals(false, result); + } + + @Test + void testSyncProductsAsUpdateLogoFromGitHub() throws IOException { + // Start testing by adding new logo + mockMarketRepoMetaStatus(); + var mockCommit = mockGHCommitHasSHA1(UUID.randomUUID().toString()); + when(mockCommit.getCommitDate()).thenReturn(new Date()); + when(marketRepoService.getLastCommit(anyLong())).thenReturn(mockCommit); + + var mockGitHubFile = mock(GitHubFile.class); + mockGitHubFile = new GitHubFile(); + mockGitHubFile.setFileName(LOGO_FILE); + mockGitHubFile.setType(FileType.LOGO); + mockGitHubFile.setStatus(FileStatus.ADDED); + when(marketRepoService.fetchMarketItemsBySHA1Range(any(), any())).thenReturn(List.of(mockGitHubFile)); + var mockGHContent = mockGHContentAsMetaJSON(); + when(gitHubService.getGHContent(any(), anyString())).thenReturn(mockGHContent); + + // Executes + var result = productService.syncLatestDataFromMarketRepo(); + assertEquals(false, result); + + // Start testing by deleting new logo + when(mockCommit.getSHA1()).thenReturn(UUID.randomUUID().toString()); + mockGitHubFile.setStatus(FileStatus.REMOVED); + when(marketRepoService.fetchMarketItemsBySHA1Range(any(), any())).thenReturn(List.of(mockGitHubFile)); + when(gitHubService.getGHContent(any(), anyString())).thenReturn(mockGHContent); + when(productRepository.findByLogoUrl(any())).thenReturn(new Product()); + + // Executes + result = productService.syncLatestDataFromMarketRepo(); + assertEquals(false, result); + } + + @Test + void testFindAllProductsWithKeyword() throws IOException { + when(productRepository.findAll(any(Pageable.class))).thenReturn(mockResultReturn); + // Executes + var result = productService.findProducts(TypeOption.ALL.getOption(), keyword, PAGEABLE); + assertEquals(mockResultReturn, result); + verify(productRepository).findAll(any(Pageable.class)); + + // Test has keyword + when(productRepository.searchByNameOrShortDescriptionRegex(any(), any(Pageable.class))) + .thenReturn(new PageImpl<>(mockResultReturn.stream() + .filter(product -> product.getName().equals(SAMPLE_PRODUCT_NAME)).collect(Collectors.toList()))); + // Executes + result = productService.findProducts(TypeOption.ALL.getOption(), SAMPLE_PRODUCT_NAME, PAGEABLE); + verify(productRepository).findAll(any(Pageable.class)); + assertTrue(result.hasContent()); + assertEquals(SAMPLE_PRODUCT_NAME, result.getContent().get(0).getName()); + + // Test has keyword and type is connector + when(productRepository.searchByKeywordAndType(any(), any(), any(Pageable.class))).thenReturn( + new PageImpl<>(mockResultReturn.stream().filter(product -> product.getName().equals(SAMPLE_PRODUCT_NAME) + && product.getType().equals(TypeOption.CONNECTORS.getCode())).collect(Collectors.toList()))); + // Executes + result = productService.findProducts(TypeOption.CONNECTORS.getOption(), SAMPLE_PRODUCT_NAME, PAGEABLE); + assertTrue(result.hasContent()); + assertEquals(SAMPLE_PRODUCT_NAME, result.getContent().get(0).getName()); + } + + @Test + void testSyncProductsFirstTime() throws IOException { + var mockCommit = mockGHCommitHasSHA1(SHA1_SAMPLE); + when(marketRepoService.getLastCommit(anyLong())).thenReturn(mockCommit); + when(repoMetaRepository.findByRepoName(anyString())).thenReturn(null); + + var mockContent = mockGHContentAsMetaJSON(); + InputStream inputStream = this.getClass().getResourceAsStream(SLASH.concat(META_FILE)); + when(mockContent.read()).thenReturn(inputStream); + + Map> mockGHContentMap = new HashMap<>(); + mockGHContentMap.put(SAMPLE_PRODUCT_ID, List.of(mockContent)); + when(marketRepoService.fetchAllMarketItems()).thenReturn(mockGHContentMap); + + // Executes + var result = productService.syncLatestDataFromMarketRepo(); + assertEquals(false, result); + } + + @Test + void testNothingToSync() throws IOException { + var gitHubRepoMeta = mock(GitHubRepoMeta.class); + when(gitHubRepoMeta.getLastSHA1()).thenReturn(SHA1_SAMPLE); + var mockCommit = mockGHCommitHasSHA1(SHA1_SAMPLE); + when(marketRepoService.getLastCommit(anyLong())).thenReturn(mockCommit); + when(repoMetaRepository.findByRepoName(anyString())).thenReturn(gitHubRepoMeta); + + // Executes + var result = productService.syncLatestDataFromMarketRepo(); + assertEquals(true, result); + } + + @Test + void testSearchProducts() { + var simplePageable = PageRequest.of(0, 20); + String type = TypeOption.ALL.getOption(); + keyword = "on"; + when(productRepository.searchByNameOrShortDescriptionRegex(keyword, simplePageable)).thenReturn(mockResultReturn); + + var result = productService.findProducts(type, keyword, simplePageable); + assertEquals(result, mockResultReturn); + verify(productRepository).searchByNameOrShortDescriptionRegex(keyword, simplePageable); + } + + private Page createPageProductsMock() { + var mockProducts = new ArrayList(); + Product mockProduct = new Product(); + mockProduct.setId(SAMPLE_PRODUCT_ID); + mockProduct.setName(SAMPLE_PRODUCT_NAME); + mockProduct.setType("connector"); + mockProducts.add(mockProduct); + + mockProduct = new Product(); + mockProduct.setId("tel-search-ch-connector"); + mockProduct.setName("Swiss phone directory"); + mockProduct.setType("util"); + mockProducts.add(mockProduct); + return new PageImpl<>(mockProducts); + } + + private void mockMarketRepoMetaStatus() { + var mockMartketRepoMeta = new GitHubRepoMeta(); + mockMartketRepoMeta.setRepoURL(GitHubConstants.AXONIVY_MARKETPLACE_REPO_NAME); + mockMartketRepoMeta.setRepoName(GitHubConstants.AXONIVY_MARKETPLACE_REPO_NAME); + mockMartketRepoMeta.setLastChange(LAST_CHANGE_TIME); + mockMartketRepoMeta.setLastSHA1(SHA1_SAMPLE); + when(repoMetaRepository.findByRepoName(any())).thenReturn(mockMartketRepoMeta); + } + + private GHCommit mockGHCommitHasSHA1(String sha1) { + var mockCommit = mock(GHCommit.class); + when(mockCommit.getSHA1()).thenReturn(sha1); + return mockCommit; + } + + private GHContent mockGHContentAsMetaJSON() { + var mockGHContent = mock(GHContent.class); + when(mockGHContent.getName()).thenReturn(META_FILE); + return mockGHContent; + } +} diff --git a/src/test/java/com/axonivy/market/service/SchedulingTasksTest.java b/src/test/java/com/axonivy/market/service/SchedulingTasksTest.java new file mode 100644 index 000000000..f23d6ea21 --- /dev/null +++ b/src/test/java/com/axonivy/market/service/SchedulingTasksTest.java @@ -0,0 +1,26 @@ +package com.axonivy.market.service; + +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.verify; + +import org.awaitility.Awaitility; +import org.awaitility.Durations; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; + +import com.axonivy.market.schedulingtask.ScheduledTasks; + +@SpringBootTest +class SchedulingTasksTest { + + @SpyBean + ScheduledTasks tasks; + + @Test + void testShouldNotTriggerAfterApplicationStarted() { + Awaitility.await().atMost(Durations.TEN_SECONDS).untilAsserted(() -> { + verify(tasks, atLeast(0)).syncDataForProductFromGitHubRepo(); + }); + } +} diff --git a/src/test/java/com/axonivy/market/service/UserServiceImplTest.java b/src/test/java/com/axonivy/market/service/UserServiceImplTest.java index 102c6f3c7..d14aaff8c 100644 --- a/src/test/java/com/axonivy/market/service/UserServiceImplTest.java +++ b/src/test/java/com/axonivy/market/service/UserServiceImplTest.java @@ -15,7 +15,7 @@ import com.axonivy.market.service.impl.UserServiceImpl; @ExtendWith(MockitoExtension.class) -public class UserServiceImplTest { +class UserServiceImplTest { @InjectMocks private UserServiceImpl employeeService; @@ -24,7 +24,7 @@ public class UserServiceImplTest { private UserRepository userRepository; @Test - public void testFindAllUser() { + void testFindAllUser() { // Mock data and service User mockUser = new User(); mockUser.setId("123"); diff --git a/src/test/resources/meta.json b/src/test/resources/meta.json new file mode 100644 index 000000000..90fb8f309 --- /dev/null +++ b/src/test/resources/meta.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json-schema.axonivy.com/market/10.0.0/meta.json", + "id": "amazon-comprehend", + "name": "Amazon Comprehend", + "description": "Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data.", + "type": "connector", + "platformReview": "4.5", + "sourceUrl": "https://github.com/axonivy-market/amazon-comprehend-connector", + "statusBadgeUrl": "https://github.com/axonivy-market/amazon-comprehend-connector/actions/workflows/ci.yml/badge.svg", + "language": "English", + "industry": "Cross-Industry", + "tags": [ + "AI" + ], + "mavenArtifacts": [ + { + "repoUrl": "https://maven.axonivy.com", + "name": "Amazon Comprehend Product", + "groupId": "com.axonivy.connector.amazon.comprehend", + "artifactId": "amazon-comprehend-connector-product", + "type": "zip" + } + ] +} \ No newline at end of file From fe0ab13f7443da57a9ee61f01be001b38babc044 Mon Sep 17 00:00:00 2001 From: Hoan Nguyen <83745591+nqhoan-axonivy@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:25:15 +0700 Subject: [PATCH 07/62] MARP-394 MP api to fetch all artifacts and search (#21) * Add name for param * Rename app name * Add param name * Remove required for pageable * New Gh token --- .github/workflows/dev-build.yml | 12 ++++++++---- .../axonivy/market/controller/ProductController.java | 5 +++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index db32f52fe..137e06a6c 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -19,19 +19,23 @@ jobs: java-version: '17' distribution: 'temurin' cache: maven - - name: Update MongoDB in application.properties + - name: Update configuration env: + APP_PROPERTIES_FILE: 'src/main/resources/application.properties' + GITHUB_TOKEN_FILE: 'src/main/resources/github.token' MONGODB_HOST: ${{ secrets.MONGODB_HOST }} MONGODB_DATABASE: ${{ secrets.MONGODB_DATABASE }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} run: | - sed -i "s/^spring.data.mongodb.host=.*$/spring.data.mongodb.host=$MONGODB_HOST/" src/main/resources/application.properties - sed -i "s/^spring.data.mongodb.database=.*$/spring.data.mongodb.database=$MONGODB_DATABASE/" src/main/resources/application.properties + sed -i "s/^spring.data.mongodb.host=.*$/spring.data.mongodb.host=$MONGODB_HOST/" $APP_PROPERTIES_FILE + sed -i "s/^spring.data.mongodb.database=.*$/spring.data.mongodb.database=$MONGODB_DATABASE/" $APP_PROPERTIES_FILE + sed -i '1d;$d' $GITHUB_TOKEN_FILE && echo $GH_TOKEN > $GITHUB_TOKEN_FILE - name: Build with Maven run: mvn clean package -DskipTests - name: Prepare deployment directory run: mkdir -p deployment && cp target/*.war deployment/ - name: Copy WAR to Tomcat server - run: sudo cp deployment/*.war /opt/tomcat/webapps/marketplace-server.war + run: sudo cp deployment/*.war /opt/tomcat/webapps/marketplace-service.war - name: Restart Tomcat server run: | sudo systemctl stop tomcat diff --git a/src/main/java/com/axonivy/market/controller/ProductController.java b/src/main/java/com/axonivy/market/controller/ProductController.java index 3f126c9f7..434beb202 100644 --- a/src/main/java/com/axonivy/market/controller/ProductController.java +++ b/src/main/java/com/axonivy/market/controller/ProductController.java @@ -43,8 +43,9 @@ public ProductController(ProductService service, ProductModelAssembler assembler @Operation(summary = "Find all products", description = "Be default system will finds product by type as 'all'") @GetMapping() - public ResponseEntity> findProducts(@RequestParam(required = true) String type, - @RequestParam(required = false) String keyword, Pageable pageable) { + public ResponseEntity> findProducts( + @RequestParam(required = true, name = "type") String type, + @RequestParam(required = false, name = "keyword") String keyword, Pageable pageable) { Page results = service.findProducts(type, keyword, pageable); if (results.isEmpty()) { return generateEmptyPagedModel(); From 47b780e8157d0bda822fd36b56257e4077f71e8f Mon Sep 17 00:00:00 2001 From: Hoan Nguyen Date: Tue, 25 Jun 2024 11:57:05 +0700 Subject: [PATCH 08/62] MARP-394 MP APIs to fetch all artifacts and search - Fix new line in token --- .../axonivy/market/github/service/impl/GitHubServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java b/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java index a5bea0728..e4f38e7e5 100644 --- a/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java +++ b/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java @@ -25,7 +25,7 @@ public class GitHubServiceImpl implements GitHubService { public GitHub getGitHub() throws IOException { File gitHubToken = ResourceUtils.getFile(GITHUB_TOKEN_FILE); var token = Files.readString(gitHubToken.toPath()); - return new GitHubBuilder().withOAuthToken(token).build(); + return new GitHubBuilder().withOAuthToken(token.trim().strip()).build(); } @Override From e3fae620056b8a8addf39d00d916ac66f41b331c Mon Sep 17 00:00:00 2001 From: Hoan Nguyen Date: Fri, 28 Jun 2024 10:09:28 +0700 Subject: [PATCH 09/62] Update README.md for githib.token --- README.md | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 68876d06f..4d546f33a 100644 --- a/README.md +++ b/README.md @@ -11,26 +11,24 @@ For further reference, please consider the following sections: ### Guides The following guides illustrate how to use some features concretely: -* installing mongodb , and access it mongodb://localhost:27017/ in mongodb compass or studio3T -* run "mvn clean install" to build a project -* run "mvn test" to test all Test class - -### MongoDB's property configs -* We can set up properties in class application.properties and MongoConfig - -### Access Swagger URL: http://{your-host}/swagger-ui/index.html - -### Steps to set up: * Installing mongodb, and access it as Url mongodb://localhost:27017/, and you can create and name whatever you want ,then you should put them to application.properties +* You can change the MongoDB configuration in file `application.properties` + ``` + spring.data.mongodb.host= + spring.data.mongodb.database= + ``` +* Update GitHub token in file `github.token` * Run mvn clean install to build project * Run mvn test to test all tests -* You can change the configuration in file “application.properties“ -### In case of using eclipse you should install manually Lombok . + +### Access Swagger URL: http://{your-host}/swagger-ui/index.html + +### Install Lombok for Eclipse IDE * Download lombok here https://projectlombok.org/download * run command "java -jar lombok.jar" then you can access file “eclipse.ini“ in eclipse folder where you install → there is a text like this: -javaagent:C:\Users\tvtphuc\eclipse\jee-2024-032\eclipse\lombok.jar → it means you are successful * Start eclipse * Import the project then in the eclipse , you should run the command “mvn clean install“ * After that you go to class MarketplaceServiceApplication → right click to main method → click run as → choose Java Application -* Then you can send a request in postman :) +* Then you can send a request in postman * If you want to run single test in class UserServiceImplTest. You can right-click to method testFindAllUser and right click → select Run as → choose JUnit Test \ No newline at end of file From 0967f381451b286e4fa066bbe723b0ac244167c8 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen <127725498+ntqdinh-axonivy@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:02:52 +0800 Subject: [PATCH 10/62] feature/Marp-475 Detail pages for new market installation and download section --- .../ArchivedArtifactsComparator.java | 14 + .../comparator/LatestVersionComparator.java | 32 + .../config/MarketApiDocumentConfig.java | 2 +- .../config/MarketHeaderInterceptor.java | 2 +- .../axonivy/market/config/MongoConfig.java | 61 +- .../com/axonivy/market/config/WebConfig.java | 2 +- .../market/constants/EntityConstants.java | 7 +- .../market/constants/GitHubConstants.java | 9 +- .../market/constants/MavenConstants.java | 19 + .../NonStandardProductPackageConstants.java | 20 + .../constants/ProductJsonConstants.java | 21 + .../constants/RequestMappingConstants.java | 2 +- .../controller/ProductDetailsController.java | 36 +- .../market/controller/UserController.java | 13 +- .../market/entity/MavenArtifactModel.java | 39 ++ .../market/entity/MavenArtifactVersion.java | 32 + .../com/axonivy/market/entity/Product.java | 78 +-- .../market/factory/ProductFactory.java | 123 ++-- .../market/github/model/ArchivedArtifact.java | 24 + .../market/github/model/MavenArtifact.java | 22 +- .../service/GHAxonIvyProductRepoService.java | 5 +- .../impl/GHAxonIvyProductRepoServiceImpl.java | 148 +++-- .../service/impl/GitHubServiceImpl.java | 2 +- .../model/MavenArtifactVersionModel.java | 18 + .../axonivy/market/model/ProductModel.java | 34 +- .../MavenArtifactVersionRepository.java | 9 + .../market/repository/ProductRepository.java | 4 + .../market/service/VersionService.java | 17 + .../service/impl/VersionServiceImpl.java | 350 +++++++++++ .../axonivy/market/utils/XmlReaderUtils.java | 59 ++ .../market/controller/AppControllerTest.java | 3 +- .../controller/ProductControllerTest.java | 2 +- .../ProductDetailsControllerTest.java | 23 +- .../market/factory/ProductFactoryTest.java | 79 ++- .../GHAxonIvyProductRepoServiceImplTest.java | 242 ++++++++ .../GHAxonIvyProductRepoServiceImplTest.java | 65 -- .../market/service/GitHubServiceImplTest.java | 3 +- .../service/VersionServiceImplTest.java | 565 ++++++++++++++++++ .../market/utils/XmlReaderUtilsTest.java | 21 + 39 files changed, 1903 insertions(+), 304 deletions(-) create mode 100644 src/main/java/com/axonivy/market/comparator/ArchivedArtifactsComparator.java create mode 100644 src/main/java/com/axonivy/market/comparator/LatestVersionComparator.java create mode 100644 src/main/java/com/axonivy/market/constants/MavenConstants.java create mode 100644 src/main/java/com/axonivy/market/constants/NonStandardProductPackageConstants.java create mode 100644 src/main/java/com/axonivy/market/constants/ProductJsonConstants.java create mode 100644 src/main/java/com/axonivy/market/entity/MavenArtifactModel.java create mode 100644 src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java create mode 100644 src/main/java/com/axonivy/market/github/model/ArchivedArtifact.java create mode 100644 src/main/java/com/axonivy/market/model/MavenArtifactVersionModel.java create mode 100644 src/main/java/com/axonivy/market/repository/MavenArtifactVersionRepository.java create mode 100644 src/main/java/com/axonivy/market/service/VersionService.java create mode 100644 src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java create mode 100644 src/main/java/com/axonivy/market/utils/XmlReaderUtils.java create mode 100644 src/test/java/com/axonivy/market/github/service/GHAxonIvyProductRepoServiceImplTest.java delete mode 100644 src/test/java/com/axonivy/market/service/GHAxonIvyProductRepoServiceImplTest.java create mode 100644 src/test/java/com/axonivy/market/service/VersionServiceImplTest.java create mode 100644 src/test/java/com/axonivy/market/utils/XmlReaderUtilsTest.java diff --git a/src/main/java/com/axonivy/market/comparator/ArchivedArtifactsComparator.java b/src/main/java/com/axonivy/market/comparator/ArchivedArtifactsComparator.java new file mode 100644 index 000000000..7a27d7718 --- /dev/null +++ b/src/main/java/com/axonivy/market/comparator/ArchivedArtifactsComparator.java @@ -0,0 +1,14 @@ +package com.axonivy.market.comparator; + +import com.axonivy.market.github.model.ArchivedArtifact; + +import java.util.Comparator; + +public class ArchivedArtifactsComparator implements Comparator { + private final LatestVersionComparator comparator = new LatestVersionComparator(); + + @Override + public int compare(ArchivedArtifact artifact1, ArchivedArtifact artifact2) { + return comparator.compare(artifact1.getLastVersion(), artifact2.getLastVersion()); + } +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/comparator/LatestVersionComparator.java b/src/main/java/com/axonivy/market/comparator/LatestVersionComparator.java new file mode 100644 index 000000000..9419b411f --- /dev/null +++ b/src/main/java/com/axonivy/market/comparator/LatestVersionComparator.java @@ -0,0 +1,32 @@ +package com.axonivy.market.comparator; + +import java.util.Comparator; + +public class LatestVersionComparator implements Comparator { + + @Override + public int compare(String v1, String v2) { + // Split by "." + String[] parts1 = v1.split("\\."); + String[] parts2 = v2.split("\\."); + + // Compare up to the shorter length + int length = Math.min(parts1.length, parts2.length); + for (int i = 0; i < length; i++) { + try { + int num1 = Integer.parseInt(parts1[i]); + int num2 = Integer.parseInt(parts2[i]); + // Return difference for numeric parts + if (num1 != num2) { + return num2 - num1; + } + // Handle non-numeric parts (e.g., "m229") + } catch (NumberFormatException e) { + return parts2[i].replaceAll("\\D", "").compareTo(parts1[i].replaceAll("\\D", "")); + } + } + + // Versions with more parts are considered larger + return parts2.length - parts1.length; + } +} diff --git a/src/main/java/com/axonivy/market/config/MarketApiDocumentConfig.java b/src/main/java/com/axonivy/market/config/MarketApiDocumentConfig.java index a92282a64..61b6a0773 100644 --- a/src/main/java/com/axonivy/market/config/MarketApiDocumentConfig.java +++ b/src/main/java/com/axonivy/market/config/MarketApiDocumentConfig.java @@ -37,4 +37,4 @@ private OpenApiCustomizer customMarketHeaders() { } }); } -} +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/config/MarketHeaderInterceptor.java b/src/main/java/com/axonivy/market/config/MarketHeaderInterceptor.java index 1d35cb9cc..963706069 100644 --- a/src/main/java/com/axonivy/market/config/MarketHeaderInterceptor.java +++ b/src/main/java/com/axonivy/market/config/MarketHeaderInterceptor.java @@ -27,4 +27,4 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons } return true; } -} +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/config/MongoConfig.java b/src/main/java/com/axonivy/market/config/MongoConfig.java index 36b7b6f1d..a6cd2bc05 100644 --- a/src/main/java/com/axonivy/market/config/MongoConfig.java +++ b/src/main/java/com/axonivy/market/config/MongoConfig.java @@ -1,8 +1,15 @@ package com.axonivy.market.config; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration; +import org.springframework.data.mongodb.core.convert.DbRefResolver; +import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.convert.MongoCustomConversions; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; import com.mongodb.ConnectionString; @@ -14,23 +21,39 @@ @EnableMongoRepositories(basePackages = "com.axonivy.market.repository") public class MongoConfig extends AbstractMongoClientConfiguration { - @Value("${spring.data.mongodb.host}") - private String host; - - @Value("${spring.data.mongodb.database}") - private String databaseName; - - @Override - protected String getDatabaseName() { - return databaseName; - } - - @Override - public MongoClient mongoClient() { - ConnectionString connectionString = new ConnectionString(host); - MongoClientSettings mongoClientSettings = MongoClientSettings.builder().applyConnectionString(connectionString) - .build(); - - return MongoClients.create(mongoClientSettings); - } + @Value("${spring.data.mongodb.host}") + private String host; + + @Value("${spring.data.mongodb.database}") + private String databaseName; + + @Override + protected String getDatabaseName() { + return databaseName; + } + + @Override + public MongoClient mongoClient() { + ConnectionString connectionString = new ConnectionString(host); + MongoClientSettings mongoClientSettings = MongoClientSettings.builder().applyConnectionString(connectionString) + .build(); + + return MongoClients.create(mongoClientSettings); + } + + /** + * By default, the key in hash map is not allow to contain dot character (.) we + * need to escape it by define a replacement to that char + **/ + @Override + @Bean + public MappingMongoConverter mappingMongoConverter(MongoDatabaseFactory databaseFactory, + MongoCustomConversions customConversions, MongoMappingContext mappingContext) { + DbRefResolver dbRefResolver = new DefaultDbRefResolver(databaseFactory); + MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext); + converter.setCustomConversions(customConversions); + converter.setCodecRegistryProvider(databaseFactory); + converter.setMapKeyDotReplacement("_"); + return converter; + } } diff --git a/src/main/java/com/axonivy/market/config/WebConfig.java b/src/main/java/com/axonivy/market/config/WebConfig.java index b885a477d..b9d75afa3 100644 --- a/src/main/java/com/axonivy/market/config/WebConfig.java +++ b/src/main/java/com/axonivy/market/config/WebConfig.java @@ -39,4 +39,4 @@ public void addCorsMappings(CorsRegistry registry) { .allowedHeaders(ALLOWED_HEADERS) .maxAge(marketCorsAllowedOriginMaxAge); } -} +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/constants/EntityConstants.java b/src/main/java/com/axonivy/market/constants/EntityConstants.java index eaf213b1d..76c1c45ab 100644 --- a/src/main/java/com/axonivy/market/constants/EntityConstants.java +++ b/src/main/java/com/axonivy/market/constants/EntityConstants.java @@ -5,7 +5,8 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public class EntityConstants { - public static final String USER = "User"; - public static final String PRODUCT = "Product"; - public static final String GH_REPO_META = "GitHubRepoMeta"; + public static final String USER = "User"; + public static final String PRODUCT = "Product"; + public static final String MAVEN_ARTIFACT_VERSION = "MavenArtifactVersion"; + public static final String GH_REPO_META = "GitHubRepoMeta"; } diff --git a/src/main/java/com/axonivy/market/constants/GitHubConstants.java b/src/main/java/com/axonivy/market/constants/GitHubConstants.java index 22ce4a3bf..c03c69f65 100644 --- a/src/main/java/com/axonivy/market/constants/GitHubConstants.java +++ b/src/main/java/com/axonivy/market/constants/GitHubConstants.java @@ -5,7 +5,8 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public class GitHubConstants { - public static final String AXONIVY_MARKET_ORGANIZATION_NAME = "axonivy-market"; - public static final String AXONIVY_MARKETPLACE_REPO_NAME = "market"; - public static final String AXONIVY_MARKETPLACE_PATH = "market"; -} + public static final String AXONIVY_MARKET_ORGANIZATION_NAME = "axonivy-market"; + public static final String AXONIVY_MARKETPLACE_REPO_NAME = "market"; + public static final String AXONIVY_MARKETPLACE_PATH = "market"; + public static final String PRODUCT_JSON_FILE_PATH_FORMAT = "%s/product.json"; +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/constants/MavenConstants.java b/src/main/java/com/axonivy/market/constants/MavenConstants.java new file mode 100644 index 000000000..992a4289b --- /dev/null +++ b/src/main/java/com/axonivy/market/constants/MavenConstants.java @@ -0,0 +1,19 @@ +package com.axonivy.market.constants; + +public class MavenConstants { + private MavenConstants() { + } + + public static final String SNAPSHOT_RELEASE_POSTFIX = "-SNAPSHOT"; + public static final String SPRINT_RELEASE_POSTFIX = "-m"; + public static final String PRODUCT_ARTIFACT_POSTFIX = "-product"; + public static final String METADATA_URL_FORMAT = "%s/%s/%s/maven-metadata.xml"; + public static final String DEFAULT_IVY_MAVEN_BASE_URL = "https://maven.axonivy.com"; + public static final String DOT_SEPARATOR = "."; + public static final String GROUP_ID_URL_SEPARATOR = "/"; + public static final String ARTIFACT_ID_SEPARATOR = "-"; + public static final String ARTIFACT_NAME_SEPARATOR = " "; + public static final String ARTIFACT_DOWNLOAD_URL_FORMAT = "%s/%s/%s/%s/%s-%s.%s"; + public static final String ARTIFACT_NAME_FORMAT = "%s (%s)"; + public static final String VERSION_EXTRACT_FORMAT_FROM_METADATA_FILE = "//versions/version/text()"; +} diff --git a/src/main/java/com/axonivy/market/constants/NonStandardProductPackageConstants.java b/src/main/java/com/axonivy/market/constants/NonStandardProductPackageConstants.java new file mode 100644 index 000000000..c2e14744e --- /dev/null +++ b/src/main/java/com/axonivy/market/constants/NonStandardProductPackageConstants.java @@ -0,0 +1,20 @@ +package com.axonivy.market.constants; + +public class NonStandardProductPackageConstants { + private NonStandardProductPackageConstants() { + } + + public static final String PORTAL = "portal"; + public static final String MICROSOFT_365 = ""; // No meta.json + public static final String MICROSOFT_CALENDAR = "msgraph-calendar"; // no fix product json + public static final String MICROSOFT_MAIL = "msgraph-mail";// no fix product json + public static final String MICROSOFT_TEAMS = "msgraph-chat";// no fix product json + public static final String MICROSOFT_TODO = "msgraph-todo";// no fix product json + public static final String CONNECTIVITY_FEATURE = "connectivity-demo"; + public static final String EMPLOYEE_ONBOARDING = "employee-onboarding"; // Invalid meta.json + public static final String ERROR_HANDLING = "error-handling-demo"; + public static final String RULE_ENGINE_DEMOS = "rule-engine-demo"; + public static final String WORKFLOW_DEMO = "workflow-demo"; + public static final String HTML_DIALOG_DEMO = "html-dialog-demo"; + public static final String PROCESSING_VALVE_DEMO = "processing-valve-demo";// no product json +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/constants/ProductJsonConstants.java b/src/main/java/com/axonivy/market/constants/ProductJsonConstants.java new file mode 100644 index 000000000..9ce62f956 --- /dev/null +++ b/src/main/java/com/axonivy/market/constants/ProductJsonConstants.java @@ -0,0 +1,21 @@ +package com.axonivy.market.constants; + +public class ProductJsonConstants { + + public static final String DATA = "data"; + public static final String REPOSITORIES = "repositories"; + public static final String URL = "url"; + public static final String ID = "id"; + public static final String PROJECTS = "projects"; + public static final String ARTIFACT_ID = "artifactId"; + public static final String GROUP_ID = "groupId"; + public static final String TYPE = "type"; + public static final String DEPENDENCIES = "dependencies"; + public static final String INSTALLERS = "installers"; + public static final String MAVEN_IMPORT_INSTALLER_ID = "maven-import"; + public static final String MAVEN_DROPIN_INSTALLER_ID = "maven-dropins"; + public static final String MAVEN_DEPENDENCY_INSTALLER_ID = "maven-dependency"; + + private ProductJsonConstants() { + } +} diff --git a/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java b/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java index b3c02f12c..f4687a442 100644 --- a/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java +++ b/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java @@ -12,4 +12,4 @@ public class RequestMappingConstants { public static final String PRODUCT = API + "/product"; public static final String PRODUCT_DETAILS = API + "/product-details"; public static final String SWAGGER_URL = "/swagger-ui/index.html"; -} +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/controller/ProductDetailsController.java b/src/main/java/com/axonivy/market/controller/ProductDetailsController.java index a9e8f3647..c5536cd48 100644 --- a/src/main/java/com/axonivy/market/controller/ProductDetailsController.java +++ b/src/main/java/com/axonivy/market/controller/ProductDetailsController.java @@ -1,22 +1,40 @@ package com.axonivy.market.controller; -import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_DETAILS; - +import com.axonivy.market.model.MavenArtifactVersionModel; +import com.axonivy.market.service.VersionService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.PathVariable; + +import java.util.List; + +import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_DETAILS; @RestController @RequestMapping(PRODUCT_DETAILS) public class ProductDetailsController { + private final VersionService service; + + public ProductDetailsController(VersionService service) { + this.service = service; + } + + @GetMapping("/{id}") + public ResponseEntity findProduct(@PathVariable("id") String key, + @RequestParam(name = "type", required = false) String type) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } - @GetMapping("/{id}") - public ResponseEntity findProduct(@PathVariable("id") String key, - @RequestParam(name = "type", required = false) String type) { - return new ResponseEntity<>(HttpStatus.NOT_FOUND); - } -} + @GetMapping("/{id}/versions") + public ResponseEntity> findProductVersionsById(@PathVariable("id") String id, + @RequestParam(name = "isShowDevVersion") boolean isShowDevVersion, + @RequestParam(name = "designerVersion", required = false) String designerVersion) { + List models = service.getArtifactsAndVersionToDisplay(id, isShowDevVersion, + designerVersion); + return new ResponseEntity<>(models, HttpStatus.OK); + } +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/controller/UserController.java b/src/main/java/com/axonivy/market/controller/UserController.java index f94d46920..c83c7cc2b 100644 --- a/src/main/java/com/axonivy/market/controller/UserController.java +++ b/src/main/java/com/axonivy/market/controller/UserController.java @@ -1,16 +1,15 @@ package com.axonivy.market.controller; -import static com.axonivy.market.constants.RequestMappingConstants.USER_MAPPING; - -import java.util.List; - +import com.axonivy.market.entity.User; +import com.axonivy.market.service.UserService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.axonivy.market.entity.User; -import com.axonivy.market.service.UserService; +import java.util.List; + +import static com.axonivy.market.constants.RequestMappingConstants.USER_MAPPING; @RestController @RequestMapping(USER_MAPPING) @@ -25,4 +24,4 @@ public UserController(UserService userService) { public ResponseEntity> getAllUser() { return ResponseEntity.ok(userService.getAllUsers()); } -} +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java b/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java new file mode 100644 index 000000000..f3f8977d5 --- /dev/null +++ b/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java @@ -0,0 +1,39 @@ +package com.axonivy.market.entity; + +import com.axonivy.market.github.model.MavenArtifact; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.Transient; + +import java.io.Serializable; +import java.util.Objects; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class MavenArtifactModel implements Serializable { + private static final long serialVersionUID = 1L; + private String name; + private String downloadUrl; + @Transient + private Boolean isProductArtifact; + + @Override + public boolean equals(Object object) { + if (this == object) + return true; + if (object == null || getClass() != object.getClass()) { + return false; + } + MavenArtifactModel reference = (MavenArtifactModel) object; + return Objects.equals(name, reference.getName()) && Objects.equals(downloadUrl, reference.getDownloadUrl()); + } + + @Override + public int hashCode() { + return Objects.hash(name, downloadUrl); + } +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java b/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java new file mode 100644 index 000000000..f92be46fc --- /dev/null +++ b/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java @@ -0,0 +1,32 @@ +package com.axonivy.market.entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.axonivy.market.constants.EntityConstants.MAVEN_ARTIFACT_VERSION; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Document(MAVEN_ARTIFACT_VERSION) +public class MavenArtifactVersion implements Serializable { + @Id + private String productId; + private List versions = new ArrayList<>(); + private Map> productArtifactWithVersionReleased = new HashMap<>(); + + public MavenArtifactVersion(String productId) { + this.productId = productId; + } +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/entity/Product.java b/src/main/java/com/axonivy/market/entity/Product.java index 75cba5e35..f38748c41 100644 --- a/src/main/java/com/axonivy/market/entity/Product.java +++ b/src/main/java/com/axonivy/market/entity/Product.java @@ -6,6 +6,7 @@ import java.util.Date; import java.util.List; +import com.axonivy.market.github.model.MavenArtifact; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.springframework.data.annotation.Id; @@ -23,45 +24,46 @@ @Document(PRODUCT) public class Product implements Serializable { - private static final long serialVersionUID = -8770801877877277258L; - @Id - private String id; - private String marketDirectory; - private String name; - private String version; - private String shortDescription; - private String logoUrl; - private Boolean listed; - private String type; - private List tags; - private String vendor; - private String vendorImage; - private String vendorUrl; - private String platformReview; - private String cost; - private String repositoryName; - private String sourceUrl; - private String statusBadgeUrl; - private String language; - private String industry; - private String compatibility; - private Boolean validate; - private Boolean contactUs; - private Integer installationCount; - private Date newestPublishedDate; - private String newestReleaseVersion; + private static final long serialVersionUID = -8770801877877277258L; + @Id + private String id; + private String marketDirectory; + private String name; + private String version; + private String shortDescription; + private String logoUrl; + private Boolean listed; + private String type; + private List tags; + private String vendor; + private String vendorImage; + private String vendorUrl; + private String platformReview; + private String cost; + private String repositoryName; + private String sourceUrl; + private String statusBadgeUrl; + private String language; + private String industry; + private String compatibility; + private Boolean validate; + private Boolean contactUs; + private Integer installationCount; + private Date newestPublishedDate; + private String newestReleaseVersion; + private List artifacts; - @Override - public int hashCode() { - return new HashCodeBuilder().append(id).hashCode(); - } + @Override + public int hashCode() { + return new HashCodeBuilder().append(id).hashCode(); + } - @Override - public boolean equals(Object obj) { - if (obj == null || this.getClass() != obj.getClass()) { - return false; - } - return new EqualsBuilder().append(id, ((Product) obj).getId()).isEquals(); - } + @Override + public boolean equals(Object obj) { + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } + return new EqualsBuilder().append(id, ((Product) obj).getId()).isEquals(); + } } \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/factory/ProductFactory.java b/src/main/java/com/axonivy/market/factory/ProductFactory.java index da55fbf09..8ec75e239 100644 --- a/src/main/java/com/axonivy/market/factory/ProductFactory.java +++ b/src/main/java/com/axonivy/market/factory/ProductFactory.java @@ -22,72 +22,73 @@ @Log4j2 @NoArgsConstructor(access = AccessLevel.PRIVATE) public class ProductFactory { - private static final ObjectMapper MAPPER = new ObjectMapper(); + private static final ObjectMapper MAPPER = new ObjectMapper(); - public static Product mappingByGHContent(Product product, GHContent content) { - if (content == null) { - return product; - } + public static Product mappingByGHContent(Product product, GHContent content) { + if (content == null) { + return product; + } - var contentName = content.getName(); - if (StringUtils.endsWith(contentName, META_FILE)) { - mappingByMetaJSONFile(product, content); - } - if (StringUtils.endsWith(contentName, LOGO_FILE)) { - product.setLogoUrl(GitHubUtils.getDownloadUrl(content)); - } - return product; - } + var contentName = content.getName(); + if (StringUtils.endsWith(contentName, META_FILE)) { + mappingByMetaJSONFile(product, content); + } + if (StringUtils.endsWith(contentName, LOGO_FILE)) { + product.setLogoUrl(GitHubUtils.getDownloadUrl(content)); + } + return product; + } - public static Product mappingByMetaJSONFile(Product product, GHContent ghContent) { - Meta meta = null; - try { - meta = jsonDecode(ghContent); - } catch (Exception e) { - log.error("Mapping from Meta file by GHContent failed", e); - return product; - } + public static Product mappingByMetaJSONFile(Product product, GHContent ghContent) { + Meta meta = null; + try { + meta = jsonDecode(ghContent); + } catch (Exception e) { + log.error("Mapping from Meta file by GHContent failed", e); + return product; + } - product.setId(meta.getId()); - product.setName(meta.getName()); - product.setMarketDirectory(extractParentDirectory(ghContent)); - product.setListed(meta.getListed()); - product.setType(meta.getType()); - product.setTags(meta.getTags()); - product.setVersion(meta.getVersion()); - product.setShortDescription(meta.getDescription()); - product.setVendor(meta.getVendor()); - product.setVendorImage(meta.getVendorImage()); - product.setVendorUrl(meta.getVendorUrl()); - product.setPlatformReview(meta.getPlatformReview()); - product.setStatusBadgeUrl(meta.getStatusBadgeUrl()); - product.setLanguage(meta.getLanguage()); - product.setIndustry(meta.getIndustry()); - extractSourceUrl(product, meta); - return product; - } + product.setId(meta.getId()); + product.setName(meta.getName()); + product.setMarketDirectory(extractParentDirectory(ghContent)); + product.setListed(meta.getListed()); + product.setType(meta.getType()); + product.setTags(meta.getTags()); + product.setVersion(meta.getVersion()); + product.setShortDescription(meta.getDescription()); + product.setVendor(meta.getVendor()); + product.setVendorImage(meta.getVendorImage()); + product.setVendorUrl(meta.getVendorUrl()); + product.setPlatformReview(meta.getPlatformReview()); + product.setStatusBadgeUrl(meta.getStatusBadgeUrl()); + product.setLanguage(meta.getLanguage()); + product.setIndustry(meta.getIndustry()); + extractSourceUrl(product, meta); + product.setArtifacts(meta.getMavenArtifacts()); + return product; + } - private static String extractParentDirectory(GHContent ghContent) { - var path = StringUtils.defaultIfEmpty(ghContent.getPath(), EMPTY); - return path.replace(ghContent.getName(), EMPTY); - } + private static String extractParentDirectory(GHContent ghContent) { + var path = StringUtils.defaultIfEmpty(ghContent.getPath(), EMPTY); + return path.replace(ghContent.getName(), EMPTY); + } - private static void extractSourceUrl(Product product, Meta meta) { - var sourceUrl = meta.getSourceUrl(); - if (StringUtils.isBlank(sourceUrl)) { - return; - } - String[] tokens = sourceUrl.split(SLASH); - var tokensLength = tokens.length; - var repositoryPath = sourceUrl; - if (tokensLength > 1) { - repositoryPath = String.join(SLASH, tokens[tokensLength - 2], tokens[tokensLength - 1]); - } - product.setRepositoryName(repositoryPath); - product.setSourceUrl(sourceUrl); - } + public static void extractSourceUrl(Product product, Meta meta) { + var sourceUrl = meta.getSourceUrl(); + if (StringUtils.isBlank(sourceUrl)) { + return; + } + String[] tokens = sourceUrl.split(SLASH); + var tokensLength = tokens.length; + var repositoryPath = sourceUrl; + if (tokensLength > 1) { + repositoryPath = String.join(SLASH, tokens[tokensLength - 2], tokens[tokensLength - 1]); + } + product.setRepositoryName(repositoryPath); + product.setSourceUrl(sourceUrl); + } - private static Meta jsonDecode(GHContent ghContent) throws IOException { - return MAPPER.readValue(ghContent.read().readAllBytes(), Meta.class); - } + private static Meta jsonDecode(GHContent ghContent) throws IOException { + return MAPPER.readValue(ghContent.read().readAllBytes(), Meta.class); + } } diff --git a/src/main/java/com/axonivy/market/github/model/ArchivedArtifact.java b/src/main/java/com/axonivy/market/github/model/ArchivedArtifact.java new file mode 100644 index 000000000..1bde19a0e --- /dev/null +++ b/src/main/java/com/axonivy/market/github/model/ArchivedArtifact.java @@ -0,0 +1,24 @@ +package com.axonivy.market.github.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ArchivedArtifact implements Serializable { + private static final long serialVersionUID = 1L; + private String lastVersion; + private String groupId; + private String artifactId; +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/github/model/MavenArtifact.java b/src/main/java/com/axonivy/market/github/model/MavenArtifact.java index 14af356b8..811b7917b 100644 --- a/src/main/java/com/axonivy/market/github/model/MavenArtifact.java +++ b/src/main/java/com/axonivy/market/github/model/MavenArtifact.java @@ -2,11 +2,14 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; - import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.springframework.data.annotation.Transient; + +import java.io.Serializable; +import java.util.List; @Getter @Setter @@ -14,10 +17,15 @@ @AllArgsConstructor @JsonInclude(JsonInclude.Include.NON_EMPTY) @JsonIgnoreProperties(ignoreUnknown = true) -public class MavenArtifact { - private String repoUrl; - private String name; - private String groupId; - private String artifactId; - private String type; +public class MavenArtifact implements Serializable { + private static final long serialVersionUID = 1L; + private String repoUrl; + private String name; + private String groupId; + private String artifactId; + private String type; + private Boolean isDependency; + @Transient + private Boolean isProductArtifact; + private List archivedArtifacts; } diff --git a/src/main/java/com/axonivy/market/github/service/GHAxonIvyProductRepoService.java b/src/main/java/com/axonivy/market/github/service/GHAxonIvyProductRepoService.java index 1665799de..3a9e85180 100644 --- a/src/main/java/com/axonivy/market/github/service/GHAxonIvyProductRepoService.java +++ b/src/main/java/com/axonivy/market/github/service/GHAxonIvyProductRepoService.java @@ -1,5 +1,6 @@ package com.axonivy.market.github.service; +import com.axonivy.market.github.model.MavenArtifact; import org.kohsuke.github.GHContent; import org.kohsuke.github.GHTag; @@ -11,4 +12,6 @@ public interface GHAxonIvyProductRepoService { GHContent getContentFromGHRepoAndTag(String repoName, String filePath, String tagVersion); List getAllTagsFromRepoName(String repoName) throws IOException; -} + + List convertProductJsonToMavenProductInfo(GHContent content) throws IOException; +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java b/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java index def4bd9b4..6b6b8c38e 100644 --- a/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java +++ b/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java @@ -1,49 +1,127 @@ package com.axonivy.market.github.service.impl; -import java.io.IOException; -import java.util.List; - +import com.axonivy.market.constants.GitHubConstants; +import com.axonivy.market.constants.ProductJsonConstants; +import com.axonivy.market.github.model.MavenArtifact; +import com.axonivy.market.github.service.GHAxonIvyProductRepoService; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.log4j.Log4j2; import org.kohsuke.github.GHContent; import org.kohsuke.github.GHOrganization; import org.kohsuke.github.GHTag; import org.springframework.stereotype.Service; -import com.axonivy.market.constants.GitHubConstants; -import com.axonivy.market.github.service.GHAxonIvyProductRepoService; import com.axonivy.market.github.service.GitHubService; -import lombok.extern.log4j.Log4j2; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; @Log4j2 @Service public class GHAxonIvyProductRepoServiceImpl implements GHAxonIvyProductRepoService { - private GHOrganization organization; - - private final GitHubService gitHubService; - - public GHAxonIvyProductRepoServiceImpl(GitHubService gitHubService) { - this.gitHubService = gitHubService; - } - - @Override - public GHContent getContentFromGHRepoAndTag(String repoName, String filePath, String tagVersion) { - try { - return getOrganization().getRepository(repoName).getFileContent(filePath, tagVersion); - } catch (IOException e) { - log.error("Cannot Get Content From File Directory", e); - return null; - } - } - - private GHOrganization getOrganization() throws IOException { - if (organization == null) { - organization = gitHubService.getOrganization(GitHubConstants.AXONIVY_MARKET_ORGANIZATION_NAME); - } - return organization; - } - - @Override - public List getAllTagsFromRepoName(String repoName) throws IOException { - return getOrganization().getRepository(repoName).listTags().toList(); - } + private GHOrganization organization; + private final GitHubService gitHubService; + private String repoUrl; + private static final ObjectMapper objectMapper = new ObjectMapper(); + + public GHAxonIvyProductRepoServiceImpl(GitHubService gitHubService) { + this.gitHubService = gitHubService; + } + + @Override + public List convertProductJsonToMavenProductInfo(GHContent content) throws IOException { + List artifacts = new ArrayList<>(); + InputStream contentStream = extractedContentStream(content); + if (Objects.isNull(contentStream)) { + return artifacts; + } + + JsonNode rootNode = objectMapper.readTree(contentStream); + JsonNode installersNode = rootNode.path(ProductJsonConstants.INSTALLERS); + + for (JsonNode mavenNode : installersNode) { + JsonNode dataNode = mavenNode.path(ProductJsonConstants.DATA); + + // Not convert to artifact if id of node is not maven-import or maven-dependency + List installerIdsToDisplay = List.of(ProductJsonConstants.MAVEN_DEPENDENCY_INSTALLER_ID, + ProductJsonConstants.MAVEN_IMPORT_INSTALLER_ID); + if (!installerIdsToDisplay.contains(mavenNode.path(ProductJsonConstants.ID).asText())) { + continue; + } + + // Extract repository URL + JsonNode repositoriesNode = dataNode.path(ProductJsonConstants.REPOSITORIES); + repoUrl = repositoriesNode.get(0).path(ProductJsonConstants.URL).asText(); + + // Process projects + if (dataNode.has(ProductJsonConstants.PROJECTS)) { + extractMavenArtifactFromJsonNode(dataNode, false, artifacts); + } + + // Process dependencies + if (dataNode.has(ProductJsonConstants.DEPENDENCIES)) { + extractMavenArtifactFromJsonNode(dataNode, true, artifacts); + } + } + return artifacts; + } + + public InputStream extractedContentStream(GHContent content) { + try { + return content.read(); + } catch (IOException | NullPointerException e) { + log.warn("Can not read the current content: {}", e.getMessage()); + return null; + } + } + + public void extractMavenArtifactFromJsonNode(JsonNode dataNode, boolean isDependency, + List artifacts) { + String nodeName = ProductJsonConstants.PROJECTS; + if (isDependency) { + nodeName = ProductJsonConstants.DEPENDENCIES; + } + JsonNode dependenciesNode = dataNode.path(nodeName); + for (JsonNode dependencyNode : dependenciesNode) { + MavenArtifact artifact = createArtifactFromJsonNode(dependencyNode, repoUrl, isDependency); + artifacts.add(artifact); + } + } + + public MavenArtifact createArtifactFromJsonNode(JsonNode node, String repoUrl, boolean isDependency) { + MavenArtifact artifact = new MavenArtifact(); + artifact.setRepoUrl(repoUrl); + artifact.setIsDependency(isDependency); + artifact.setGroupId(node.path(ProductJsonConstants.GROUP_ID).asText()); + artifact.setArtifactId(node.path(ProductJsonConstants.ARTIFACT_ID).asText()); + artifact.setType(node.path(ProductJsonConstants.TYPE).asText()); + artifact.setIsProductArtifact(true); + return artifact; + } + + @Override + public GHContent getContentFromGHRepoAndTag(String repoName, String filePath, String tagVersion) { + try { + return getOrganization().getRepository(repoName).getFileContent(filePath, tagVersion); + } catch (IOException e) { + log.error("Cannot Get Content From File Directory", e); + return null; + } + } + + public GHOrganization getOrganization() throws IOException { + if (organization == null) { + organization = gitHubService.getOrganization(GitHubConstants.AXONIVY_MARKET_ORGANIZATION_NAME); + } + return organization; + } + + @Override + public List getAllTagsFromRepoName(String repoName) throws IOException { + return getOrganization().getRepository(repoName).listTags().toList(); + } } diff --git a/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java b/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java index e4f38e7e5..b1a069ed5 100644 --- a/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java +++ b/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java @@ -49,4 +49,4 @@ public GHContent getGHContent(GHRepository ghRepository, String path) throws IOE Assert.notNull(ghRepository, "Repository must not be null"); return ghRepository.getFileContent(path); } -} +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/model/MavenArtifactVersionModel.java b/src/main/java/com/axonivy/market/model/MavenArtifactVersionModel.java new file mode 100644 index 000000000..3cb4ee1d7 --- /dev/null +++ b/src/main/java/com/axonivy/market/model/MavenArtifactVersionModel.java @@ -0,0 +1,18 @@ +package com.axonivy.market.model; + +import com.axonivy.market.entity.MavenArtifactModel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class MavenArtifactVersionModel { + private String version; + private List artifactsByVersion; +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/model/ProductModel.java b/src/main/java/com/axonivy/market/model/ProductModel.java index e088f1230..f11a62244 100644 --- a/src/main/java/com/axonivy/market/model/ProductModel.java +++ b/src/main/java/com/axonivy/market/model/ProductModel.java @@ -20,23 +20,23 @@ @Relation(collectionRelation = "products", itemRelation = "product") @JsonInclude(Include.NON_NULL) public class ProductModel extends RepresentationModel { - private String id; - private String name; - private String shortDescription; - private String logoUrl; - private String type; - private List tags; + private String id; + private String name; + private String shortDescription; + private String logoUrl; + private String type; + private List tags; - @Override - public int hashCode() { - return new HashCodeBuilder().append(id).hashCode(); - } + @Override + public int hashCode() { + return new HashCodeBuilder().append(id).hashCode(); + } - @Override - public boolean equals(Object obj) { - if (obj == null || this.getClass() != obj.getClass()) { - return false; - } - return new EqualsBuilder().append(id, ((ProductModel) obj).getId()).isEquals(); - } + @Override + public boolean equals(Object obj) { + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } + return new EqualsBuilder().append(id, ((ProductModel) obj).getId()).isEquals(); + } } diff --git a/src/main/java/com/axonivy/market/repository/MavenArtifactVersionRepository.java b/src/main/java/com/axonivy/market/repository/MavenArtifactVersionRepository.java new file mode 100644 index 000000000..15dd9cb03 --- /dev/null +++ b/src/main/java/com/axonivy/market/repository/MavenArtifactVersionRepository.java @@ -0,0 +1,9 @@ +package com.axonivy.market.repository; + +import com.axonivy.market.entity.MavenArtifactVersion; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface MavenArtifactVersionRepository extends MongoRepository { +} diff --git a/src/main/java/com/axonivy/market/repository/ProductRepository.java b/src/main/java/com/axonivy/market/repository/ProductRepository.java index 238cd774f..1b18bad04 100644 --- a/src/main/java/com/axonivy/market/repository/ProductRepository.java +++ b/src/main/java/com/axonivy/market/repository/ProductRepository.java @@ -8,6 +8,8 @@ import com.axonivy.market.entity.Product; +import java.util.Optional; + @Repository public interface ProductRepository extends MongoRepository { @@ -15,6 +17,8 @@ public interface ProductRepository extends MongoRepository { Product findByLogoUrl(String logoUrl); + Optional findById(String productId); + @Query("{'marketDirectory': {$regex : ?0, $options: 'i'}}") Product findByMarketDirectoryRegex(String search); diff --git a/src/main/java/com/axonivy/market/service/VersionService.java b/src/main/java/com/axonivy/market/service/VersionService.java new file mode 100644 index 000000000..214e7b669 --- /dev/null +++ b/src/main/java/com/axonivy/market/service/VersionService.java @@ -0,0 +1,17 @@ +package com.axonivy.market.service; + +import com.axonivy.market.model.MavenArtifactVersionModel; + +import java.util.List; + +public interface VersionService { + + List getVersionsToDisplay(Boolean isShowDevVersion, String designerVersion); + + List getVersionsFromArtifactDetails(String repoUrl, String groupId, String artifactId); + + String buildMavenMetadataUrlFromArtifact(String repoUrl, String groupId, String artifactId); + + List getArtifactsAndVersionToDisplay(String productId, Boolean isShowDevVersion, + String designerVersion); +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java b/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java new file mode 100644 index 000000000..f1aeaba06 --- /dev/null +++ b/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java @@ -0,0 +1,350 @@ +package com.axonivy.market.service.impl; + +import com.axonivy.market.constants.GitHubConstants; +import com.axonivy.market.constants.MavenConstants; +import com.axonivy.market.constants.NonStandardProductPackageConstants; +import com.axonivy.market.entity.MavenArtifactVersion; +import com.axonivy.market.entity.Product; +import com.axonivy.market.github.model.ArchivedArtifact; +import com.axonivy.market.github.model.MavenArtifact; +import com.axonivy.market.entity.MavenArtifactModel; +import com.axonivy.market.github.service.GHAxonIvyProductRepoService; +import com.axonivy.market.model.MavenArtifactVersionModel; +import com.axonivy.market.repository.MavenArtifactVersionRepository; +import com.axonivy.market.repository.ProductRepository; +import com.axonivy.market.service.VersionService; +import com.axonivy.market.comparator.ArchivedArtifactsComparator; +import com.axonivy.market.comparator.LatestVersionComparator; +import com.axonivy.market.utils.XmlReaderUtils; +import lombok.Getter; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.kohsuke.github.GHContent; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Log4j2 +@Service +@Getter +public class VersionServiceImpl implements VersionService { + + private final GHAxonIvyProductRepoService gitHubService; + private final MavenArtifactVersionRepository mavenArtifactVersionRepository; + private final ProductRepository productRepository; + @Getter + private String repoName; + private Map> archivedArtifactsMap; + private List artifactsFromMeta; + private MavenArtifactVersion proceedDataCache; + private MavenArtifact metaProductArtifact; + private final LatestVersionComparator latestVersionComparator = new LatestVersionComparator(); + @Getter + private String productJsonFilePath; + private String productId; + + public VersionServiceImpl(GHAxonIvyProductRepoService gitHubService, + MavenArtifactVersionRepository mavenArtifactVersionRepository, ProductRepository productRepository) { + this.gitHubService = gitHubService; + this.mavenArtifactVersionRepository = mavenArtifactVersionRepository; + this.productRepository = productRepository; + + } + + private void resetData() { + repoName = null; + archivedArtifactsMap = new HashMap<>(); + artifactsFromMeta = Collections.emptyList(); + proceedDataCache = null; + metaProductArtifact = null; + productJsonFilePath = null; + productId = null; + + } + + public List getArtifactsAndVersionToDisplay(String productId, Boolean isShowDevVersion, + String designerVersion) { + List results = new ArrayList<>(); + resetData(); + + this.productId = productId; + artifactsFromMeta = getProductMetaArtifacts(productId); + List versionsToDisplay = getVersionsToDisplay(isShowDevVersion, designerVersion); + proceedDataCache = mavenArtifactVersionRepository.findById(productId) + .orElse(new MavenArtifactVersion(productId)); + metaProductArtifact = artifactsFromMeta.stream() + .filter(artifact -> artifact.getArtifactId().endsWith(MavenConstants.PRODUCT_ARTIFACT_POSTFIX)) + .findAny().orElse(new MavenArtifact()); + + sanitizeMetaArtifactBeforeHandle(); + + boolean isNewVersionDetected = handleArtifactForVersionToDisplay(versionsToDisplay, results); + if (isNewVersionDetected) { + mavenArtifactVersionRepository.save(proceedDataCache); + } + return results; + } + + public boolean handleArtifactForVersionToDisplay(List versionsToDisplay, + List result) { + boolean isNewVersionDetected = false; + for (String version : versionsToDisplay) { + List artifactsInVersion = convertMavenArtifactsToModels(artifactsFromMeta, version); + List productArtifactModels = proceedDataCache.getProductArtifactWithVersionReleased() + .get(version); + if (productArtifactModels == null) { + isNewVersionDetected = true; + productArtifactModels = updateArtifactsInVersionWithProductArtifact(version); + } + artifactsInVersion.addAll(productArtifactModels); + result.add(new MavenArtifactVersionModel(version, artifactsInVersion.stream().distinct().toList())); + } + return isNewVersionDetected; + } + + public List updateArtifactsInVersionWithProductArtifact(String version) { + List productArtifactModels = convertMavenArtifactsToModels(getProductJsonByVersion(version), + version); + proceedDataCache.getVersions().add(version); + proceedDataCache.getProductArtifactWithVersionReleased().put(version, productArtifactModels); + return productArtifactModels; + } + + public List getProductMetaArtifacts(String productId) { + Product productInfo = productRepository.findById(productId).orElse(new Product()); + String fullRepoName = productInfo.getRepositoryName(); + if (StringUtils.isNotEmpty(fullRepoName)) { + repoName = getRepoNameFromMarketRepo(fullRepoName); + } + return Optional.ofNullable(productInfo.getArtifacts()).orElse(new ArrayList<>()); + } + + public void sanitizeMetaArtifactBeforeHandle() { + artifactsFromMeta.remove(metaProductArtifact); + artifactsFromMeta.forEach(artifact -> { + List archivedArtifacts = new ArrayList<>( + Optional.ofNullable(artifact.getArchivedArtifacts()).orElse(Collections.emptyList()).stream() + .sorted(new ArchivedArtifactsComparator()).toList()); + Collections.reverse(archivedArtifacts); + archivedArtifactsMap.put(artifact.getArtifactId(), archivedArtifacts); + }); + } + + @Override + public List getVersionsToDisplay(Boolean isShowDevVersion, String designerVersion) { + List versions = getVersionsFromMavenArtifacts(); + Stream versionStream = versions.stream(); + if (BooleanUtils.isTrue(isShowDevVersion)) { + return versionStream.filter(version -> isOfficialVersionOrUnReleasedDevVersion(versions, version)) + .sorted(new LatestVersionComparator()).toList(); + } + if (StringUtils.isNotBlank(designerVersion)) { + return versionStream.filter(version -> isMatchWithDesignerVersion(version, designerVersion)).toList(); + } + return versions.stream().filter(this::isReleasedVersion).sorted(new LatestVersionComparator()).toList(); + } + + public List getVersionsFromMavenArtifacts() { + Set versions = new HashSet<>(); + for (MavenArtifact artifact : artifactsFromMeta) { + versions.addAll(getVersionsFromArtifactDetails(artifact.getRepoUrl(), artifact.getGroupId(), + artifact.getArtifactId())); + Optional.ofNullable(artifact.getArchivedArtifacts()).orElse(Collections.emptyList()) + .forEach(archivedArtifact -> versions.addAll(getVersionsFromArtifactDetails(artifact.getRepoUrl(), + archivedArtifact.getGroupId(), archivedArtifact.getArtifactId()))); + } + List versionList = new ArrayList<>(versions); + versionList.sort(new LatestVersionComparator()); + return versionList; + } + + @Override + public List getVersionsFromArtifactDetails(String repoUrl, String groupId, String artifactID) { + List versions = new ArrayList<>(); + String baseUrl = buildMavenMetadataUrlFromArtifact(repoUrl, groupId, artifactID); + if (StringUtils.isNotBlank(baseUrl)) { + versions.addAll(XmlReaderUtils.readXMLFromUrl(baseUrl)); + } + return versions; + } + + @Override + public String buildMavenMetadataUrlFromArtifact(String repoUrl, String groupId, String artifactID) { + if (StringUtils.isAnyBlank(groupId, artifactID)) { + return StringUtils.EMPTY; + } + repoUrl = Optional.ofNullable(repoUrl).orElse(MavenConstants.DEFAULT_IVY_MAVEN_BASE_URL); + groupId = groupId.replace(MavenConstants.DOT_SEPARATOR, MavenConstants.GROUP_ID_URL_SEPARATOR); + return String.format(MavenConstants.METADATA_URL_FORMAT, repoUrl, groupId, artifactID); + } + + public String getBugfixVersion(String version) { + + if (isSnapshotVersion(version)) { + version = version.replace(MavenConstants.SNAPSHOT_RELEASE_POSTFIX, StringUtils.EMPTY); + } else if (isSprintVersion(version)) { + version = version.split(MavenConstants.SPRINT_RELEASE_POSTFIX)[0]; + } + String[] segments = version.split("\\."); + if (segments.length >= 3) { + segments[2] = segments[2].split(MavenConstants.ARTIFACT_ID_SEPARATOR)[0]; + return segments[0] + MavenConstants.DOT_SEPARATOR + segments[1] + MavenConstants.DOT_SEPARATOR + + segments[2]; + } + return version; + } + + public boolean isOfficialVersionOrUnReleasedDevVersion(List versions, String version) { + if (isReleasedVersion(version)) { + return true; + } + String bugfixVersion; + if (isSnapshotVersion(version)) { + bugfixVersion = getBugfixVersion( + version.replace(MavenConstants.SNAPSHOT_RELEASE_POSTFIX, StringUtils.EMPTY)); + } else { + bugfixVersion = getBugfixVersion(version.split(MavenConstants.SPRINT_RELEASE_POSTFIX)[0]); + } + return versions.stream().noneMatch(currentVersion -> !currentVersion.equals(version) + && getBugfixVersion(currentVersion).equals(bugfixVersion)); + } + + public boolean isSnapshotVersion(String version) { + return version.endsWith(MavenConstants.SNAPSHOT_RELEASE_POSTFIX); + } + + public boolean isSprintVersion(String version) { + return version.contains(MavenConstants.SPRINT_RELEASE_POSTFIX); + } + + public boolean isReleasedVersion(String version) { + return !(isSprintVersion(version) || isSnapshotVersion(version)); + } + + public boolean isMatchWithDesignerVersion(String version, String designerVersion) { + return isReleasedVersion(version) && version.startsWith(designerVersion); + } + + public List getProductJsonByVersion(String version) { + List result = new ArrayList<>(); + String versionTag = buildProductJsonFilePath(version); + try { + GHContent productJsonContent = gitHubService.getContentFromGHRepoAndTag(repoName, productJsonFilePath, + versionTag); + if (Objects.isNull(productJsonContent)) { + return result; + } + result = gitHubService.convertProductJsonToMavenProductInfo(productJsonContent); + } catch (IOException e) { + log.warn("Can not get the product.json from repo {} by path in {} version {}", repoName, + productJsonFilePath, versionTag); + } + return result; + } + + public String buildProductJsonFilePath(String version) { + String versionTag = "v" + version; + String pathToProductJsonFileFromTagContent = metaProductArtifact.getArtifactId(); + switch (productId) { + case NonStandardProductPackageConstants.PORTAL: + pathToProductJsonFileFromTagContent = "AxonIvyPortal/portal-product"; + versionTag = version; + break; + case NonStandardProductPackageConstants.CONNECTIVITY_FEATURE: + pathToProductJsonFileFromTagContent = "connectivity/connectivity-demos-product"; + break; + case NonStandardProductPackageConstants.ERROR_HANDLING: + pathToProductJsonFileFromTagContent = "error-handling/error-handling-demos-product"; + break; + case NonStandardProductPackageConstants.WORKFLOW_DEMO: + pathToProductJsonFileFromTagContent = "workflow/workflow-demos-product"; + break; + case NonStandardProductPackageConstants.MICROSOFT_365: + pathToProductJsonFileFromTagContent = "msgraph-connector-product/products/msgraph-connector"; + break; + case NonStandardProductPackageConstants.HTML_DIALOG_DEMO: + pathToProductJsonFileFromTagContent = "html-dialog/html-dialog-demos-product"; + break; + case NonStandardProductPackageConstants.RULE_ENGINE_DEMOS: + pathToProductJsonFileFromTagContent = "rule-engine/rule-engine-demos-product"; + break; + default: + break; + } + productJsonFilePath = String.format(GitHubConstants.PRODUCT_JSON_FILE_PATH_FORMAT, + pathToProductJsonFileFromTagContent); + return versionTag; + } + + public MavenArtifactModel convertMavenArtifactToModel(MavenArtifact artifact, String version) { + String artifactName = artifact.getName(); + if (StringUtils.isBlank(artifactName)) { + artifactName = convertArtifactIdToName(artifact.getArtifactId()); + } + artifact.setType(Optional.ofNullable(artifact.getType()).orElse("iar")); + artifactName = String.format(MavenConstants.ARTIFACT_NAME_FORMAT, artifactName, artifact.getType()); + return new MavenArtifactModel(artifactName, buildDownloadUrlFromArtifactAndVersion(artifact, version), + artifact.getIsProductArtifact()); + } + + public List convertMavenArtifactsToModels(List artifacts, String version) { + List results = new ArrayList<>(); + if (!CollectionUtils.isEmpty(artifacts)) { + for (MavenArtifact artifact : artifacts) { + MavenArtifactModel mavenArtifactModel = convertMavenArtifactToModel(artifact, version); + results.add(mavenArtifactModel); + } + } + return results; + } + + public String buildDownloadUrlFromArtifactAndVersion(MavenArtifact artifact, String version) { + String groupIdByVersion = artifact.getGroupId(); + String artifactIdByVersion = artifact.getArtifactId(); + String repoUrl = Optional.ofNullable(artifact.getRepoUrl()).orElse(MavenConstants.DEFAULT_IVY_MAVEN_BASE_URL); + ArchivedArtifact archivedArtifactBestMatchVersion = findArchivedArtifactInfoBestMatchWithVersion( + artifact.getArtifactId(), version); + + if (Objects.nonNull(archivedArtifactBestMatchVersion)) { + groupIdByVersion = archivedArtifactBestMatchVersion.getGroupId(); + artifactIdByVersion = archivedArtifactBestMatchVersion.getArtifactId(); + } + groupIdByVersion = groupIdByVersion.replace(MavenConstants.DOT_SEPARATOR, + MavenConstants.GROUP_ID_URL_SEPARATOR); + return String.format(MavenConstants.ARTIFACT_DOWNLOAD_URL_FORMAT, repoUrl, groupIdByVersion, + artifactIdByVersion, version, artifactIdByVersion, version, artifact.getType()); + } + + public ArchivedArtifact findArchivedArtifactInfoBestMatchWithVersion(String artifactId, String version) { + List archivedArtifacts = archivedArtifactsMap.get(artifactId); + + if (CollectionUtils.isEmpty(archivedArtifacts)) { + return null; + } + for (ArchivedArtifact archivedArtifact : archivedArtifacts) { + if (latestVersionComparator.compare(archivedArtifact.getLastVersion(), version) <= 0) { + return archivedArtifact; + } + } + return null; + } + + public String convertArtifactIdToName(String artifactId) { + if (StringUtils.isBlank(artifactId)) { + return StringUtils.EMPTY; + } + return Arrays.stream(artifactId.split(MavenConstants.ARTIFACT_ID_SEPARATOR)) + .map(part -> part.substring(0, 1).toUpperCase() + part.substring(1).toLowerCase()) + .collect(Collectors.joining(MavenConstants.ARTIFACT_NAME_SEPARATOR)); + } + + public String getRepoNameFromMarketRepo(String fullRepoName) { + String[] repoNamePart = fullRepoName.split("/"); + return repoNamePart[repoNamePart.length - 1]; + } +} \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/utils/XmlReaderUtils.java b/src/main/java/com/axonivy/market/utils/XmlReaderUtils.java new file mode 100644 index 000000000..8f7f6a6bc --- /dev/null +++ b/src/main/java/com/axonivy/market/utils/XmlReaderUtils.java @@ -0,0 +1,59 @@ +package com.axonivy.market.utils; + +import com.axonivy.market.constants.MavenConstants; +import lombok.extern.log4j.Log4j2; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathFactory; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Log4j2 +public class XmlReaderUtils { + private static final RestTemplate restTemplate = new RestTemplate(); + + private XmlReaderUtils() { + } + + public static List readXMLFromUrl(String url) { + List versions = new ArrayList<>(); + try { + String xmlData = restTemplate.getForObject(url, String.class); + extractVersions(xmlData, versions); + } catch (HttpClientErrorException e) { + log.error(e.getMessage()); + } + return versions; + } + + public static void extractVersions(String xmlData, List versions) { + try { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document document = builder.parse(new InputSource(new StringReader(xmlData))); + + XPath xpath = XPathFactory.newInstance().newXPath(); + XPathExpression expr = xpath.compile(MavenConstants.VERSION_EXTRACT_FORMAT_FROM_METADATA_FILE); + + Object result = expr.evaluate(document, XPathConstants.NODESET); + NodeList versionNodes = (NodeList) result; + + for (int i = 0; i < versionNodes.getLength(); i++) { + versions.add(Optional.ofNullable(versionNodes.item(i)).map(Node::getTextContent).orElse(null)); + } + } catch (Exception e) { + log.error(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/axonivy/market/controller/AppControllerTest.java b/src/test/java/com/axonivy/market/controller/AppControllerTest.java index b46637a6e..104006d9d 100644 --- a/src/test/java/com/axonivy/market/controller/AppControllerTest.java +++ b/src/test/java/com/axonivy/market/controller/AppControllerTest.java @@ -22,5 +22,4 @@ void testRoot() throws Exception { assertTrue(response.hasBody()); assertTrue(response.getBody().getMessageDetails().contains("/swagger-ui/index.html")); } - -} +} \ No newline at end of file diff --git a/src/test/java/com/axonivy/market/controller/ProductControllerTest.java b/src/test/java/com/axonivy/market/controller/ProductControllerTest.java index 68e846755..a2c6f3ec7 100644 --- a/src/test/java/com/axonivy/market/controller/ProductControllerTest.java +++ b/src/test/java/com/axonivy/market/controller/ProductControllerTest.java @@ -102,4 +102,4 @@ private Product createProductMock() { mockProduct.setTags(List.of("AI")); return mockProduct; } -} +} \ No newline at end of file diff --git a/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java b/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java index b8babd511..1779befd8 100644 --- a/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java +++ b/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java @@ -2,11 +2,20 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import com.axonivy.market.model.MavenArtifactVersionModel; +import com.axonivy.market.service.VersionService; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.List; +import java.util.Objects; @ExtendWith(MockitoExtension.class) class ProductDetailsControllerTest { @@ -14,10 +23,22 @@ class ProductDetailsControllerTest { @InjectMocks private ProductDetailsController productDetailsController; + @Mock + VersionService versionService; + @Test void testFindProduct() { var result = productDetailsController.findProduct("", ""); assertEquals(HttpStatus.NOT_FOUND, result.getStatusCode()); } -} + @Test + void testFindProductVersionsById(){ + List models = List.of(new MavenArtifactVersionModel()); + Mockito.when(versionService.getArtifactsAndVersionToDisplay(Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyString())).thenReturn(models); + ResponseEntity> result = productDetailsController.findProductVersionsById("protal", true, "10.0.1"); + Assertions.assertEquals(HttpStatus.OK, result.getStatusCode()); + Assertions.assertEquals(1, Objects.requireNonNull(result.getBody()).size()); + Assertions.assertEquals(models, result.getBody()); + } +} \ No newline at end of file diff --git a/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java b/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java index 7c8099b91..96d27d5bf 100644 --- a/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java +++ b/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java @@ -10,6 +10,8 @@ import java.io.IOException; import java.io.InputStream; +import com.axonivy.market.github.model.Meta; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.kohsuke.github.GHContent; @@ -20,31 +22,54 @@ @ExtendWith(MockitoExtension.class) class ProductFactoryTest { - private static final String DUMMY_LOGO_URL = "https://raw.githubusercontent.com/axonivy-market/market/master/market/connector/amazon-comprehend-connector/logo.png"; - - @Test - void testMappingByGHContent() throws IOException { - Product product = new Product(); - GHContent mockContent = mock(GHContent.class); - when(mockContent.getName()).thenReturn(CommonConstants.META_FILE); - InputStream inputStream = this.getClass().getResourceAsStream(SLASH.concat(META_FILE)); - when(mockContent.read()).thenReturn(inputStream); - var result = ProductFactory.mappingByGHContent(product, mockContent); - assertNotEquals(null, result); - assertEquals("Amazon Comprehend", result.getName()); - } - - @Test - void testMappingLogo() throws IOException { - Product product = new Product(); - GHContent content = mock(GHContent.class); - when(content.getName()).thenReturn(CommonConstants.LOGO_FILE); - var result = ProductFactory.mappingByGHContent(product, content); - assertNotEquals(null, result); - - when(content.getName()).thenReturn(CommonConstants.LOGO_FILE); - when(content.getDownloadUrl()).thenReturn(DUMMY_LOGO_URL); - result = ProductFactory.mappingByGHContent(product, content); - assertNotEquals(null, result); - } + private static final String DUMMY_LOGO_URL = "https://raw.githubusercontent.com/axonivy-market/market/master/market/connector/amazon-comprehend-connector/logo.png"; + + @Test + void testMappingByGHContent() throws IOException { + Product product = new Product(); + GHContent mockContent = mock(GHContent.class); + var result = ProductFactory.mappingByGHContent(product, null); + assertEquals(product, result); + when(mockContent.getName()).thenReturn(CommonConstants.META_FILE); + InputStream inputStream = this.getClass().getResourceAsStream(SLASH.concat(META_FILE)); + when(mockContent.read()).thenReturn(inputStream); + result = ProductFactory.mappingByGHContent(product, mockContent); + assertNotEquals(null, result); + assertEquals("Amazon Comprehend", result.getName()); + } + + @Test + void testMappingLogo() throws IOException { + Product product = new Product(); + GHContent content = mock(GHContent.class); + when(content.getName()).thenReturn(CommonConstants.LOGO_FILE); + var result = ProductFactory.mappingByGHContent(product, content); + assertNotEquals(null, result); + + when(content.getName()).thenReturn(CommonConstants.LOGO_FILE); + when(content.getDownloadUrl()).thenReturn(DUMMY_LOGO_URL); + result = ProductFactory.mappingByGHContent(product, content); + assertNotEquals(null, result); + } + + @Test + void testExtractSourceUrl() throws IOException { + Product product = new Product(); + Meta meta = new Meta(); + ProductFactory.extractSourceUrl(product, meta); + Assertions.assertNull(product.getRepositoryName()); + Assertions.assertNull(product.getSourceUrl()); + + String sourceUrl = "https://github.com/axonivy-market/alfresco-connector"; + meta.setSourceUrl(sourceUrl); + ProductFactory.extractSourceUrl(product, meta); + Assertions.assertEquals("axonivy-market/alfresco-connector", product.getRepositoryName()); + Assertions.assertEquals(sourceUrl, product.getSourceUrl()); + + sourceUrl = "portal"; + meta.setSourceUrl(sourceUrl); + ProductFactory.extractSourceUrl(product, meta); + Assertions.assertEquals(sourceUrl, product.getRepositoryName()); + Assertions.assertEquals(sourceUrl, product.getSourceUrl()); + } } diff --git a/src/test/java/com/axonivy/market/github/service/GHAxonIvyProductRepoServiceImplTest.java b/src/test/java/com/axonivy/market/github/service/GHAxonIvyProductRepoServiceImplTest.java new file mode 100644 index 000000000..28293329d --- /dev/null +++ b/src/test/java/com/axonivy/market/github/service/GHAxonIvyProductRepoServiceImplTest.java @@ -0,0 +1,242 @@ +package com.axonivy.market.github.service; + +import com.axonivy.market.constants.ProductJsonConstants; +import com.axonivy.market.github.model.MavenArtifact; +import com.axonivy.market.github.service.impl.GHAxonIvyProductRepoServiceImpl; +import com.fasterxml.jackson.databind.JsonNode; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.kohsuke.github.*; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GHAxonIvyProductRepoServiceImplTest { + + private static final String DUMMY_TAG = "v1.0.0"; + + @Mock + PagedIterable listTags; + + @Mock + GHRepository ghRepository; + + @Mock + GitHubService githubService; + + @Mock + GHOrganization organization; + + @Mock + JsonNode dataNode; + + @Mock + JsonNode childNode; + + @Mock + GHContent content = new GHContent(); + + @InjectMocks + @Spy + private GHAxonIvyProductRepoServiceImpl axonivyProductRepoServiceImpl; + + void setup() throws IOException { + var mockGHOrganization = mock(GHOrganization.class); + when(githubService.getOrganization(any())).thenReturn(mockGHOrganization); + when(mockGHOrganization.getRepository(any())).thenReturn(ghRepository); + } + + @Test + void testAllTagsFromRepoName() throws IOException { + setup(); + var mockTag = mock(GHTag.class); + when(mockTag.getName()).thenReturn(DUMMY_TAG); + when(listTags.toList()).thenReturn(List.of(mockTag)); + when(ghRepository.listTags()).thenReturn(listTags); + var result = axonivyProductRepoServiceImpl.getAllTagsFromRepoName(""); + assertEquals(1, result.size()); + assertEquals(DUMMY_TAG, result.get(0).getName()); + } + + @Test + void testContentFromGHRepoAndTag() throws IOException { + setup(); + var result = axonivyProductRepoServiceImpl.getContentFromGHRepoAndTag("", null, null); + assertNull(result); + when(axonivyProductRepoServiceImpl.getOrganization()).thenThrow(IOException.class); + result = axonivyProductRepoServiceImpl.getContentFromGHRepoAndTag("", null, null); + assertNull(result); + } + + @Test + void testExtractMavenArtifactFromJsonNode() { + List artifacts = new ArrayList<>(); + boolean isDependency = true; + String nodeName = ProductJsonConstants.DEPENDENCIES; + + createListNodeForDataNoteByName(nodeName); + MavenArtifact mockArtifact = Mockito.mock(MavenArtifact.class); + Mockito.doReturn(mockArtifact).when(axonivyProductRepoServiceImpl).createArtifactFromJsonNode(childNode, null, + isDependency); + + axonivyProductRepoServiceImpl.extractMavenArtifactFromJsonNode(dataNode, isDependency, artifacts); + + assertEquals(1, artifacts.size()); + assertSame(mockArtifact, artifacts.get(0)); + + isDependency = false; + nodeName = ProductJsonConstants.PROJECTS; + createListNodeForDataNoteByName(nodeName); + + Mockito.doReturn(mockArtifact).when(axonivyProductRepoServiceImpl).createArtifactFromJsonNode(childNode, null, + isDependency); + + axonivyProductRepoServiceImpl.extractMavenArtifactFromJsonNode(dataNode, isDependency, artifacts); + + assertEquals(2, artifacts.size()); + assertSame(mockArtifact, artifacts.get(1)); + } + + private void createListNodeForDataNoteByName(String nodeName) { + JsonNode sectionNode = Mockito.mock(JsonNode.class); + Iterator iterator = Mockito.mock(Iterator.class); + Mockito.when(dataNode.path(nodeName)).thenReturn(sectionNode); + Mockito.when(sectionNode.iterator()).thenReturn(iterator); + Mockito.when(iterator.hasNext()).thenReturn(true, false); + Mockito.when(iterator.next()).thenReturn(childNode); + } + + @Test + void testCreateArtifactFromJsonNode() { + String repoUrl = "http://example.com/repo"; + boolean isDependency = true; + String groupId = "com.example"; + String artifactId = "example-artifact"; + String type = "jar"; + + JsonNode groupIdNode = Mockito.mock(JsonNode.class); + JsonNode artifactIdNode = Mockito.mock(JsonNode.class); + JsonNode typeNode = Mockito.mock(JsonNode.class); + Mockito.when(groupIdNode.asText()).thenReturn(groupId); + Mockito.when(artifactIdNode.asText()).thenReturn(artifactId); + Mockito.when(typeNode.asText()).thenReturn(type); + Mockito.when(dataNode.path(ProductJsonConstants.GROUP_ID)).thenReturn(groupIdNode); + Mockito.when(dataNode.path(ProductJsonConstants.ARTIFACT_ID)).thenReturn(artifactIdNode); + Mockito.when(dataNode.path(ProductJsonConstants.TYPE)).thenReturn(typeNode); + + MavenArtifact artifact = axonivyProductRepoServiceImpl.createArtifactFromJsonNode(dataNode, repoUrl, + isDependency); + + assertEquals(repoUrl, artifact.getRepoUrl()); + assertTrue(artifact.getIsDependency()); + assertEquals(groupId, artifact.getGroupId()); + assertEquals(artifactId, artifact.getArtifactId()); + assertEquals(type, artifact.getType()); + assertTrue(artifact.getIsProductArtifact()); + } + + @Test + void testConvertProductJsonToMavenProductInfo() throws IOException { + assertEquals(0, axonivyProductRepoServiceImpl.convertProductJsonToMavenProductInfo(null).size()); + assertEquals(0, axonivyProductRepoServiceImpl.convertProductJsonToMavenProductInfo(content).size()); + + InputStream inputStream = getMockInputStream(); + Mockito.when(axonivyProductRepoServiceImpl.extractedContentStream(content)).thenReturn(inputStream); + assertEquals(2, axonivyProductRepoServiceImpl.convertProductJsonToMavenProductInfo(content).size()); + inputStream = getMockInputStreamWithOutProjectAndDependency(); + Mockito.when(axonivyProductRepoServiceImpl.extractedContentStream(content)).thenReturn(inputStream); + assertEquals(0, axonivyProductRepoServiceImpl.convertProductJsonToMavenProductInfo(content).size()); + } + + private static InputStream getMockInputStream() { + String jsonContent = """ + { + "$schema": "https://json-schema.axonivy.com/market/10.0.0/product.json", + "installers": [ + { + "id": "maven-import", + "data": { + "projects": [ + { + "groupId": "com.axonivy.utils.bpmnstatistic", + "artifactId": "bpmn-statistic-demo", + "version": "${version}", + "type": "iar" + } + ], + "repositories": [ + { + "id": "maven.axonivy.com", + "url": "https://maven.axonivy.com", + "snapshots": { + "enabled": "true" + } + } + ] + } + }, + { + "id": "maven-dependency", + "data": { + "dependencies": [ + { + "groupId": "com.axonivy.utils.bpmnstatistic", + "artifactId": "bpmn-statistic", + "version": "${version}", + "type": "iar" + } + ], + "repositories": [ + { + "id": "maven.axonivy.com", + "url": "https://maven.axonivy.com", + "snapshots": { + "enabled": "true" + } + } + ] + } + } + ] + } + """; + return new ByteArrayInputStream(jsonContent.getBytes(StandardCharsets.UTF_8)); + } + + private static InputStream getMockInputStreamWithOutProjectAndDependency() { + String jsonContent = "{\n" + " \"installers\": [\n" + " {\n" + " \"data\": {\n" + + " \"repositories\": [\n" + " {\n" + + " \"url\": \"http://example.com/repo\"\n" + " }\n" + " ]\n" + " }\n" + + " }\n" + " ]\n" + "}"; + return new ByteArrayInputStream(jsonContent.getBytes(StandardCharsets.UTF_8)); + } + + @Test + void testExtractedContentStream() { + assertNull(axonivyProductRepoServiceImpl.extractedContentStream(null)); + assertNull(axonivyProductRepoServiceImpl.extractedContentStream(content)); + } + + @Test + void testGetOrganization() throws IOException { + Mockito.when(githubService.getOrganization(Mockito.anyString())).thenReturn(organization); + assertEquals(organization, axonivyProductRepoServiceImpl.getOrganization()); + assertEquals(organization, axonivyProductRepoServiceImpl.getOrganization()); + } +} diff --git a/src/test/java/com/axonivy/market/service/GHAxonIvyProductRepoServiceImplTest.java b/src/test/java/com/axonivy/market/service/GHAxonIvyProductRepoServiceImplTest.java deleted file mode 100644 index d5d8ba716..000000000 --- a/src/test/java/com/axonivy/market/service/GHAxonIvyProductRepoServiceImplTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.axonivy.market.service; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.kohsuke.github.GHOrganization; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GHTag; -import org.kohsuke.github.PagedIterable; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import com.axonivy.market.github.service.GitHubService; -import com.axonivy.market.github.service.impl.GHAxonIvyProductRepoServiceImpl; - -@ExtendWith(MockitoExtension.class) -class GHAxonIvyProductRepoServiceImplTest { - - private static final String DUMMY_TAG = "v1.0.0"; - - @Mock - PagedIterable listTags; - - @Mock - GHRepository ghRepository; - - @Mock - GitHubService gitHubService; - - @InjectMocks - private GHAxonIvyProductRepoServiceImpl axonivyProductRepoServiceImpl; - - @BeforeEach - void setup() throws IOException { - var mockGHOrganization = mock(GHOrganization.class); - when(mockGHOrganization.getRepository(any())).thenReturn(ghRepository); - when(gitHubService.getOrganization(any())).thenReturn(mockGHOrganization); - } - - @Test - void testAllTagsFromRepoName() throws IOException { - var mockTag = mock(GHTag.class); - when(mockTag.getName()).thenReturn(DUMMY_TAG); - when(listTags.toList()).thenReturn(List.of(mockTag)); - when(ghRepository.listTags()).thenReturn(listTags); - var result = axonivyProductRepoServiceImpl.getAllTagsFromRepoName(""); - assertEquals(1, result.size()); - assertEquals(DUMMY_TAG, result.get(0).getName()); - } - - @Test - void testContentFromGHRepoAndTag() { - var result = axonivyProductRepoServiceImpl.getContentFromGHRepoAndTag("", null, null); - assertEquals(null, result); - } -} diff --git a/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java b/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java index 159c43683..ea3bd0fe7 100644 --- a/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java +++ b/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java @@ -7,6 +7,7 @@ import java.io.IOException; +import com.axonivy.market.github.service.impl.GitHubServiceImpl; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.kohsuke.github.GHContent; @@ -16,8 +17,6 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import com.axonivy.market.github.service.impl.GitHubServiceImpl; - @ExtendWith(MockitoExtension.class) class GitHubServiceImplTest { private static final String DUMMY_API_URL = "https://api.github.com"; diff --git a/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java b/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java new file mode 100644 index 000000000..932b7a864 --- /dev/null +++ b/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java @@ -0,0 +1,565 @@ +package com.axonivy.market.service; + +import com.axonivy.market.constants.MavenConstants; +import com.axonivy.market.constants.NonStandardProductPackageConstants; +import com.axonivy.market.entity.MavenArtifactModel; +import com.axonivy.market.entity.MavenArtifactVersion; +import com.axonivy.market.entity.Product; +import com.axonivy.market.github.model.ArchivedArtifact; +import com.axonivy.market.github.model.MavenArtifact; +import com.axonivy.market.github.service.GHAxonIvyProductRepoService; +import com.axonivy.market.model.MavenArtifactVersionModel; +import com.axonivy.market.repository.MavenArtifactVersionRepository; +import com.axonivy.market.repository.ProductRepository; +import com.axonivy.market.service.impl.VersionServiceImpl; +import com.axonivy.market.utils.XmlReaderUtils; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.StringUtils; +import org.assertj.core.api.Fail; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.kohsuke.github.GHContent; +import org.mockito.*; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.io.IOException; +import java.util.*; + +@Log4j2 +@ExtendWith(MockitoExtension.class) +class VersionServiceImplTest { + private String repoName; + private Map> archivedArtifactsMap; + private List artifactsFromMeta; + private MavenArtifactVersion proceedDataCache; + private MavenArtifact metaProductArtifact; + @Spy + @InjectMocks + private VersionServiceImpl versionService; + + @Mock + private GHAxonIvyProductRepoService gitHubService; + + @Mock + private MavenArtifactVersionRepository mavenArtifactVersionRepository; + + @Mock + private ProductRepository productRepository; + + @BeforeEach() + void prepareBeforeTest() { + archivedArtifactsMap = new HashMap<>(); + artifactsFromMeta = new ArrayList<>(); + metaProductArtifact = new MavenArtifact(); + proceedDataCache = new MavenArtifactVersion(); + repoName = StringUtils.EMPTY; + ReflectionTestUtils.setField(versionService, "archivedArtifactsMap", archivedArtifactsMap); + ReflectionTestUtils.setField(versionService, "artifactsFromMeta", artifactsFromMeta); + ReflectionTestUtils.setField(versionService, "proceedDataCache", proceedDataCache); + ReflectionTestUtils.setField(versionService, "metaProductArtifact", metaProductArtifact); + } + + private void setUpArtifactFromMeta() { + String repoUrl = "https://maven.axonivy.com"; + String groupId = "com.axonivy.connector.adobe.acrobat.sign"; + String artifactId = "adobe-acrobat-sign-connector"; + metaProductArtifact.setGroupId(groupId); + metaProductArtifact.setArtifactId(artifactId); + metaProductArtifact.setIsProductArtifact(true); + MavenArtifact additionalMavenArtifact = new MavenArtifact(repoUrl, "", groupId, artifactId, "", null, null, + null); + artifactsFromMeta.add(metaProductArtifact); + artifactsFromMeta.add(additionalMavenArtifact); + } + + @Test + void testGetArtifactsAndVersionToDisplay() { + String productId = "adobe-acrobat-sign-connector"; + String targetVersion = "10.0.10"; + setUpArtifactFromMeta(); + when(versionService.getProductMetaArtifacts(Mockito.anyString())).thenReturn(artifactsFromMeta); + when(versionService.getVersionsToDisplay(Mockito.anyBoolean(), Mockito.anyString())) + .thenReturn(List.of(targetVersion)); + when(mavenArtifactVersionRepository.findById(Mockito.anyString())).thenReturn(Optional.empty()); + ArrayList artifactsInVersion = new ArrayList<>(); + artifactsInVersion.add(new MavenArtifactModel()); + when(versionService.convertMavenArtifactsToModels(Mockito.anyList(), Mockito.anyString())) + .thenReturn(artifactsInVersion); + Assertions.assertEquals(1, + versionService.getArtifactsAndVersionToDisplay(productId, false, targetVersion).size()); + + MavenArtifactVersion proceededData = new MavenArtifactVersion(); + proceededData.getProductArtifactWithVersionReleased().put(targetVersion, new ArrayList<>()); + when(mavenArtifactVersionRepository.findById(Mockito.anyString())).thenReturn(Optional.of(proceededData)); + Assertions.assertEquals(1, + versionService.getArtifactsAndVersionToDisplay(productId, false, targetVersion).size()); + } + + @Test + void testHandleArtifactForVersionToDisplay() { + String newVersionDetected = "10.0.10"; + List result = new ArrayList<>(); + List versionsToDisplay = List.of(newVersionDetected); + ReflectionTestUtils.setField(versionService, "productId", "adobe-acrobat-connector"); + Assertions.assertTrue(versionService.handleArtifactForVersionToDisplay(versionsToDisplay, result)); + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(newVersionDetected, result.get(0).getVersion()); + Assertions.assertEquals(0, result.get(0).getArtifactsByVersion().size()); + + result = new ArrayList<>(); + ArrayList artifactsInVersion = new ArrayList<>(); + artifactsInVersion.add(new MavenArtifactModel()); + when(versionService.convertMavenArtifactsToModels(Mockito.anyList(), Mockito.anyString())) + .thenReturn(artifactsInVersion); + Assertions.assertFalse(versionService.handleArtifactForVersionToDisplay(versionsToDisplay, result)); + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(1, result.get(0).getArtifactsByVersion().size()); + } + + @Test + void testGetProductMetaArtifacts() { + Product product = new Product(); + MavenArtifact artifact1 = new MavenArtifact(); + MavenArtifact artifact2 = new MavenArtifact(); + List artifacts = List.of(artifact1, artifact2); + product.setArtifacts(artifacts); + when(productRepository.findById(Mockito.anyString())).thenReturn(Optional.of(product)); + List result = versionService.getProductMetaArtifacts("portal"); + Assertions.assertEquals(artifacts, result); + Assertions.assertNull(versionService.getRepoName()); + + product.setRepositoryName("/market/portal"); + versionService.getProductMetaArtifacts("portal"); + Assertions.assertEquals("portal", versionService.getRepoName()); + } + + @Test + void testUpdateArtifactsInVersionWithProductArtifact() { + String version = "10.0.10"; + ReflectionTestUtils.setField(versionService, "productId", "adobe-acrobat-connector"); + MavenArtifactModel artifactModel = new MavenArtifactModel(); + List mockMavenArtifactModels = List.of(artifactModel); + when(versionService.getProductJsonByVersion(Mockito.anyString())).thenReturn(List.of(new MavenArtifact())); + when(versionService.convertMavenArtifactsToModels(Mockito.anyList(), Mockito.anyString())) + .thenReturn(mockMavenArtifactModels); + Assertions.assertEquals(mockMavenArtifactModels, + versionService.updateArtifactsInVersionWithProductArtifact(version)); + Assertions.assertEquals(1, proceedDataCache.getVersions().size()); + Assertions.assertEquals(1, proceedDataCache.getProductArtifactWithVersionReleased().size()); + Assertions.assertEquals(version, proceedDataCache.getVersions().get(0)); + } + + @Test + void testSanitizeMetaArtifactBeforeHandle() { + setUpArtifactFromMeta(); + String groupId = "com.axonivy.connector.adobe.acrobat.sign"; + String archivedArtifactId1 = "adobe-acrobat-sign-connector"; + String archivedArtifactId2 = "adobe-acrobat-sign-connector"; + ArchivedArtifact archivedArtifact1 = new ArchivedArtifact("10.0.10", groupId, archivedArtifactId1); + ArchivedArtifact archivedArtifact2 = new ArchivedArtifact("10.0.20", groupId, archivedArtifactId2); + artifactsFromMeta.get(1).setArchivedArtifacts(List.of(archivedArtifact2, archivedArtifact1)); + + versionService.sanitizeMetaArtifactBeforeHandle(); + String artifactId = "adobe-acrobat-sign-connector"; + + Assertions.assertEquals(1, artifactsFromMeta.size()); + Assertions.assertEquals(1, archivedArtifactsMap.size()); + Assertions.assertEquals(2, archivedArtifactsMap.get(artifactId).size()); + Assertions.assertEquals(archivedArtifact1, archivedArtifactsMap.get(artifactId).get(0)); + } + + @Test + void testGetVersionsToDisplay() { + String repoUrl = "https://maven.axonivy.com"; + String groupId = "com.axonivy.connector.adobe.acrobat.sign"; + String artifactId = "adobe-acrobat-sign-connector"; + artifactsFromMeta.add(new MavenArtifact(repoUrl, null, groupId, artifactId, null, null, null, null)); + ArrayList versionFromArtifact = new ArrayList<>(); + versionFromArtifact.add("10.0.6"); + versionFromArtifact.add("10.0.5"); + versionFromArtifact.add("10.0.4"); + versionFromArtifact.add("10.0.3-SNAPSHOT"); + when(versionService.getVersionsFromArtifactDetails(repoUrl, groupId, artifactId)) + .thenReturn(versionFromArtifact); + Assertions.assertEquals(versionFromArtifact, versionService.getVersionsToDisplay(true, null)); + Assertions.assertEquals(List.of("10.0.5"), versionService.getVersionsToDisplay(null, "10.0.5")); + versionFromArtifact.remove("10.0.3-SNAPSHOT"); + Assertions.assertEquals(versionFromArtifact, versionService.getVersionsToDisplay(null, null)); + } + + @Test + void getVersionsFromMavenArtifacts() { + String repoUrl = "https://maven.axonivy.com"; + String groupId = "com.axonivy.connector.adobe.acrobat.sign"; + String artifactId = "adobe-acrobat-sign-connector"; + String archivedArtifactId = "adobe-sign-connector"; + artifactsFromMeta.add(new MavenArtifact(repoUrl, null, groupId, artifactId, null, null, null, null)); + ArrayList versionFromArtifact = new ArrayList<>(); + versionFromArtifact.add("10.0.6"); + versionFromArtifact.add("10.0.5"); + versionFromArtifact.add("10.0.4"); + + when(versionService.getVersionsFromArtifactDetails(repoUrl, groupId, artifactId)) + .thenReturn(versionFromArtifact); + Assertions.assertEquals(versionService.getVersionsFromMavenArtifacts(), versionFromArtifact); + + List archivedArtifacts = List.of(new ArchivedArtifact("10.0.9", groupId, archivedArtifactId)); + ArrayList versionFromArchivedArtifact = new ArrayList<>(); + versionFromArchivedArtifact.add("10.0.3"); + versionFromArchivedArtifact.add("10.0.2"); + versionFromArchivedArtifact.add("10.0.1"); + artifactsFromMeta.get(0).setArchivedArtifacts(archivedArtifacts); + when(versionService.getVersionsFromArtifactDetails(repoUrl, groupId, archivedArtifactId)) + .thenReturn(versionFromArchivedArtifact); + versionFromArtifact.addAll(versionFromArchivedArtifact); + Assertions.assertEquals(versionService.getVersionsFromMavenArtifacts(), versionFromArtifact); + } + + @Test + void testGetVersionsFromArtifactDetails() { + + String repoUrl = "https://maven.axonivy.com"; + String groupId = "com.axonivy.connector.adobe.acrobat.sign"; + String artifactId = "adobe-acrobat-sign-connector"; + + ArrayList versionFromArtifact = new ArrayList<>(); + versionFromArtifact.add("10.0.16"); + versionFromArtifact.add("10.0.18"); + versionFromArtifact.add("10.0.19"); + versionFromArtifact.add("10.0.20"); + versionFromArtifact.add("10.0.21"); + + try (MockedStatic xmlUtils = Mockito.mockStatic(XmlReaderUtils.class)) { + xmlUtils.when(() -> XmlReaderUtils.readXMLFromUrl(Mockito.anyString())).thenReturn(versionFromArtifact); + } + Assertions.assertEquals(versionService.getVersionsFromArtifactDetails(repoUrl, null, null), new ArrayList<>()); + Assertions.assertEquals(versionService.getVersionsFromArtifactDetails(repoUrl, groupId, artifactId), + versionFromArtifact); + } + + @Test + void testBuildMavenMetadataUrlFromArtifact() { + String repoUrl = "https://maven.axonivy.com"; + String groupId = "com.axonivy.connector.adobe.acrobat.sign"; + String artifactId = "adobe-acrobat-sign-connector"; + String metadataUrl = "https://maven.axonivy.com/com/axonivy/connector/adobe/acrobat/sign/adobe-acrobat-sign-connector/maven-metadata.xml"; + Assertions.assertEquals(StringUtils.EMPTY, + versionService.buildMavenMetadataUrlFromArtifact(repoUrl, null, artifactId)); + Assertions.assertEquals(StringUtils.EMPTY, + versionService.buildMavenMetadataUrlFromArtifact(repoUrl, groupId, null), StringUtils.EMPTY); + Assertions.assertEquals(metadataUrl, + versionService.buildMavenMetadataUrlFromArtifact(repoUrl, groupId, artifactId)); + } + + @Test + void testIsReleasedVersionOrUnReleaseDevVersion() { + String releasedVersion = "10.0.20"; + String snapshotVersion = "10.0.20-SNAPSHOT"; + String sprintVersion = "10.0.20-m1234"; + String minorSprintVersion = "10.0.20.1-m1234"; + String unreleasedSprintVersion = "10.0.21-m1235"; + List versions = List.of(releasedVersion, snapshotVersion, sprintVersion, unreleasedSprintVersion); + Assertions.assertTrue(versionService.isOfficialVersionOrUnReleasedDevVersion(versions, releasedVersion)); + Assertions.assertFalse(versionService.isOfficialVersionOrUnReleasedDevVersion(versions, sprintVersion)); + Assertions.assertFalse(versionService.isOfficialVersionOrUnReleasedDevVersion(versions, snapshotVersion)); + Assertions.assertFalse(versionService.isOfficialVersionOrUnReleasedDevVersion(versions, minorSprintVersion)); + Assertions + .assertTrue(versionService.isOfficialVersionOrUnReleasedDevVersion(versions, unreleasedSprintVersion)); + } + + @Test + void testGetBugfixVersion() { + String releasedVersion = "10.0.20"; + String snapshotVersion = "10.0.20-SNAPSHOT"; + String sprintVersion = "10.0.20-m1234"; + String minorSprintVersion = "10.0.20.1-m1234"; + Assertions.assertEquals(releasedVersion, versionService.getBugfixVersion(releasedVersion)); + Assertions.assertEquals(releasedVersion, versionService.getBugfixVersion(snapshotVersion)); + Assertions.assertEquals(releasedVersion, versionService.getBugfixVersion(sprintVersion)); + Assertions.assertEquals(releasedVersion, versionService.getBugfixVersion(minorSprintVersion)); + } + + @Test + void testIsSnapshotVersion() { + String targetVersion = "10.0.21-SNAPSHOT"; + Assertions.assertTrue(versionService.isSnapshotVersion(targetVersion)); + + targetVersion = "10.0.21-m1234"; + Assertions.assertFalse(versionService.isSnapshotVersion(targetVersion)); + + targetVersion = "10.0.21"; + Assertions.assertFalse(versionService.isSnapshotVersion(targetVersion)); + } + + @Test + void testIsSprintVersion() { + String targetVersion = "10.0.21-m1234"; + Assertions.assertTrue(versionService.isSprintVersion(targetVersion)); + + targetVersion = "10.0.21-SNAPSHOT"; + Assertions.assertFalse(versionService.isSprintVersion(targetVersion)); + + targetVersion = "10.0.21"; + Assertions.assertFalse(versionService.isSprintVersion(targetVersion)); + } + + @Test + void testIsReleasedVersion() { + String targetVersion = "10.0.21"; + Assertions.assertTrue(versionService.isReleasedVersion(targetVersion)); + + targetVersion = "10.0.21-SNAPSHOT"; + Assertions.assertFalse(versionService.isReleasedVersion(targetVersion)); + + targetVersion = "10.0.21-m1231"; + Assertions.assertFalse(versionService.isReleasedVersion(targetVersion)); + } + + @Test + void testIsMatchWithDesignerVersion() { + String designerVersion = "10.0.21"; + String targetVersion = "10.0.21.2"; + Assertions.assertTrue(versionService.isMatchWithDesignerVersion(targetVersion, designerVersion)); + + targetVersion = "10.0.21-SNAPSHOT"; + Assertions.assertFalse(versionService.isMatchWithDesignerVersion(targetVersion, designerVersion)); + + targetVersion = "10.0.19"; + Assertions.assertFalse(versionService.isMatchWithDesignerVersion(targetVersion, designerVersion)); + } + + @Test + void testGetProductJsonByVersion() { + String targetArtifactId = "adobe-acrobat-sign-connector"; + String targetGroupId = "com.axonivy.connector.adobe.acrobat"; + GHContent mockContent = mock(GHContent.class); + repoName = "adobe-acrobat-sign-connector"; + ReflectionTestUtils.setField(versionService, "repoName", repoName); + ReflectionTestUtils.setField(versionService, "productId", "adobe-acrobat-connector"); + MavenArtifact productArtifact = new MavenArtifact("https://maven.axonivy.com", null, targetGroupId, + targetArtifactId, "iar", null, true, null); + + metaProductArtifact.setRepoUrl("https://maven.axonivy.com"); + metaProductArtifact.setGroupId(targetGroupId); + metaProductArtifact.setArtifactId(targetArtifactId); + when(gitHubService.getContentFromGHRepoAndTag(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(null); + Assertions.assertEquals(0, versionService.getProductJsonByVersion("10.0.20").size()); + + metaProductArtifact.setGroupId("com.axonivy.connector.adobe.acrobat.connector"); + when(gitHubService.getContentFromGHRepoAndTag(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(mockContent); + + try { + when(gitHubService.convertProductJsonToMavenProductInfo(mockContent)).thenReturn(List.of(productArtifact)); + Assertions.assertEquals(1, versionService.getProductJsonByVersion("10.0.20").size()); + + when(gitHubService.convertProductJsonToMavenProductInfo(mockContent)) + .thenThrow(new IOException("Mock IO Exception")); + Assertions.assertEquals(0, versionService.getProductJsonByVersion("10.0.20").size()); + } catch (IOException e) { + Fail.fail("Mock setup should not throw an exception"); + } + } + + @Test + void testConvertMavenArtifactToModel() { + String downloadUrl = "https://maven.axonivy.com/com/axonivy/connector/adobe/acrobat/sign/adobe-acrobat-sign-connector/10.0.21/adobe-acrobat-sign-connector-10.0.21.iar"; + String artifactName = "Adobe Acrobat Sign Connector (iar)"; + + MavenArtifact targetArtifact = new MavenArtifact(null, null, "com.axonivy.connector.adobe.acrobat.sign", + "adobe-acrobat-sign-connector", null, null, null, null); + + // Assert case handle artifact without name + MavenArtifactModel result = versionService.convertMavenArtifactToModel(targetArtifact, "10.0.21"); + MavenArtifactModel expectedResult = new MavenArtifactModel(artifactName, downloadUrl, null); + Assertions.assertEquals(expectedResult.getName(), result.getName()); + Assertions.assertEquals(expectedResult.getDownloadUrl(), result.getDownloadUrl()); + + // Assert case handle artifact with name + artifactName = "Adobe Connector"; + String expectedArtifactName = "Adobe Connector (iar)"; + targetArtifact.setName(artifactName); + result = versionService.convertMavenArtifactToModel(targetArtifact, "10.0.21"); + expectedResult = new MavenArtifactModel(artifactName, downloadUrl, null); + Assertions.assertEquals(expectedArtifactName, result.getName()); + Assertions.assertEquals(expectedResult.getDownloadUrl(), result.getDownloadUrl()); + } + + @Test + void testConvertMavenArtifactsToModels() { + // Assert case param is empty + List result = versionService.convertMavenArtifactsToModels(Collections.emptyList(), + "10.0.21"); + Assertions.assertEquals(Collections.emptyList(), result); + + // Assert case param is null + result = versionService.convertMavenArtifactsToModels(null, "10.0.21"); + Assertions.assertEquals(Collections.emptyList(), result); + + // Assert case param is a list with existed element + MavenArtifact targetArtifact = new MavenArtifact(null, null, "com.axonivy.connector.adobe.acrobat.sign", + "adobe-acrobat-sign-connector", null, null, null, null); + result = versionService.convertMavenArtifactsToModels(List.of(targetArtifact), "10.0.21"); + Assertions.assertEquals(1, result.size()); + } + + @Test + void testBuildDownloadUrlFromArtifactAndVersion() { + // Set up artifact for testing + String targetArtifactId = "adobe-acrobat-sign-connector"; + String targetGroupId = "com.axonivy.connector"; + MavenArtifact targetArtifact = new MavenArtifact(null, null, targetGroupId, targetArtifactId, "iar", null, null, + null); + String targetVersion = "10.0.10"; + + // Assert case without archived artifact + String expectedResult = String.format(MavenConstants.ARTIFACT_DOWNLOAD_URL_FORMAT, + MavenConstants.DEFAULT_IVY_MAVEN_BASE_URL, "com/axonivy/connector", targetArtifactId, targetVersion, + targetArtifactId, targetVersion, "iar"); + String result = versionService.buildDownloadUrlFromArtifactAndVersion(targetArtifact, targetVersion); + Assertions.assertEquals(expectedResult, result); + + // Assert case with artifact not match & use custom repo + ArchivedArtifact adobeArchivedArtifactVersion9 = new ArchivedArtifact("10.0.9", "com.axonivy.adobe.connector", + "adobe-connector"); + ArchivedArtifact adobeArchivedArtifactVersion8 = new ArchivedArtifact("10.0.8", + "com.axonivy.adobe.sign.connector", "adobe-sign-connector"); + archivedArtifactsMap.put(targetArtifactId, + List.of(adobeArchivedArtifactVersion9, adobeArchivedArtifactVersion8)); + String customRepoUrl = "https://nexus.axonivy.com"; + targetArtifact.setRepoUrl(customRepoUrl); + result = versionService.buildDownloadUrlFromArtifactAndVersion(targetArtifact, targetVersion); + expectedResult = String.format(MavenConstants.ARTIFACT_DOWNLOAD_URL_FORMAT, customRepoUrl, + "com/axonivy/connector", targetArtifactId, targetVersion, targetArtifactId, targetVersion, "iar"); + Assertions.assertEquals(expectedResult, result); + + // Assert case with artifact got matching archived artifact & use custom file + // type + String customType = "zip"; + targetArtifact.setType(customType); + targetVersion = "10.0.9"; + result = versionService.buildDownloadUrlFromArtifactAndVersion(targetArtifact, "10.0.9"); + expectedResult = String.format(MavenConstants.ARTIFACT_DOWNLOAD_URL_FORMAT, customRepoUrl, + "com/axonivy/adobe/connector", "adobe-connector", targetVersion, "adobe-connector", targetVersion, + customType); + Assertions.assertEquals(expectedResult, result); + } + + @Test + void testFindArchivedArtifactInfoBestMatchWithVersion() { + String targetArtifactId = "adobe-acrobat-sign-connector"; + String targetVersion = "10.0.10"; + ArchivedArtifact result = versionService.findArchivedArtifactInfoBestMatchWithVersion(targetArtifactId, + targetVersion); + Assertions.assertNull(result); + + // Assert case with target version higher than all of latest version from + // archived artifact list + ArchivedArtifact adobeArchivedArtifactVersion8 = new ArchivedArtifact("10.0.8", "com.axonivy.connector", + "adobe-sign-connector"); + ArchivedArtifact adobeArchivedArtifactVersion9 = new ArchivedArtifact("10.0.9", "com.axonivy.connector", + "adobe-acrobat-sign-connector"); + List archivedArtifacts = new ArrayList<>(); + archivedArtifacts.add(adobeArchivedArtifactVersion8); + archivedArtifacts.add(adobeArchivedArtifactVersion9); + archivedArtifactsMap.put(targetArtifactId, archivedArtifacts); + result = versionService.findArchivedArtifactInfoBestMatchWithVersion(targetArtifactId, targetVersion); + Assertions.assertNull(result); + + // Assert case with target version less than all of latest version from archived + // artifact list + result = versionService.findArchivedArtifactInfoBestMatchWithVersion(targetArtifactId, "10.0.7"); + Assertions.assertEquals(adobeArchivedArtifactVersion8, result); + + // Assert case with target version is in range of archived artifact list + ArchivedArtifact adobeArchivedArtifactVersion10 = new ArchivedArtifact("10.0.10", "com.axonivy.connector", + "adobe-sign-connector"); + + archivedArtifactsMap.get(targetArtifactId).add(adobeArchivedArtifactVersion10); + result = versionService.findArchivedArtifactInfoBestMatchWithVersion(targetArtifactId, targetVersion); + Assertions.assertEquals(adobeArchivedArtifactVersion10, result); + } + + @Test + void testConvertArtifactIdToName() { + String defaultArtifactId = "adobe-acrobat-sign-connector"; + String result = versionService.convertArtifactIdToName(defaultArtifactId); + Assertions.assertEquals("Adobe Acrobat Sign Connector", result); + + result = versionService.convertArtifactIdToName(null); + Assertions.assertEquals(StringUtils.EMPTY, result); + + result = versionService.convertArtifactIdToName(StringUtils.EMPTY); + Assertions.assertEquals(StringUtils.EMPTY, result); + + result = versionService.convertArtifactIdToName(" "); + Assertions.assertEquals(StringUtils.EMPTY, result); + } + + @Test + void testGetRepoNameFromMarketRepo() { + String defaultRepositoryName = "market/adobe-acrobat-connector"; + String expectedRepoName = "adobe-acrobat-connector"; + String result = versionService.getRepoNameFromMarketRepo(defaultRepositoryName); + Assertions.assertEquals(expectedRepoName, result); + + defaultRepositoryName = "market/utils/adobe-acrobat-connector"; + result = versionService.getRepoNameFromMarketRepo(defaultRepositoryName); + Assertions.assertEquals(expectedRepoName, result); + + defaultRepositoryName = "adobe-acrobat-connector"; + result = versionService.getRepoNameFromMarketRepo(defaultRepositoryName); + Assertions.assertEquals(expectedRepoName, result); + } + + @Test + void testBuildProductJsonFilePath() { + String version = "10.0.1"; + ReflectionTestUtils.setField(versionService, "productId", "adobe-acrobat-connector"); + Assertions.assertEquals("v10.0.1", versionService.buildProductJsonFilePath(version)); + + ReflectionTestUtils.setField(versionService, "productId", NonStandardProductPackageConstants.PORTAL); + Assertions.assertEquals("10.0.1", versionService.buildProductJsonFilePath(version)); + Assertions.assertEquals("AxonIvyPortal/portal-product/product.json", versionService.getProductJsonFilePath()); + + ReflectionTestUtils.setField(versionService, "productId", + NonStandardProductPackageConstants.CONNECTIVITY_FEATURE); + versionService.buildProductJsonFilePath(version); + Assertions.assertEquals("connectivity/connectivity-demos-product/product.json", + versionService.getProductJsonFilePath()); + + ReflectionTestUtils.setField(versionService, "productId", NonStandardProductPackageConstants.ERROR_HANDLING); + versionService.buildProductJsonFilePath(version); + Assertions.assertEquals("error-handling/error-handling-demos-product/product.json", + versionService.getProductJsonFilePath()); + + ReflectionTestUtils.setField(versionService, "productId", NonStandardProductPackageConstants.WORKFLOW_DEMO); + versionService.buildProductJsonFilePath(version); + Assertions.assertEquals("workflow/workflow-demos-product/product.json", + versionService.getProductJsonFilePath()); + + ReflectionTestUtils.setField(versionService, "productId", NonStandardProductPackageConstants.MICROSOFT_365); + versionService.buildProductJsonFilePath(version); + Assertions.assertEquals("msgraph-connector-product/products/msgraph-connector/product.json", + versionService.getProductJsonFilePath()); + + ReflectionTestUtils.setField(versionService, "productId", NonStandardProductPackageConstants.HTML_DIALOG_DEMO); + versionService.buildProductJsonFilePath(version); + versionService.buildProductJsonFilePath(version); + Assertions.assertEquals("html-dialog/html-dialog-demos-product/product.json", + versionService.getProductJsonFilePath()); + + ReflectionTestUtils.setField(versionService, "productId", NonStandardProductPackageConstants.RULE_ENGINE_DEMOS); + versionService.buildProductJsonFilePath(version); + Assertions.assertEquals("rule-engine/rule-engine-demos-product/product.json", + versionService.getProductJsonFilePath()); + } +} \ No newline at end of file diff --git a/src/test/java/com/axonivy/market/utils/XmlReaderUtilsTest.java b/src/test/java/com/axonivy/market/utils/XmlReaderUtilsTest.java new file mode 100644 index 000000000..7245ce561 --- /dev/null +++ b/src/test/java/com/axonivy/market/utils/XmlReaderUtilsTest.java @@ -0,0 +1,21 @@ +package com.axonivy.market.utils; + +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Collections; +import java.util.List; + +@ExtendWith(MockitoExtension.class) +class XmlReaderUtilsTest { + + @Test + void testExtractVersions() { + List versions = Collections.emptyList(); + XmlReaderUtils.extractVersions(StringUtils.EMPTY, versions); + Assertions.assertTrue(versions.isEmpty()); + } +} \ No newline at end of file From 9dc6a5f5834ba8d093ec6e36a2ccc594c627b27d Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Wed, 3 Jul 2024 18:34:16 +0700 Subject: [PATCH 11/62] feature/Marp-475 Detail pages for new market installation and download --- .../com/axonivy/market/service/impl/VersionServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java b/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java index f1aeaba06..36988dcea 100644 --- a/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java +++ b/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java @@ -210,7 +210,7 @@ public boolean isOfficialVersionOrUnReleasedDevVersion(List versions, St } else { bugfixVersion = getBugfixVersion(version.split(MavenConstants.SPRINT_RELEASE_POSTFIX)[0]); } - return versions.stream().noneMatch(currentVersion -> !currentVersion.equals(version) + return versions.stream().noneMatch(currentVersion -> !currentVersion.equals(version) && isReleasedVersion(currentVersion) && getBugfixVersion(currentVersion).equals(bugfixVersion)); } From 6880dacbe5fc18822049fb99683e3b28db2e14b8 Mon Sep 17 00:00:00 2001 From: Pham Hoang Hung <84316773+phhung-axonivy@users.noreply.github.com> Date: Fri, 5 Jul 2024 10:22:06 +0700 Subject: [PATCH 12/62] MARP-463 Multilingualism for Website - landing page (#22) --- .../assembler/ProductModelAssembler.java | 4 +- .../market/constants/GitHubConstants.java | 11 +-- .../market/controller/ProductController.java | 5 +- .../market/entity/MavenArtifactModel.java | 10 +-- .../market/entity/MavenArtifactVersion.java | 4 +- .../com/axonivy/market/entity/Product.java | 9 +- .../com/axonivy/market/enums/Language.java | 12 +++ .../com/axonivy/market/enums/SortOption.java | 8 +- .../market/factory/ProductFactory.java | 24 ++++- .../com/axonivy/market/github/model/Meta.java | 5 +- .../market/github/service/GitHubService.java | 4 +- .../impl/GHAxonIvyMarketRepoServiceImpl.java | 5 +- .../service/impl/GitHubServiceImpl.java | 8 +- .../axonivy/market/model/DisplayValue.java | 44 +++++++++ .../market/model/MultilingualismValue.java | 17 ++++ .../axonivy/market/model/ProductModel.java | 4 +- .../market/repository/ProductRepository.java | 8 +- .../market/service/ProductService.java | 2 +- .../service/impl/ProductServiceImpl.java | 17 ++-- .../controller/ProductControllerTest.java | 24 +++-- .../market/factory/ProductFactoryTest.java | 90 ++++++++++--------- .../GHAxonIvyMarketRepoServiceImplTest.java | 2 +- .../market/service/GitHubServiceImplTest.java | 6 +- .../service/ProductServiceImplTest.java | 49 +++++----- .../service/VersionServiceImplTest.java | 45 ++++++---- src/test/resources/meta.json | 22 ++++- 26 files changed, 297 insertions(+), 142 deletions(-) create mode 100644 src/main/java/com/axonivy/market/enums/Language.java create mode 100644 src/main/java/com/axonivy/market/model/DisplayValue.java create mode 100644 src/main/java/com/axonivy/market/model/MultilingualismValue.java diff --git a/src/main/java/com/axonivy/market/assembler/ProductModelAssembler.java b/src/main/java/com/axonivy/market/assembler/ProductModelAssembler.java index 6b36a9d07..bd9c948cd 100644 --- a/src/main/java/com/axonivy/market/assembler/ProductModelAssembler.java +++ b/src/main/java/com/axonivy/market/assembler/ProductModelAssembler.java @@ -27,8 +27,8 @@ public ProductModel toModel(Product product) { private ProductModel createResource(ProductModel model, Product product) { model.setId(product.getId()); - model.setName(product.getName()); - model.setShortDescription(product.getShortDescription()); + model.setNames(product.getNames()); + model.setShortDescriptions(product.getShortDescriptions()); model.setType(product.getType()); model.setTags(product.getTags()); model.setLogoUrl(product.getLogoUrl()); diff --git a/src/main/java/com/axonivy/market/constants/GitHubConstants.java b/src/main/java/com/axonivy/market/constants/GitHubConstants.java index c03c69f65..33c84df88 100644 --- a/src/main/java/com/axonivy/market/constants/GitHubConstants.java +++ b/src/main/java/com/axonivy/market/constants/GitHubConstants.java @@ -5,8 +5,9 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public class GitHubConstants { - public static final String AXONIVY_MARKET_ORGANIZATION_NAME = "axonivy-market"; - public static final String AXONIVY_MARKETPLACE_REPO_NAME = "market"; - public static final String AXONIVY_MARKETPLACE_PATH = "market"; - public static final String PRODUCT_JSON_FILE_PATH_FORMAT = "%s/product.json"; -} \ No newline at end of file + public static final String AXONIVY_MARKET_ORGANIZATION_NAME = "axonivy-market"; + public static final String AXONIVY_MARKETPLACE_REPO_NAME = "market"; + public static final String AXONIVY_MARKETPLACE_PATH = "market"; + public static final String DEFAULT_BRANCH = "feature/MARP-463-Multilingualism-for-Website"; + public static final String PRODUCT_JSON_FILE_PATH_FORMAT = "%s/product.json"; +} diff --git a/src/main/java/com/axonivy/market/controller/ProductController.java b/src/main/java/com/axonivy/market/controller/ProductController.java index 434beb202..55d0444ff 100644 --- a/src/main/java/com/axonivy/market/controller/ProductController.java +++ b/src/main/java/com/axonivy/market/controller/ProductController.java @@ -45,8 +45,9 @@ public ProductController(ProductService service, ProductModelAssembler assembler @GetMapping() public ResponseEntity> findProducts( @RequestParam(required = true, name = "type") String type, - @RequestParam(required = false, name = "keyword") String keyword, Pageable pageable) { - Page results = service.findProducts(type, keyword, pageable); + @RequestParam(required = false, name = "keyword") String keyword, + @RequestParam(required = true, name = "language") String language, Pageable pageable) { + Page results = service.findProducts(type, keyword, language, pageable); if (results.isEmpty()) { return generateEmptyPagedModel(); } diff --git a/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java b/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java index f3f8977d5..6b5328e26 100644 --- a/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java +++ b/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java @@ -1,14 +1,14 @@ package com.axonivy.market.entity; -import com.axonivy.market.github.model.MavenArtifact; +import java.io.Serializable; +import java.util.Objects; + +import org.springframework.data.annotation.Transient; + import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.springframework.data.annotation.Transient; - -import java.io.Serializable; -import java.util.Objects; @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java b/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java index f92be46fc..8c8820579 100644 --- a/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java +++ b/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java @@ -21,7 +21,9 @@ @NoArgsConstructor @Document(MAVEN_ARTIFACT_VERSION) public class MavenArtifactVersion implements Serializable { - @Id + private static final long serialVersionUID = -6492612804634492078L; + + @Id private String productId; private List versions = new ArrayList<>(); private Map> productArtifactWithVersionReleased = new HashMap<>(); diff --git a/src/main/java/com/axonivy/market/entity/Product.java b/src/main/java/com/axonivy/market/entity/Product.java index f38748c41..9d89381f7 100644 --- a/src/main/java/com/axonivy/market/entity/Product.java +++ b/src/main/java/com/axonivy/market/entity/Product.java @@ -12,6 +12,9 @@ import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; +import com.axonivy.market.model.MultilingualismValue; +import com.fasterxml.jackson.annotation.JsonProperty; + import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -28,9 +31,11 @@ public class Product implements Serializable { @Id private String id; private String marketDirectory; - private String name; + @JsonProperty + private MultilingualismValue names; private String version; - private String shortDescription; + @JsonProperty + private MultilingualismValue shortDescriptions; private String logoUrl; private Boolean listed; private String type; diff --git a/src/main/java/com/axonivy/market/enums/Language.java b/src/main/java/com/axonivy/market/enums/Language.java new file mode 100644 index 000000000..f1115c234 --- /dev/null +++ b/src/main/java/com/axonivy/market/enums/Language.java @@ -0,0 +1,12 @@ +package com.axonivy.market.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum Language { + EN("en"), DE("de"); + + private String value; +} diff --git a/src/main/java/com/axonivy/market/enums/SortOption.java b/src/main/java/com/axonivy/market/enums/SortOption.java index 308a01bd3..59914ab90 100644 --- a/src/main/java/com/axonivy/market/enums/SortOption.java +++ b/src/main/java/com/axonivy/market/enums/SortOption.java @@ -10,7 +10,7 @@ @Getter @AllArgsConstructor public enum SortOption { - POPULARITY("popularity", "installationCount"), ALPHABETICALLY("alphabetically", "name"), + POPULARITY("popularity", "installationCount"), ALPHABETICALLY("alphabetically", "names"), RECENT("recent", "newestPublishedDate"); private String option; @@ -25,4 +25,10 @@ public static SortOption of(String option) { } throw new InvalidParamException(ErrorCode.PRODUCT_SORT_INVALID, "SortOption: " + option); } + + public String getCode(String language) { + return StringUtils.isNotBlank(language) && ALPHABETICALLY.option.equalsIgnoreCase(option) + ? String.format("%s.%s", ALPHABETICALLY.code, language) + : code; + } } diff --git a/src/main/java/com/axonivy/market/factory/ProductFactory.java b/src/main/java/com/axonivy/market/factory/ProductFactory.java index 8ec75e239..4cd0f6971 100644 --- a/src/main/java/com/axonivy/market/factory/ProductFactory.java +++ b/src/main/java/com/axonivy/market/factory/ProductFactory.java @@ -6,13 +6,18 @@ import static org.apache.commons.lang3.StringUtils.EMPTY; import java.io.IOException; +import java.util.List; import org.apache.commons.lang3.StringUtils; import org.kohsuke.github.GHContent; +import org.springframework.util.CollectionUtils; import com.axonivy.market.entity.Product; +import com.axonivy.market.enums.Language; import com.axonivy.market.github.model.Meta; import com.axonivy.market.github.util.GitHubUtils; +import com.axonivy.market.model.DisplayValue; +import com.axonivy.market.model.MultilingualismValue; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.AccessLevel; @@ -49,13 +54,13 @@ public static Product mappingByMetaJSONFile(Product product, GHContent ghContent } product.setId(meta.getId()); - product.setName(meta.getName()); + product.setNames(mappingMultilingualismValueByMetaJSONFile(meta.getNames())); product.setMarketDirectory(extractParentDirectory(ghContent)); product.setListed(meta.getListed()); product.setType(meta.getType()); product.setTags(meta.getTags()); product.setVersion(meta.getVersion()); - product.setShortDescription(meta.getDescription()); + product.setShortDescriptions(mappingMultilingualismValueByMetaJSONFile(meta.getDescriptions())); product.setVendor(meta.getVendor()); product.setVendorImage(meta.getVendorImage()); product.setVendorUrl(meta.getVendorUrl()); @@ -68,6 +73,21 @@ public static Product mappingByMetaJSONFile(Product product, GHContent ghContent return product; } + private static MultilingualismValue mappingMultilingualismValueByMetaJSONFile(List list) { + MultilingualismValue value = new MultilingualismValue(); + if (!CollectionUtils.isEmpty(list)) { + for (DisplayValue name : list) { + if (Language.EN.getValue().equalsIgnoreCase(name.getLocale())) { + value.setEn(name.getValue()); + } else if (Language.DE.getValue().equalsIgnoreCase(name.getLocale())) { + value.setDe(name.getValue()); + } + } + } + + return value; + } + private static String extractParentDirectory(GHContent ghContent) { var path = StringUtils.defaultIfEmpty(ghContent.getPath(), EMPTY); return path.replace(ghContent.getName(), EMPTY); diff --git a/src/main/java/com/axonivy/market/github/model/Meta.java b/src/main/java/com/axonivy/market/github/model/Meta.java index 4ef988820..ee80857de 100644 --- a/src/main/java/com/axonivy/market/github/model/Meta.java +++ b/src/main/java/com/axonivy/market/github/model/Meta.java @@ -2,6 +2,7 @@ import java.util.List; +import com.axonivy.market.model.DisplayValue; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -19,8 +20,8 @@ public class Meta { @JsonProperty("$schema") private String schema; private String id; - private String name; - private String description; + private List names; + private List descriptions; private String type; private String platformReview; private String sourceUrl; diff --git a/src/main/java/com/axonivy/market/github/service/GitHubService.java b/src/main/java/com/axonivy/market/github/service/GitHubService.java index 2f4fb7e16..7dd5009db 100644 --- a/src/main/java/com/axonivy/market/github/service/GitHubService.java +++ b/src/main/java/com/axonivy/market/github/service/GitHubService.java @@ -16,7 +16,7 @@ public interface GitHubService { public GHRepository getRepository(String repositoryPath) throws IOException; - public List getDirectoryContent(GHRepository ghRepository, String path) throws IOException; + public List getDirectoryContent(GHRepository ghRepository, String path, String ref) throws IOException; - public GHContent getGHContent(GHRepository ghRepository, String path) throws IOException; + public GHContent getGHContent(GHRepository ghRepository, String path, String ref) throws IOException; } diff --git a/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java b/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java index 0d5202c18..6c96bee26 100644 --- a/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java +++ b/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java @@ -29,7 +29,6 @@ @Log4j2 @Service public class GHAxonIvyMarketRepoServiceImpl implements GHAxonIvyMarketRepoService { - private static final String DEFAULT_BRANCH = "master"; private static final LocalDateTime INITIAL_COMMIT_DATE = LocalDateTime.of(2020, 10, 30, 0, 0); private GHOrganization organization; private GHRepository repository; @@ -45,7 +44,7 @@ public Map> fetchAllMarketItems() { Map> ghContentMap = new HashMap<>(); try { List directoryContent = gitHubService.getDirectoryContent(getRepository(), - GitHubConstants.AXONIVY_MARKETPLACE_PATH); + GitHubConstants.AXONIVY_MARKETPLACE_PATH, GitHubConstants.DEFAULT_BRANCH); for (var content : directoryContent) { extractFileInDirectoryContent(content, ghContentMap); } @@ -85,7 +84,7 @@ public GHCommit getLastCommit(long lastCommitTime) { } private GHCommitQueryBuilder createQueryCommitsBuilder(long lastCommitTime) { - return getRepository().queryCommits().since(lastCommitTime).from(DEFAULT_BRANCH); + return getRepository().queryCommits().since(lastCommitTime).from(GitHubConstants.DEFAULT_BRANCH); } @Override diff --git a/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java b/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java index b1a069ed5..62bbd9181 100644 --- a/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java +++ b/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java @@ -34,9 +34,9 @@ public GHOrganization getOrganization(String orgName) throws IOException { } @Override - public List getDirectoryContent(GHRepository ghRepository, String path) throws IOException { + public List getDirectoryContent(GHRepository ghRepository, String path, String ref) throws IOException { Assert.notNull(ghRepository, "Repository must not be null"); - return ghRepository.getDirectoryContent(path); + return ghRepository.getDirectoryContent(path, ref); } @Override @@ -45,8 +45,8 @@ public GHRepository getRepository(String repositoryPath) throws IOException { } @Override - public GHContent getGHContent(GHRepository ghRepository, String path) throws IOException { + public GHContent getGHContent(GHRepository ghRepository, String path, String ref) throws IOException { Assert.notNull(ghRepository, "Repository must not be null"); - return ghRepository.getFileContent(path); + return ghRepository.getFileContent(path, ref); } } \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/model/DisplayValue.java b/src/main/java/com/axonivy/market/model/DisplayValue.java new file mode 100644 index 000000000..cd0bf4abf --- /dev/null +++ b/src/main/java/com/axonivy/market/model/DisplayValue.java @@ -0,0 +1,44 @@ +package com.axonivy.market.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class DisplayValue { + + private String locale; + private String value; + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof DisplayValue)) { + return false; + } + DisplayValue other = (DisplayValue) obj; + EqualsBuilder builder = new EqualsBuilder(); + builder.append(value, other.getValue()); + builder.append(locale, other.locale); + return builder.isEquals(); + } + + @Override + public int hashCode() { + HashCodeBuilder builder = new HashCodeBuilder(); + builder.append(getValue()); + builder.append(getLocale()); + return builder.hashCode(); + } +} diff --git a/src/main/java/com/axonivy/market/model/MultilingualismValue.java b/src/main/java/com/axonivy/market/model/MultilingualismValue.java new file mode 100644 index 000000000..58431cf8e --- /dev/null +++ b/src/main/java/com/axonivy/market/model/MultilingualismValue.java @@ -0,0 +1,17 @@ +package com.axonivy.market.model; + +import java.io.Serializable; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class MultilingualismValue implements Serializable { + private static final long serialVersionUID = -4193508237020296419L; + + private String en; + private String de; +} diff --git a/src/main/java/com/axonivy/market/model/ProductModel.java b/src/main/java/com/axonivy/market/model/ProductModel.java index f11a62244..7ba586438 100644 --- a/src/main/java/com/axonivy/market/model/ProductModel.java +++ b/src/main/java/com/axonivy/market/model/ProductModel.java @@ -21,8 +21,8 @@ @JsonInclude(Include.NON_NULL) public class ProductModel extends RepresentationModel { private String id; - private String name; - private String shortDescription; + private MultilingualismValue names; + private MultilingualismValue shortDescriptions; private String logoUrl; private String type; private List tags; diff --git a/src/main/java/com/axonivy/market/repository/ProductRepository.java b/src/main/java/com/axonivy/market/repository/ProductRepository.java index 1b18bad04..25ff5e794 100644 --- a/src/main/java/com/axonivy/market/repository/ProductRepository.java +++ b/src/main/java/com/axonivy/market/repository/ProductRepository.java @@ -22,9 +22,9 @@ public interface ProductRepository extends MongoRepository { @Query("{'marketDirectory': {$regex : ?0, $options: 'i'}}") Product findByMarketDirectoryRegex(String search); - @Query("{ $and: [ { $or: [ { 'name': { $regex: ?0, $options: 'i' } }, { 'shortDescription': { $regex: ?0, $options: 'i' } } ] }, { 'type': ?1 } ] }") - Page searchByKeywordAndType(String keyword, String type, Pageable unifiedPageabe); + @Query("{ $and: [ { $or: [ { 'names.?': { $regex: ?0, $options: 'i' } }, { 'shortDescriptions.?': { $regex: ?0, $options: 'i' } } ] }, { 'type': ?1 } ] }") + Page searchByKeywordAndType(String keyword, String type, String language, Pageable unifiedPageabe); - @Query("{ $or: [ { 'name': { $regex: ?0, $options: 'i' } }, { 'shortDescription': { $regex: ?0, $options: 'i' } } ] }") - Page searchByNameOrShortDescriptionRegex(String keyword, Pageable unifiedPageabe); + @Query("{ $or: [ { 'names.?1': { $regex: ?0, $options: 'i' } }, { 'shortDescriptions.?1': { $regex: ?0, $options: 'i' } } ] }") + Page searchByNameOrShortDescriptionRegex(String keyword, String language, Pageable unifiedPageabe); } diff --git a/src/main/java/com/axonivy/market/service/ProductService.java b/src/main/java/com/axonivy/market/service/ProductService.java index 0a3f5529f..5e35368de 100644 --- a/src/main/java/com/axonivy/market/service/ProductService.java +++ b/src/main/java/com/axonivy/market/service/ProductService.java @@ -6,7 +6,7 @@ import com.axonivy.market.entity.Product; public interface ProductService { - Page findProducts(String type, String keyword, Pageable pageable); + Page findProducts(String type, String keyword, String language, Pageable pageable); boolean syncLatestDataFromMarketRepo(); } diff --git a/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java b/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java index 44de63b60..70f7a3a16 100644 --- a/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java +++ b/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java @@ -63,23 +63,23 @@ public ProductServiceImpl(ProductRepository productRepository, GHAxonIvyMarketRe } @Override - public Page findProducts(String type, String keyword, Pageable pageable) { + public Page findProducts(String type, String keyword, String language, Pageable pageable) { final var typeOption = TypeOption.of(type); - final var searchPageable = refinePagination(pageable); + final var searchPageable = refinePagination(language, pageable); Page result = Page.empty(); switch (typeOption) { case ALL: if (StringUtils.isBlank(keyword)) { result = productRepository.findAll(searchPageable); } else { - result = productRepository.searchByNameOrShortDescriptionRegex(keyword, searchPageable); + result = productRepository.searchByNameOrShortDescriptionRegex(keyword, language, searchPageable); } break; case CONNECTORS, UTILITIES, SOLUTIONS: if (StringUtils.isBlank(keyword)) { result = productRepository.findByType(typeOption.getCode(), searchPageable); } else { - result = productRepository.searchByKeywordAndType(keyword, typeOption.getCode(), searchPageable); + result = productRepository.searchByKeywordAndType(keyword, typeOption.getCode(), language, searchPageable); } break; default: @@ -136,7 +136,8 @@ private void updateLatestChangeToProductsFromGithubRepo() { Product product = new Product(); GHContent fileContent; try { - fileContent = gitHubService.getGHContent(axonIvyMarketRepoService.getRepository(), file.getFileName()); + fileContent = gitHubService.getGHContent(axonIvyMarketRepoService.getRepository(), file.getFileName(), + GitHubConstants.DEFAULT_BRANCH); } catch (IOException e) { log.error("Get GHContent failed: ", e); continue; @@ -187,13 +188,13 @@ private void modifyProductByMetaContent(GitHubFile file, Product product) { } } - private Pageable refinePagination(Pageable pageable) { + private Pageable refinePagination(String language, Pageable pageable) { PageRequest pageRequest = (PageRequest) pageable; - if (pageable != null && pageable.getSort() != null) { + if (pageable != null) { List orders = new ArrayList<>(); for (var sort : pageable.getSort()) { final var sortOption = SortOption.of(sort.getProperty()); - Order order = new Order(sort.getDirection(), sortOption.getCode()); + Order order = new Order(sort.getDirection(), sortOption.getCode(language)); orders.add(order); } pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(orders)); diff --git a/src/test/java/com/axonivy/market/controller/ProductControllerTest.java b/src/test/java/com/axonivy/market/controller/ProductControllerTest.java index a2c6f3ec7..00417f662 100644 --- a/src/test/java/com/axonivy/market/controller/ProductControllerTest.java +++ b/src/test/java/com/axonivy/market/controller/ProductControllerTest.java @@ -28,12 +28,15 @@ import com.axonivy.market.enums.ErrorCode; import com.axonivy.market.enums.SortOption; import com.axonivy.market.enums.TypeOption; +import com.axonivy.market.model.MultilingualismValue; import com.axonivy.market.service.ProductService; @ExtendWith(MockitoExtension.class) class ProductControllerTest { private static final String PRODUCT_NAME_SAMPLE = "Amazon Comprehend"; + private static final String PRODUCT_NAME_DE_SAMPLE = "Amazon Comprehend DE"; private static final String PRODUCT_DESC_SAMPLE = "Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data."; + private static final String PRODUCT_DESC_DE_SAMPLE = "Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data. DE"; @Mock private ProductService service; @@ -59,9 +62,9 @@ void setup() { void testFindProductsAsEmpty() { PageRequest pageable = PageRequest.of(0, 20); Page mockProducts = new PageImpl(List.of(), pageable, 0); - when(service.findProducts(any(), any(), any())).thenReturn(mockProducts); + when(service.findProducts(any(), any(), any(), any())).thenReturn(mockProducts); when(pagedResourcesAssembler.toEmptyModel(any(), any())).thenReturn(PagedModel.empty()); - var result = productController.findProducts(TypeOption.ALL.getOption(), null, pageable); + var result = productController.findProducts(TypeOption.ALL.getOption(), null, "en", pageable); assertEquals(HttpStatus.OK, result.getStatusCode()); assertTrue(result.hasBody()); assertEquals(0, result.getBody().getContent().size()); @@ -73,16 +76,17 @@ void testFindProducts() { Product mockProduct = createProductMock(); Page mockProducts = new PageImpl(List.of(mockProduct), pageable, 1); - when(service.findProducts(any(), any(), any())).thenReturn(mockProducts); + when(service.findProducts(any(), any(), any(), any())).thenReturn(mockProducts); assembler = new ProductModelAssembler(); var mockProductModel = assembler.toModel(mockProduct); var mockPagedModel = PagedModel.of(List.of(mockProductModel), new PageMetadata(1, 0, 1)); when(pagedResourcesAssembler.toModel(any(), any(ProductModelAssembler.class))).thenReturn(mockPagedModel); - var result = productController.findProducts(TypeOption.ALL.getOption(), null, pageable); + var result = productController.findProducts(TypeOption.ALL.getOption(), "", "en", pageable); assertEquals(HttpStatus.OK, result.getStatusCode()); assertTrue(result.hasBody()); assertEquals(1, result.getBody().getContent().size()); - assertEquals(PRODUCT_NAME_SAMPLE, result.getBody().getContent().iterator().next().getName()); + assertEquals(PRODUCT_NAME_SAMPLE, result.getBody().getContent().iterator().next().getNames().getEn()); + assertEquals(PRODUCT_NAME_DE_SAMPLE, result.getBody().getContent().iterator().next().getNames().getDe()); } @Test @@ -96,8 +100,14 @@ void testSyncProducts() { private Product createProductMock() { Product mockProduct = new Product(); mockProduct.setId("amazon-comprehend"); - mockProduct.setName(PRODUCT_NAME_SAMPLE); - mockProduct.setShortDescription(PRODUCT_DESC_SAMPLE); + MultilingualismValue name = new MultilingualismValue(); + name.setEn(PRODUCT_NAME_SAMPLE); + name.setDe(PRODUCT_NAME_DE_SAMPLE); + mockProduct.setNames(name); + MultilingualismValue shortDescription = new MultilingualismValue(); + shortDescription.setEn(PRODUCT_DESC_SAMPLE); + shortDescription.setDe(PRODUCT_DESC_DE_SAMPLE); + mockProduct.setShortDescriptions(shortDescription); mockProduct.setType("connector"); mockProduct.setTags(List.of("AI")); return mockProduct; diff --git a/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java b/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java index 96d27d5bf..0221c4ce6 100644 --- a/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java +++ b/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java @@ -22,54 +22,56 @@ @ExtendWith(MockitoExtension.class) class ProductFactoryTest { - private static final String DUMMY_LOGO_URL = "https://raw.githubusercontent.com/axonivy-market/market/master/market/connector/amazon-comprehend-connector/logo.png"; + private static final String DUMMY_LOGO_URL = + "https://raw.githubusercontent.com/axonivy-market/market/master/market/connector/amazon-comprehend-connector/logo.png"; - @Test - void testMappingByGHContent() throws IOException { - Product product = new Product(); - GHContent mockContent = mock(GHContent.class); - var result = ProductFactory.mappingByGHContent(product, null); - assertEquals(product, result); - when(mockContent.getName()).thenReturn(CommonConstants.META_FILE); - InputStream inputStream = this.getClass().getResourceAsStream(SLASH.concat(META_FILE)); - when(mockContent.read()).thenReturn(inputStream); - result = ProductFactory.mappingByGHContent(product, mockContent); - assertNotEquals(null, result); - assertEquals("Amazon Comprehend", result.getName()); - } + @Test + void testMappingByGHContent() throws IOException { + Product product = new Product(); + GHContent mockContent = mock(GHContent.class); + var result = ProductFactory.mappingByGHContent(product, null); + assertEquals(product, result); + when(mockContent.getName()).thenReturn(CommonConstants.META_FILE); + InputStream inputStream = this.getClass().getResourceAsStream(SLASH.concat(META_FILE)); + when(mockContent.read()).thenReturn(inputStream); + result = ProductFactory.mappingByGHContent(product, mockContent); + assertNotEquals(null, result); + assertEquals("Amazon Comprehend", result.getNames().getEn()); + assertEquals("Amazon Comprehend DE", result.getNames().getDe()); + } - @Test - void testMappingLogo() throws IOException { - Product product = new Product(); - GHContent content = mock(GHContent.class); - when(content.getName()).thenReturn(CommonConstants.LOGO_FILE); - var result = ProductFactory.mappingByGHContent(product, content); - assertNotEquals(null, result); + @Test + void testMappingLogo() throws IOException { + Product product = new Product(); + GHContent content = mock(GHContent.class); + when(content.getName()).thenReturn(CommonConstants.LOGO_FILE); + var result = ProductFactory.mappingByGHContent(product, content); + assertNotEquals(null, result); - when(content.getName()).thenReturn(CommonConstants.LOGO_FILE); - when(content.getDownloadUrl()).thenReturn(DUMMY_LOGO_URL); - result = ProductFactory.mappingByGHContent(product, content); - assertNotEquals(null, result); - } + when(content.getName()).thenReturn(CommonConstants.LOGO_FILE); + when(content.getDownloadUrl()).thenReturn(DUMMY_LOGO_URL); + result = ProductFactory.mappingByGHContent(product, content); + assertNotEquals(null, result); + } - @Test - void testExtractSourceUrl() throws IOException { - Product product = new Product(); - Meta meta = new Meta(); - ProductFactory.extractSourceUrl(product, meta); - Assertions.assertNull(product.getRepositoryName()); - Assertions.assertNull(product.getSourceUrl()); + @Test + void testExtractSourceUrl() throws IOException { + Product product = new Product(); + Meta meta = new Meta(); + ProductFactory.extractSourceUrl(product, meta); + Assertions.assertNull(product.getRepositoryName()); + Assertions.assertNull(product.getSourceUrl()); - String sourceUrl = "https://github.com/axonivy-market/alfresco-connector"; - meta.setSourceUrl(sourceUrl); - ProductFactory.extractSourceUrl(product, meta); - Assertions.assertEquals("axonivy-market/alfresco-connector", product.getRepositoryName()); - Assertions.assertEquals(sourceUrl, product.getSourceUrl()); + String sourceUrl = "https://github.com/axonivy-market/alfresco-connector"; + meta.setSourceUrl(sourceUrl); + ProductFactory.extractSourceUrl(product, meta); + Assertions.assertEquals("axonivy-market/alfresco-connector", product.getRepositoryName()); + Assertions.assertEquals(sourceUrl, product.getSourceUrl()); - sourceUrl = "portal"; - meta.setSourceUrl(sourceUrl); - ProductFactory.extractSourceUrl(product, meta); - Assertions.assertEquals(sourceUrl, product.getRepositoryName()); - Assertions.assertEquals(sourceUrl, product.getSourceUrl()); - } + sourceUrl = "portal"; + meta.setSourceUrl(sourceUrl); + ProductFactory.extractSourceUrl(product, meta); + Assertions.assertEquals(sourceUrl, product.getRepositoryName()); + Assertions.assertEquals(sourceUrl, product.getSourceUrl()); + } } diff --git a/src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java b/src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java index d8b06b074..fa74bc54a 100644 --- a/src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java +++ b/src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java @@ -73,7 +73,7 @@ void testFetchAllMarketItems() throws IOException { mockGhContents.add(mockGHContent); when(mockGHFileContent.isFile()).thenReturn(true); when(pagedGHContent.toList()).thenReturn(List.of(mockGHFileContent)); - when(gitHubService.getDirectoryContent(any(), any())).thenReturn(mockGhContents); + when(gitHubService.getDirectoryContent(any(), any(), any())).thenReturn(mockGhContents); ghContentMap = axonIvyMarketRepoServiceImpl.fetchAllMarketItems(); assertEquals(1, ghContentMap.values().size()); diff --git a/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java b/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java index ea3bd0fe7..e26226c6b 100644 --- a/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java +++ b/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java @@ -41,14 +41,14 @@ void testGetGithubContent() throws IOException { var mockGHContent = mock(GHContent.class); final String dummryURL = DUMMY_API_URL.concat("/dummry-content"); when(mockGHContent.getUrl()).thenReturn(dummryURL); - when(ghRepository.getFileContent(any())).thenReturn(mockGHContent); - var result = gitHubService.getGHContent(ghRepository, ""); + when(ghRepository.getFileContent(any(), any())).thenReturn(mockGHContent); + var result = gitHubService.getGHContent(ghRepository, "", ""); assertEquals(dummryURL, result.getUrl()); } @Test void testGetDirectoryContent() throws IOException { - var result = gitHubService.getDirectoryContent(ghRepository, ""); + var result = gitHubService.getDirectoryContent(ghRepository, "", ""); assertEquals(0, result.size()); } diff --git a/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java b/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java index 5671a21c5..bcebfd4e8 100644 --- a/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java +++ b/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java @@ -46,6 +46,7 @@ import com.axonivy.market.github.model.GitHubFile; import com.axonivy.market.github.service.GHAxonIvyMarketRepoService; import com.axonivy.market.github.service.GitHubService; +import com.axonivy.market.model.MultilingualismValue; import com.axonivy.market.repository.GitHubRepoMetaRepository; import com.axonivy.market.repository.ProductRepository; import com.axonivy.market.service.impl.ProductServiceImpl; @@ -60,6 +61,7 @@ class ProductServiceImplTest { Sort.by(SortOption.ALPHABETICALLY.getOption()).descending()); private static final String SHA1_SAMPLE = "35baa89091b2452b77705da227f1a964ecabc6c8"; private String keyword; + private String langague; private Page mockResultReturn; @Mock @@ -84,21 +86,22 @@ public void setup() { @Test void testFindProducts() { + langague = "en"; // Start testing by All when(productRepository.findAll(any(Pageable.class))).thenReturn(mockResultReturn); // Executes - var result = productService.findProducts(TypeOption.ALL.getOption(), keyword, PAGEABLE); + var result = productService.findProducts(TypeOption.ALL.getOption(), keyword, langague, PAGEABLE); assertEquals(mockResultReturn, result); // Start testing by Connector when(productRepository.findByType(any(), any(Pageable.class))).thenReturn(mockResultReturn); // Executes - result = productService.findProducts(TypeOption.CONNECTORS.getOption(), keyword, PAGEABLE); + result = productService.findProducts(TypeOption.CONNECTORS.getOption(), keyword, langague, PAGEABLE); assertEquals(mockResultReturn, result); // Start testing by Other // Executes - result = productService.findProducts(TypeOption.DEMOS.getOption(), keyword, PAGEABLE); + result = productService.findProducts(TypeOption.DEMOS.getOption(), keyword, langague, PAGEABLE); assertEquals(0, result.getSize()); } @@ -116,7 +119,7 @@ void testSyncProductsAsUpdateMetaJSONFromGitHub() throws IOException { mockGithubFile.setStatus(FileStatus.ADDED); when(marketRepoService.fetchMarketItemsBySHA1Range(any(), any())).thenReturn(List.of(mockGithubFile)); var mockGHContent = mockGHContentAsMetaJSON(); - when(gitHubService.getGHContent(any(), anyString())).thenReturn(mockGHContent); + when(gitHubService.getGHContent(any(), anyString(), anyString())).thenReturn(mockGHContent); // Executes var result = productService.syncLatestDataFromMarketRepo(); @@ -147,7 +150,7 @@ void testSyncProductsAsUpdateLogoFromGitHub() throws IOException { mockGitHubFile.setStatus(FileStatus.ADDED); when(marketRepoService.fetchMarketItemsBySHA1Range(any(), any())).thenReturn(List.of(mockGitHubFile)); var mockGHContent = mockGHContentAsMetaJSON(); - when(gitHubService.getGHContent(any(), anyString())).thenReturn(mockGHContent); + when(gitHubService.getGHContent(any(), anyString(), anyString())).thenReturn(mockGHContent); // Executes var result = productService.syncLatestDataFromMarketRepo(); @@ -157,7 +160,7 @@ void testSyncProductsAsUpdateLogoFromGitHub() throws IOException { when(mockCommit.getSHA1()).thenReturn(UUID.randomUUID().toString()); mockGitHubFile.setStatus(FileStatus.REMOVED); when(marketRepoService.fetchMarketItemsBySHA1Range(any(), any())).thenReturn(List.of(mockGitHubFile)); - when(gitHubService.getGHContent(any(), anyString())).thenReturn(mockGHContent); + when(gitHubService.getGHContent(any(), anyString(), anyString())).thenReturn(mockGHContent); when(productRepository.findByLogoUrl(any())).thenReturn(new Product()); // Executes @@ -167,30 +170,31 @@ void testSyncProductsAsUpdateLogoFromGitHub() throws IOException { @Test void testFindAllProductsWithKeyword() throws IOException { + langague = "en"; when(productRepository.findAll(any(Pageable.class))).thenReturn(mockResultReturn); // Executes - var result = productService.findProducts(TypeOption.ALL.getOption(), keyword, PAGEABLE); + var result = productService.findProducts(TypeOption.ALL.getOption(), keyword, langague, PAGEABLE); assertEquals(mockResultReturn, result); verify(productRepository).findAll(any(Pageable.class)); // Test has keyword - when(productRepository.searchByNameOrShortDescriptionRegex(any(), any(Pageable.class))) + when(productRepository.searchByNameOrShortDescriptionRegex(any(), any(), any(Pageable.class))) .thenReturn(new PageImpl<>(mockResultReturn.stream() - .filter(product -> product.getName().equals(SAMPLE_PRODUCT_NAME)).collect(Collectors.toList()))); + .filter(product -> product.getNames().getEn().equals(SAMPLE_PRODUCT_NAME)).collect(Collectors.toList()))); // Executes - result = productService.findProducts(TypeOption.ALL.getOption(), SAMPLE_PRODUCT_NAME, PAGEABLE); + result = productService.findProducts(TypeOption.ALL.getOption(), SAMPLE_PRODUCT_NAME, langague, PAGEABLE); verify(productRepository).findAll(any(Pageable.class)); assertTrue(result.hasContent()); - assertEquals(SAMPLE_PRODUCT_NAME, result.getContent().get(0).getName()); + assertEquals(SAMPLE_PRODUCT_NAME, result.getContent().get(0).getNames().getEn()); // Test has keyword and type is connector - when(productRepository.searchByKeywordAndType(any(), any(), any(Pageable.class))).thenReturn( - new PageImpl<>(mockResultReturn.stream().filter(product -> product.getName().equals(SAMPLE_PRODUCT_NAME) + when(productRepository.searchByKeywordAndType(any(), any(), any(), any(Pageable.class))).thenReturn( + new PageImpl<>(mockResultReturn.stream().filter(product -> product.getNames().getEn().equals(SAMPLE_PRODUCT_NAME) && product.getType().equals(TypeOption.CONNECTORS.getCode())).collect(Collectors.toList()))); // Executes - result = productService.findProducts(TypeOption.CONNECTORS.getOption(), SAMPLE_PRODUCT_NAME, PAGEABLE); + result = productService.findProducts(TypeOption.CONNECTORS.getOption(), SAMPLE_PRODUCT_NAME, langague, PAGEABLE); assertTrue(result.hasContent()); - assertEquals(SAMPLE_PRODUCT_NAME, result.getContent().get(0).getName()); + assertEquals(SAMPLE_PRODUCT_NAME, result.getContent().get(0).getNames().getEn()); } @Test @@ -230,24 +234,29 @@ void testSearchProducts() { var simplePageable = PageRequest.of(0, 20); String type = TypeOption.ALL.getOption(); keyword = "on"; - when(productRepository.searchByNameOrShortDescriptionRegex(keyword, simplePageable)).thenReturn(mockResultReturn); + langague = "en"; + when(productRepository.searchByNameOrShortDescriptionRegex(keyword, langague, simplePageable)).thenReturn(mockResultReturn); - var result = productService.findProducts(type, keyword, simplePageable); + var result = productService.findProducts(type, keyword, langague, simplePageable); assertEquals(result, mockResultReturn); - verify(productRepository).searchByNameOrShortDescriptionRegex(keyword, simplePageable); + verify(productRepository).searchByNameOrShortDescriptionRegex(keyword, langague, simplePageable); } private Page createPageProductsMock() { var mockProducts = new ArrayList(); + MultilingualismValue name = new MultilingualismValue(); Product mockProduct = new Product(); mockProduct.setId(SAMPLE_PRODUCT_ID); - mockProduct.setName(SAMPLE_PRODUCT_NAME); + name.setEn(SAMPLE_PRODUCT_NAME); + mockProduct.setNames(name); mockProduct.setType("connector"); mockProducts.add(mockProduct); mockProduct = new Product(); mockProduct.setId("tel-search-ch-connector"); - mockProduct.setName("Swiss phone directory"); + name = new MultilingualismValue(); + name.setEn("Swiss phone directory"); + mockProduct.setNames(name); mockProduct.setType("util"); mockProducts.add(mockProduct); return new PageImpl<>(mockProducts); diff --git a/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java b/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java index 932b7a864..f9f75b439 100644 --- a/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java +++ b/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java @@ -1,5 +1,31 @@ package com.axonivy.market.service; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; +import org.assertj.core.api.Fail; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.kohsuke.github.GHContent; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + import com.axonivy.market.constants.MavenConstants; import com.axonivy.market.constants.NonStandardProductPackageConstants; import com.axonivy.market.entity.MavenArtifactModel; @@ -13,26 +39,7 @@ import com.axonivy.market.repository.ProductRepository; import com.axonivy.market.service.impl.VersionServiceImpl; import com.axonivy.market.utils.XmlReaderUtils; -import lombok.extern.log4j.Log4j2; -import org.apache.commons.lang3.StringUtils; -import org.assertj.core.api.Fail; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.kohsuke.github.GHContent; -import org.mockito.*; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.test.util.ReflectionTestUtils; - -import java.io.IOException; -import java.util.*; -@Log4j2 @ExtendWith(MockitoExtension.class) class VersionServiceImplTest { private String repoName; diff --git a/src/test/resources/meta.json b/src/test/resources/meta.json index 90fb8f309..d46c28424 100644 --- a/src/test/resources/meta.json +++ b/src/test/resources/meta.json @@ -1,8 +1,26 @@ { "$schema": "https://json-schema.axonivy.com/market/10.0.0/meta.json", "id": "amazon-comprehend", - "name": "Amazon Comprehend", - "description": "Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data.", + "names": [ + { + "locale":"en", + "value": "Amazon Comprehend" + }, + { + "locale":"de", + "value": "Amazon Comprehend DE" + } + ], + "descriptions": [ + { + "locale":"en", + "value": "Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data." + }, + { + "locale":"de", + "value": "Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data. DE" + } + ], "type": "connector", "platformReview": "4.5", "sourceUrl": "https://github.com/axonivy-market/amazon-comprehend-connector", From 79ce2ac42c967e6b4591fef049afcdf36dad4e07 Mon Sep 17 00:00:00 2001 From: Hoan Nguyen <83745591+nqhoan-axonivy@users.noreply.github.com> Date: Fri, 5 Jul 2024 11:09:46 +0700 Subject: [PATCH 13/62] Feature/marp 566 retructure prepare for docker (#25) Co-authored-by: Hoan Nguyen --- .github/workflows/service-ci-build.yml | 50 + .../{dev-build.yml => service-dev-build.yml} | 0 .github/workflows/ui-ci-build.yml | 57 + .github/workflows/ui-dev-build.yml | 28 + CODE_OF_CONDUCT.md | 24 + marketplace-build/Dockerfile | 1 + .../Marketplace Tomcat v10.1 Server.launch | 22 + marketplace-build/docker-compose.yml | 1 + marketplace-service/.gitignore | 36 + marketplace-service/README.md | 34 + .../lombok.config | 0 pom.xml => marketplace-service/pom.xml | 0 .../sonar-project.properties | 0 .../market/MarketplaceServiceApplication.java | 0 .../axonivy/market/ServletInitializer.java | 0 .../assembler/ProductModelAssembler.java | 0 .../ArchivedArtifactsComparator.java | 0 .../comparator/LatestVersionComparator.java | 0 .../config/MarketApiDocumentConfig.java | 0 .../config/MarketHeaderInterceptor.java | 0 .../axonivy/market/config/MongoConfig.java | 0 .../com/axonivy/market/config/WebConfig.java | 0 .../market/constants/CommonConstants.java | 0 .../market/constants/EntityConstants.java | 0 .../constants/ErrorMessageConstants.java | 0 .../market/constants/GitHubConstants.java | 0 .../market/constants/MavenConstants.java | 0 .../NonStandardProductPackageConstants.java | 0 .../constants/ProductJsonConstants.java | 0 .../constants/RequestMappingConstants.java | 0 .../market/controller/AppController.java | 0 .../market/controller/ProductController.java | 0 .../controller/ProductDetailsController.java | 0 .../market/controller/UserController.java | 0 .../axonivy/market/entity/GitHubRepoMeta.java | 0 .../market/entity/MavenArtifactModel.java | 0 .../market/entity/MavenArtifactVersion.java | 0 .../com/axonivy/market/entity/Product.java | 0 .../java/com/axonivy/market/entity/User.java | 0 .../com/axonivy/market/enums/ErrorCode.java | 0 .../com/axonivy/market/enums/FileStatus.java | 0 .../com/axonivy/market/enums/FileType.java | 0 .../com/axonivy/market/enums/Language.java | 0 .../com/axonivy/market/enums/SortOption.java | 0 .../com/axonivy/market/enums/TypeOption.java | 0 .../market/exceptions/ExceptionHandlers.java | 0 .../model/InvalidParamException.java | 0 .../model/MissingHeaderException.java | 0 .../exceptions/model/NotFoundException.java | 0 .../market/factory/ProductFactory.java | 0 .../market/github/model/ArchivedArtifact.java | 0 .../market/github/model/GitHubFile.java | 0 .../market/github/model/MavenArtifact.java | 0 .../com/axonivy/market/github/model/Meta.java | 0 .../service/GHAxonIvyMarketRepoService.java | 0 .../service/GHAxonIvyProductRepoService.java | 0 .../market/github/service/GitHubService.java | 0 .../impl/GHAxonIvyMarketRepoServiceImpl.java | 0 .../impl/GHAxonIvyProductRepoServiceImpl.java | 0 .../service/impl/GitHubServiceImpl.java | 0 .../market/github/util/GitHubUtils.java | 0 .../axonivy/market/model/DisplayValue.java | 0 .../model/MavenArtifactVersionModel.java | 0 .../com/axonivy/market/model/Message.java | 0 .../market/model/MultilingualismValue.java | 0 .../axonivy/market/model/ProductModel.java | 0 .../repository/GitHubRepoMetaRepository.java | 0 .../MavenArtifactVersionRepository.java | 0 .../market/repository/ProductRepository.java | 0 .../market/repository/UserRepository.java | 0 .../market/schedulingtask/ScheduledTasks.java | 0 .../market/service/ProductService.java | 0 .../axonivy/market/service/UserService.java | 0 .../market/service/VersionService.java | 0 .../service/impl/ProductServiceImpl.java | 0 .../market/service/impl/UserServiceImpl.java | 0 .../service/impl/VersionServiceImpl.java | 0 .../axonivy/market/utils/XmlReaderUtils.java | 0 .../main/resources/application.properties | 0 .../market/controller/AppControllerTest.java | 0 .../controller/ProductControllerTest.java | 0 .../ProductDetailsControllerTest.java | 0 .../market/controller/UserControllerTest.java | 0 .../market/factory/ProductFactoryTest.java | 0 .../GHAxonIvyProductRepoServiceImplTest.java | 0 .../market/handler/ExceptionHandlersTest.java | 0 .../GHAxonIvyMarketRepoServiceImplTest.java | 0 .../market/service/GitHubServiceImplTest.java | 0 .../service/ProductServiceImplTest.java | 0 .../market/service/SchedulingTasksTest.java | 0 .../market/service/UserServiceImplTest.java | 0 .../service/VersionServiceImplTest.java | 0 .../market/utils/XmlReaderUtilsTest.java | 0 .../src}/test/resources/meta.json | 0 marketplace-ui/.editorconfig | 16 + marketplace-ui/.gitignore | 48 + marketplace-ui/.prettierrc | 15 + marketplace-ui/.vscode/extensions.json | 4 + marketplace-ui/.vscode/launch.json | 20 + marketplace-ui/.vscode/tasks.json | 42 + marketplace-ui/README.md | 27 + marketplace-ui/angular.json | 124 + marketplace-ui/karma.conf.js | 58 + marketplace-ui/package-lock.json | 14063 ++++++++++++++++ marketplace-ui/package.json | 55 + marketplace-ui/sonar-project.properties | 5 + marketplace-ui/src/app/app.component.html | 21 + marketplace-ui/src/app/app.component.scss | 44 + marketplace-ui/src/app/app.component.spec.ts | 30 + marketplace-ui/src/app/app.component.ts | 16 + marketplace-ui/src/app/app.config.ts | 33 + marketplace-ui/src/app/app.routes.ts | 14 + .../src/app/core/configs/translate.config.ts | 21 + .../app/core/interceptors/api.interceptor.ts | 46 + .../language/language.service.spec.ts | 30 + .../services/language/language.service.ts | 28 + .../services/loading/loading.service.spec.ts | 26 + .../core/services/loading/loading.service.ts | 17 + .../core/services/theme/theme.service.spec.ts | 42 + .../app/core/services/theme/theme.service.ts | 46 + .../src/app/modules/home/home.component.html | 3 + .../src/app/modules/home/home.component.scss | 7 + .../app/modules/home/home.component.spec.ts | 33 + .../src/app/modules/home/home.component.ts | 11 + .../src/app/modules/home/home.routes.ts | 9 + .../product-card/product-card.component.html | 28 + .../product-card/product-card.component.scss | 26 + .../product-card.component.spec.ts | 54 + .../product-card/product-card.component.ts | 22 + ...oduct-detail-version-action.component.html | 85 + ...oduct-detail-version-action.component.scss | 178 + ...ct-detail-version-action.component.spec.ts | 174 + ...product-detail-version-action.component.ts | 133 + .../product-detail.component.html | 18 + .../product-detail.component.scss | 0 .../product-detail.component.spec.ts | 48 + .../product-detail.component.ts | 33 + .../product-filter.component.html | 79 + .../product-filter.component.scss | 64 + .../product-filter.component.spec.ts | 76 + .../product-filter.component.ts | 47 + .../modules/product/product.component.html | 45 + .../modules/product/product.component.scss | 3 + .../modules/product/product.component.spec.ts | 159 + .../app/modules/product/product.component.ts | 155 + .../src/app/modules/product/product.routes.ts | 11 + .../modules/product/product.service.spec.ts | 183 + .../app/modules/product/product.service.ts | 62 + .../components/footer/footer.component.html | 89 + .../components/footer/footer.component.scss | 38 + .../footer/footer.component.spec.ts | 73 + .../components/footer/footer.component.ts | 24 + .../components/header/header.component.html | 38 + .../components/header/header.component.scss | 59 + .../header/header.component.spec.ts | 105 + .../components/header/header.component.ts | 47 + .../language-selection.component.html | 14 + .../language-selection.component.scss | 18 + .../language-selection.component.spec.ts | 30 + .../language-selection.component.ts | 27 + .../navigation/navigation.component.html | 36 + .../navigation/navigation.component.scss | 61 + .../navigation/navigation.component.spec.ts | 48 + .../header/navigation/navigation.component.ts | 18 + .../search-bar/search-bar.component.html | 81 + .../search-bar/search-bar.component.scss | 33 + .../search-bar/search-bar.component.spec.ts | 74 + .../header/search-bar/search-bar.component.ts | 50 + .../theme-selection.component.html | 9 + .../theme-selection.component.scss | 10 + .../theme-selection.component.spec.ts | 37 + .../theme-selection.component.ts | 13 + .../app/shared/constants/common.constant.ts | 110 + .../src/app/shared/enums/language.enum.ts | 4 + .../src/app/shared/enums/request-param.ts | 6 + .../src/app/shared/enums/sort-option.enum.ts | 5 + .../src/app/shared/enums/theme.enum.ts | 4 + .../src/app/shared/enums/type-option.enum.ts | 7 + .../src/app/shared/mocks/mock-data.ts | 209 + .../src/app/shared/mocks/mock-services.ts | 24 + .../src/app/shared/models/apis/link.model.ts | 14 + .../src/app/shared/models/apis/page.model.ts | 6 + .../models/apis/product-response.model.ts | 11 + .../src/app/shared/models/criteria.model.ts | 10 + .../app/shared/models/display-value.model.ts | 5 + .../app/shared/models/maven-artifact.model.ts | 16 + .../src/app/shared/models/nav-item.model.ts | 4 + .../src/app/shared/models/product.model.ts | 33 + .../shared/models/vesion-artifact.model.ts | 10 + .../src/app/shared/pipes/logo.pipe.ts | 16 + .../app/shared/pipes/multilingualism.pipe.ts | 21 + marketplace-ui/src/assets/.gitkeep | 0 marketplace-ui/src/assets/fonts/inter.ttf | Bin 0 -> 804612 bytes marketplace-ui/src/assets/i18n/de.yaml | 55 + marketplace-ui/src/assets/i18n/en.yaml | 59 + .../assets/images/misc/axonivy-logo-black.svg | 1 + .../assets/images/misc/axonivy-logo-round.png | Bin 0 -> 2418 bytes .../src/assets/images/misc/axonivy-logo.svg | 1 + .../src/assets/scss/custom-style.scss | 243 + .../environments/environment.development.ts | 4 + .../src/environments/environment.ts | 4 + marketplace-ui/src/favicon.ico | Bin 0 -> 701 bytes marketplace-ui/src/index.html | 13 + marketplace-ui/src/main.ts | 9 + marketplace-ui/src/styles.scss | 9 + marketplace-ui/tsconfig.app.json | 17 + marketplace-ui/tsconfig.json | 33 + marketplace-ui/tsconfig.spec.json | 15 + src/main/resources/github.token | 1 - 209 files changed, 18900 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/service-ci-build.yml rename .github/workflows/{dev-build.yml => service-dev-build.yml} (100%) create mode 100644 .github/workflows/ui-ci-build.yml create mode 100644 .github/workflows/ui-dev-build.yml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 marketplace-build/Dockerfile create mode 100644 marketplace-build/Marketplace Tomcat v10.1 Server.launch create mode 100644 marketplace-build/docker-compose.yml create mode 100644 marketplace-service/.gitignore create mode 100644 marketplace-service/README.md rename lombok.config => marketplace-service/lombok.config (100%) rename pom.xml => marketplace-service/pom.xml (100%) rename sonar-project.properties => marketplace-service/sonar-project.properties (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/MarketplaceServiceApplication.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/ServletInitializer.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/assembler/ProductModelAssembler.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/comparator/ArchivedArtifactsComparator.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/comparator/LatestVersionComparator.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/config/MarketApiDocumentConfig.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/config/MarketHeaderInterceptor.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/config/MongoConfig.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/config/WebConfig.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/constants/CommonConstants.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/constants/EntityConstants.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/constants/ErrorMessageConstants.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/constants/GitHubConstants.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/constants/MavenConstants.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/constants/NonStandardProductPackageConstants.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/constants/ProductJsonConstants.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/constants/RequestMappingConstants.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/controller/AppController.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/controller/ProductController.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/controller/ProductDetailsController.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/controller/UserController.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/entity/GitHubRepoMeta.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/entity/MavenArtifactModel.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/entity/MavenArtifactVersion.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/entity/Product.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/entity/User.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/enums/ErrorCode.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/enums/FileStatus.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/enums/FileType.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/enums/Language.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/enums/SortOption.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/enums/TypeOption.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/exceptions/ExceptionHandlers.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/exceptions/model/InvalidParamException.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/exceptions/model/MissingHeaderException.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/exceptions/model/NotFoundException.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/factory/ProductFactory.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/github/model/ArchivedArtifact.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/github/model/GitHubFile.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/github/model/MavenArtifact.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/github/model/Meta.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/github/service/GHAxonIvyMarketRepoService.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/github/service/GHAxonIvyProductRepoService.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/github/service/GitHubService.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/github/util/GitHubUtils.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/model/DisplayValue.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/model/MavenArtifactVersionModel.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/model/Message.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/model/MultilingualismValue.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/model/ProductModel.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/repository/GitHubRepoMetaRepository.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/repository/MavenArtifactVersionRepository.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/repository/ProductRepository.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/repository/UserRepository.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/schedulingtask/ScheduledTasks.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/service/ProductService.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/service/UserService.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/service/VersionService.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/service/impl/UserServiceImpl.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java (100%) rename {src => marketplace-service/src}/main/java/com/axonivy/market/utils/XmlReaderUtils.java (100%) rename {src => marketplace-service/src}/main/resources/application.properties (100%) rename {src => marketplace-service/src}/test/java/com/axonivy/market/controller/AppControllerTest.java (100%) rename {src => marketplace-service/src}/test/java/com/axonivy/market/controller/ProductControllerTest.java (100%) rename {src => marketplace-service/src}/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java (100%) rename {src => marketplace-service/src}/test/java/com/axonivy/market/controller/UserControllerTest.java (100%) rename {src => marketplace-service/src}/test/java/com/axonivy/market/factory/ProductFactoryTest.java (100%) rename {src => marketplace-service/src}/test/java/com/axonivy/market/github/service/GHAxonIvyProductRepoServiceImplTest.java (100%) rename {src => marketplace-service/src}/test/java/com/axonivy/market/handler/ExceptionHandlersTest.java (100%) rename {src => marketplace-service/src}/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java (100%) rename {src => marketplace-service/src}/test/java/com/axonivy/market/service/GitHubServiceImplTest.java (100%) rename {src => marketplace-service/src}/test/java/com/axonivy/market/service/ProductServiceImplTest.java (100%) rename {src => marketplace-service/src}/test/java/com/axonivy/market/service/SchedulingTasksTest.java (100%) rename {src => marketplace-service/src}/test/java/com/axonivy/market/service/UserServiceImplTest.java (100%) rename {src => marketplace-service/src}/test/java/com/axonivy/market/service/VersionServiceImplTest.java (100%) rename {src => marketplace-service/src}/test/java/com/axonivy/market/utils/XmlReaderUtilsTest.java (100%) rename {src => marketplace-service/src}/test/resources/meta.json (100%) create mode 100644 marketplace-ui/.editorconfig create mode 100644 marketplace-ui/.gitignore create mode 100644 marketplace-ui/.prettierrc create mode 100644 marketplace-ui/.vscode/extensions.json create mode 100644 marketplace-ui/.vscode/launch.json create mode 100644 marketplace-ui/.vscode/tasks.json create mode 100644 marketplace-ui/README.md create mode 100644 marketplace-ui/angular.json create mode 100644 marketplace-ui/karma.conf.js create mode 100644 marketplace-ui/package-lock.json create mode 100644 marketplace-ui/package.json create mode 100644 marketplace-ui/sonar-project.properties create mode 100644 marketplace-ui/src/app/app.component.html create mode 100644 marketplace-ui/src/app/app.component.scss create mode 100644 marketplace-ui/src/app/app.component.spec.ts create mode 100644 marketplace-ui/src/app/app.component.ts create mode 100644 marketplace-ui/src/app/app.config.ts create mode 100644 marketplace-ui/src/app/app.routes.ts create mode 100644 marketplace-ui/src/app/core/configs/translate.config.ts create mode 100644 marketplace-ui/src/app/core/interceptors/api.interceptor.ts create mode 100644 marketplace-ui/src/app/core/services/language/language.service.spec.ts create mode 100644 marketplace-ui/src/app/core/services/language/language.service.ts create mode 100644 marketplace-ui/src/app/core/services/loading/loading.service.spec.ts create mode 100644 marketplace-ui/src/app/core/services/loading/loading.service.ts create mode 100644 marketplace-ui/src/app/core/services/theme/theme.service.spec.ts create mode 100644 marketplace-ui/src/app/core/services/theme/theme.service.ts create mode 100644 marketplace-ui/src/app/modules/home/home.component.html create mode 100644 marketplace-ui/src/app/modules/home/home.component.scss create mode 100644 marketplace-ui/src/app/modules/home/home.component.spec.ts create mode 100644 marketplace-ui/src/app/modules/home/home.component.ts create mode 100644 marketplace-ui/src/app/modules/home/home.routes.ts create mode 100644 marketplace-ui/src/app/modules/product/product-card/product-card.component.html create mode 100644 marketplace-ui/src/app/modules/product/product-card/product-card.component.scss create mode 100644 marketplace-ui/src/app/modules/product/product-card/product-card.component.spec.ts create mode 100644 marketplace-ui/src/app/modules/product/product-card/product-card.component.ts create mode 100644 marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.html create mode 100644 marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.scss create mode 100644 marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts create mode 100644 marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.ts create mode 100644 marketplace-ui/src/app/modules/product/product-detail/product-detail.component.html create mode 100644 marketplace-ui/src/app/modules/product/product-detail/product-detail.component.scss create mode 100644 marketplace-ui/src/app/modules/product/product-detail/product-detail.component.spec.ts create mode 100644 marketplace-ui/src/app/modules/product/product-detail/product-detail.component.ts create mode 100644 marketplace-ui/src/app/modules/product/product-filter/product-filter.component.html create mode 100644 marketplace-ui/src/app/modules/product/product-filter/product-filter.component.scss create mode 100644 marketplace-ui/src/app/modules/product/product-filter/product-filter.component.spec.ts create mode 100644 marketplace-ui/src/app/modules/product/product-filter/product-filter.component.ts create mode 100644 marketplace-ui/src/app/modules/product/product.component.html create mode 100644 marketplace-ui/src/app/modules/product/product.component.scss create mode 100644 marketplace-ui/src/app/modules/product/product.component.spec.ts create mode 100644 marketplace-ui/src/app/modules/product/product.component.ts create mode 100644 marketplace-ui/src/app/modules/product/product.routes.ts create mode 100644 marketplace-ui/src/app/modules/product/product.service.spec.ts create mode 100644 marketplace-ui/src/app/modules/product/product.service.ts create mode 100644 marketplace-ui/src/app/shared/components/footer/footer.component.html create mode 100644 marketplace-ui/src/app/shared/components/footer/footer.component.scss create mode 100644 marketplace-ui/src/app/shared/components/footer/footer.component.spec.ts create mode 100644 marketplace-ui/src/app/shared/components/footer/footer.component.ts create mode 100644 marketplace-ui/src/app/shared/components/header/header.component.html create mode 100644 marketplace-ui/src/app/shared/components/header/header.component.scss create mode 100644 marketplace-ui/src/app/shared/components/header/header.component.spec.ts create mode 100644 marketplace-ui/src/app/shared/components/header/header.component.ts create mode 100644 marketplace-ui/src/app/shared/components/header/language-selection/language-selection.component.html create mode 100644 marketplace-ui/src/app/shared/components/header/language-selection/language-selection.component.scss create mode 100644 marketplace-ui/src/app/shared/components/header/language-selection/language-selection.component.spec.ts create mode 100644 marketplace-ui/src/app/shared/components/header/language-selection/language-selection.component.ts create mode 100644 marketplace-ui/src/app/shared/components/header/navigation/navigation.component.html create mode 100644 marketplace-ui/src/app/shared/components/header/navigation/navigation.component.scss create mode 100644 marketplace-ui/src/app/shared/components/header/navigation/navigation.component.spec.ts create mode 100644 marketplace-ui/src/app/shared/components/header/navigation/navigation.component.ts create mode 100644 marketplace-ui/src/app/shared/components/header/search-bar/search-bar.component.html create mode 100644 marketplace-ui/src/app/shared/components/header/search-bar/search-bar.component.scss create mode 100644 marketplace-ui/src/app/shared/components/header/search-bar/search-bar.component.spec.ts create mode 100644 marketplace-ui/src/app/shared/components/header/search-bar/search-bar.component.ts create mode 100644 marketplace-ui/src/app/shared/components/header/theme-selection/theme-selection.component.html create mode 100644 marketplace-ui/src/app/shared/components/header/theme-selection/theme-selection.component.scss create mode 100644 marketplace-ui/src/app/shared/components/header/theme-selection/theme-selection.component.spec.ts create mode 100644 marketplace-ui/src/app/shared/components/header/theme-selection/theme-selection.component.ts create mode 100644 marketplace-ui/src/app/shared/constants/common.constant.ts create mode 100644 marketplace-ui/src/app/shared/enums/language.enum.ts create mode 100644 marketplace-ui/src/app/shared/enums/request-param.ts create mode 100644 marketplace-ui/src/app/shared/enums/sort-option.enum.ts create mode 100644 marketplace-ui/src/app/shared/enums/theme.enum.ts create mode 100644 marketplace-ui/src/app/shared/enums/type-option.enum.ts create mode 100644 marketplace-ui/src/app/shared/mocks/mock-data.ts create mode 100644 marketplace-ui/src/app/shared/mocks/mock-services.ts create mode 100644 marketplace-ui/src/app/shared/models/apis/link.model.ts create mode 100644 marketplace-ui/src/app/shared/models/apis/page.model.ts create mode 100644 marketplace-ui/src/app/shared/models/apis/product-response.model.ts create mode 100644 marketplace-ui/src/app/shared/models/criteria.model.ts create mode 100644 marketplace-ui/src/app/shared/models/display-value.model.ts create mode 100644 marketplace-ui/src/app/shared/models/maven-artifact.model.ts create mode 100644 marketplace-ui/src/app/shared/models/nav-item.model.ts create mode 100644 marketplace-ui/src/app/shared/models/product.model.ts create mode 100644 marketplace-ui/src/app/shared/models/vesion-artifact.model.ts create mode 100644 marketplace-ui/src/app/shared/pipes/logo.pipe.ts create mode 100644 marketplace-ui/src/app/shared/pipes/multilingualism.pipe.ts create mode 100644 marketplace-ui/src/assets/.gitkeep create mode 100644 marketplace-ui/src/assets/fonts/inter.ttf create mode 100644 marketplace-ui/src/assets/i18n/de.yaml create mode 100644 marketplace-ui/src/assets/i18n/en.yaml create mode 100644 marketplace-ui/src/assets/images/misc/axonivy-logo-black.svg create mode 100644 marketplace-ui/src/assets/images/misc/axonivy-logo-round.png create mode 100644 marketplace-ui/src/assets/images/misc/axonivy-logo.svg create mode 100644 marketplace-ui/src/assets/scss/custom-style.scss create mode 100644 marketplace-ui/src/environments/environment.development.ts create mode 100644 marketplace-ui/src/environments/environment.ts create mode 100644 marketplace-ui/src/favicon.ico create mode 100644 marketplace-ui/src/index.html create mode 100644 marketplace-ui/src/main.ts create mode 100644 marketplace-ui/src/styles.scss create mode 100644 marketplace-ui/tsconfig.app.json create mode 100644 marketplace-ui/tsconfig.json create mode 100644 marketplace-ui/tsconfig.spec.json delete mode 100644 src/main/resources/github.token diff --git a/.github/workflows/service-ci-build.yml b/.github/workflows/service-ci-build.yml new file mode 100644 index 000000000..86a8ff9ed --- /dev/null +++ b/.github/workflows/service-ci-build.yml @@ -0,0 +1,50 @@ +name: CI Build +run-name: Build on branch ${{github.ref_name}} triggered by ${{github.actor}} + +on: + push: + workflow_dispatch: + +jobs: + build: + name: Executes Tests + runs-on: self-hosted + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + - name: Tests with Maven + run: mvn clean install + analysis: + name: Sonarqube analysis + needs: build + runs-on: self-hosted + env: + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_PROJECT_KEY : ${{ secrets.SONAR_PROJECT_KEY }} + steps: + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Run SonarQube Scanner + run: | + mvn -B verify sonar:sonar \ + -Dsonar.host.url=${{ env.SONAR_HOST_URL }} \ + -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }} \ + -Dsonar.projectName="AxonIvy Market Service" \ + -Dsonar.token=${{ env.SONAR_TOKEN }} \ + - name: SonarQube Quality Gate check + id: sonarqube-quality-gate-check + uses: sonarsource/sonarqube-quality-gate-action@master + timeout-minutes: 5 + with: + scanMetadataReportFile: target/sonar/report-task.txt + args: -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }} diff --git a/.github/workflows/dev-build.yml b/.github/workflows/service-dev-build.yml similarity index 100% rename from .github/workflows/dev-build.yml rename to .github/workflows/service-dev-build.yml diff --git a/.github/workflows/ui-ci-build.yml b/.github/workflows/ui-ci-build.yml new file mode 100644 index 000000000..8a437d1f4 --- /dev/null +++ b/.github/workflows/ui-ci-build.yml @@ -0,0 +1,57 @@ +name: CI Build +run-name: Build on branch ${{github.ref_name}} triggered by ${{github.actor}} + +on: + push: + branches-ignore: + - develop + - master + workflow_dispatch: + +jobs: + build: + name: Build + runs-on: self-hosted + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + - name: Install Dependencies + run: npm install + - name: Build project + run: npm run build + + analysis: + name: Sonarqube + needs: build + runs-on: self-hosted + env: + SONAR_PROJECT_KEY: 'AxonIvy-Market-UI' + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + steps: + - name: Execute Tests + run: npm run test + - uses: sonarsource/sonarqube-scan-action@master + env: + SONAR_TOKEN: ${{ env.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ env.SONAR_HOST_URL }} + with: + args: + -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }} + - name: SonarQube Quality Gate check + id: sonarqube-quality-gate-check + uses: sonarsource/sonarqube-quality-gate-action@master + timeout-minutes: 5 + env: + SONAR_TOKEN: ${{ env.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ env.SONAR_HOST_URL }} + + - name: Clean up + run: | + rm -rf * diff --git a/.github/workflows/ui-dev-build.yml b/.github/workflows/ui-dev-build.yml new file mode 100644 index 000000000..cb34d8665 --- /dev/null +++ b/.github/workflows/ui-dev-build.yml @@ -0,0 +1,28 @@ +name: Dev Build +run-name: Build and Deploy Marketplace-UI on branch ${{github.ref_name}} by ${{github.actor}} + +on: + push: + branches: [ "develop" ] + workflow_dispatch: + +jobs: + build: + name: Build and deploy new code to Deployment directory + runs-on: self-hosted + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + - name: Install Dependencies + run: npm install + - name: Build Angular app + run: npm run build -- --configuration production --output-path=dist + - name: Execute Tests + run: npm run test + - name: Copy files to Deployment directory + if: success() + run: sudo cp -r dist/* /var/www/marketplace-ui diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..ec0fe32aa --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,24 @@ +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone. +As part of the Ricoh Group, Axon Ivy is guided by [The spirit of the three loves](https://www.ricoh.com/about/ricoh-way): + +- **Love your neighbor** 🤝 +We love to get in touch with people and are willing to help others when we are aware of their issues and ideas. Everyone who participates as a user or contributor in this repository is our neighbor. + +- **Love your country** 🗺 +We love the place we’re located at and enjoy the nature around us. We take care of the environment and are eager to learn from cultures around the globe. + +- **Love your work** 👷♂️ +We are passionate developers, eager to work with new technologies, and are happy to be part of the digital transformation. We love to be creative at work and see our visions accomplished. + +## Our Guidelines + +This repository is intended to facilitate a friendly and inspiring exchange in which we focus on technical content. + +- Be friendly and patient. +- Be welcoming. +- Be considerate. +- Be respectful. +- Be careful in the words that you choose. +- When we disagree, try to understand why. diff --git a/marketplace-build/Dockerfile b/marketplace-build/Dockerfile new file mode 100644 index 000000000..f6e0339af --- /dev/null +++ b/marketplace-build/Dockerfile @@ -0,0 +1 @@ +Placeholder \ No newline at end of file diff --git a/marketplace-build/Marketplace Tomcat v10.1 Server.launch b/marketplace-build/Marketplace Tomcat v10.1 Server.launch new file mode 100644 index 000000000..5e4b64925 --- /dev/null +++ b/marketplace-build/Marketplace Tomcat v10.1 Server.launch @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/marketplace-build/docker-compose.yml b/marketplace-build/docker-compose.yml new file mode 100644 index 000000000..f6e0339af --- /dev/null +++ b/marketplace-build/docker-compose.yml @@ -0,0 +1 @@ +Placeholder \ No newline at end of file diff --git a/marketplace-service/.gitignore b/marketplace-service/.gitignore new file mode 100644 index 000000000..3ccbd3e32 --- /dev/null +++ b/marketplace-service/.gitignore @@ -0,0 +1,36 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +/bin/ + +### Token +*.token diff --git a/marketplace-service/README.md b/marketplace-service/README.md new file mode 100644 index 000000000..4d546f33a --- /dev/null +++ b/marketplace-service/README.md @@ -0,0 +1,34 @@ +# Getting Started + +### Reference Documentation +For further reference, please consider the following sections: + +* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) +* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.2.5/maven-plugin/reference/html/) +* [Spring Data MongoDB](https://docs.spring.io/spring-boot/docs/3.2.5/reference/htmlsingle/index.html#data.nosql.mongodb) +* [Spring Web](https://docs.spring.io/spring-boot/docs/3.2.5/reference/htmlsingle/index.html#web) + +### Guides +The following guides illustrate how to use some features concretely: + +* Installing mongodb, and access it as Url mongodb://localhost:27017/, and you can create and name whatever you want ,then you should put them to application.properties +* You can change the MongoDB configuration in file `application.properties` + ``` + spring.data.mongodb.host= + spring.data.mongodb.database= + ``` +* Update GitHub token in file `github.token` +* Run mvn clean install to build project +* Run mvn test to test all tests + + +### Access Swagger URL: http://{your-host}/swagger-ui/index.html + +### Install Lombok for Eclipse IDE +* Download lombok here https://projectlombok.org/download +* run command "java -jar lombok.jar" then you can access file “eclipse.ini“ in eclipse folder where you install → there is a text like this: -javaagent:C:\Users\tvtphuc\eclipse\jee-2024-032\eclipse\lombok.jar → it means you are successful +* Start eclipse +* Import the project then in the eclipse , you should run the command “mvn clean install“ +* After that you go to class MarketplaceServiceApplication → right click to main method → click run as → choose Java Application +* Then you can send a request in postman +* If you want to run single test in class UserServiceImplTest. You can right-click to method testFindAllUser and right click → select Run as → choose JUnit Test \ No newline at end of file diff --git a/lombok.config b/marketplace-service/lombok.config similarity index 100% rename from lombok.config rename to marketplace-service/lombok.config diff --git a/pom.xml b/marketplace-service/pom.xml similarity index 100% rename from pom.xml rename to marketplace-service/pom.xml diff --git a/sonar-project.properties b/marketplace-service/sonar-project.properties similarity index 100% rename from sonar-project.properties rename to marketplace-service/sonar-project.properties diff --git a/src/main/java/com/axonivy/market/MarketplaceServiceApplication.java b/marketplace-service/src/main/java/com/axonivy/market/MarketplaceServiceApplication.java similarity index 100% rename from src/main/java/com/axonivy/market/MarketplaceServiceApplication.java rename to marketplace-service/src/main/java/com/axonivy/market/MarketplaceServiceApplication.java diff --git a/src/main/java/com/axonivy/market/ServletInitializer.java b/marketplace-service/src/main/java/com/axonivy/market/ServletInitializer.java similarity index 100% rename from src/main/java/com/axonivy/market/ServletInitializer.java rename to marketplace-service/src/main/java/com/axonivy/market/ServletInitializer.java diff --git a/src/main/java/com/axonivy/market/assembler/ProductModelAssembler.java b/marketplace-service/src/main/java/com/axonivy/market/assembler/ProductModelAssembler.java similarity index 100% rename from src/main/java/com/axonivy/market/assembler/ProductModelAssembler.java rename to marketplace-service/src/main/java/com/axonivy/market/assembler/ProductModelAssembler.java diff --git a/src/main/java/com/axonivy/market/comparator/ArchivedArtifactsComparator.java b/marketplace-service/src/main/java/com/axonivy/market/comparator/ArchivedArtifactsComparator.java similarity index 100% rename from src/main/java/com/axonivy/market/comparator/ArchivedArtifactsComparator.java rename to marketplace-service/src/main/java/com/axonivy/market/comparator/ArchivedArtifactsComparator.java diff --git a/src/main/java/com/axonivy/market/comparator/LatestVersionComparator.java b/marketplace-service/src/main/java/com/axonivy/market/comparator/LatestVersionComparator.java similarity index 100% rename from src/main/java/com/axonivy/market/comparator/LatestVersionComparator.java rename to marketplace-service/src/main/java/com/axonivy/market/comparator/LatestVersionComparator.java diff --git a/src/main/java/com/axonivy/market/config/MarketApiDocumentConfig.java b/marketplace-service/src/main/java/com/axonivy/market/config/MarketApiDocumentConfig.java similarity index 100% rename from src/main/java/com/axonivy/market/config/MarketApiDocumentConfig.java rename to marketplace-service/src/main/java/com/axonivy/market/config/MarketApiDocumentConfig.java diff --git a/src/main/java/com/axonivy/market/config/MarketHeaderInterceptor.java b/marketplace-service/src/main/java/com/axonivy/market/config/MarketHeaderInterceptor.java similarity index 100% rename from src/main/java/com/axonivy/market/config/MarketHeaderInterceptor.java rename to marketplace-service/src/main/java/com/axonivy/market/config/MarketHeaderInterceptor.java diff --git a/src/main/java/com/axonivy/market/config/MongoConfig.java b/marketplace-service/src/main/java/com/axonivy/market/config/MongoConfig.java similarity index 100% rename from src/main/java/com/axonivy/market/config/MongoConfig.java rename to marketplace-service/src/main/java/com/axonivy/market/config/MongoConfig.java diff --git a/src/main/java/com/axonivy/market/config/WebConfig.java b/marketplace-service/src/main/java/com/axonivy/market/config/WebConfig.java similarity index 100% rename from src/main/java/com/axonivy/market/config/WebConfig.java rename to marketplace-service/src/main/java/com/axonivy/market/config/WebConfig.java diff --git a/src/main/java/com/axonivy/market/constants/CommonConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/CommonConstants.java similarity index 100% rename from src/main/java/com/axonivy/market/constants/CommonConstants.java rename to marketplace-service/src/main/java/com/axonivy/market/constants/CommonConstants.java diff --git a/src/main/java/com/axonivy/market/constants/EntityConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/EntityConstants.java similarity index 100% rename from src/main/java/com/axonivy/market/constants/EntityConstants.java rename to marketplace-service/src/main/java/com/axonivy/market/constants/EntityConstants.java diff --git a/src/main/java/com/axonivy/market/constants/ErrorMessageConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/ErrorMessageConstants.java similarity index 100% rename from src/main/java/com/axonivy/market/constants/ErrorMessageConstants.java rename to marketplace-service/src/main/java/com/axonivy/market/constants/ErrorMessageConstants.java diff --git a/src/main/java/com/axonivy/market/constants/GitHubConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/GitHubConstants.java similarity index 100% rename from src/main/java/com/axonivy/market/constants/GitHubConstants.java rename to marketplace-service/src/main/java/com/axonivy/market/constants/GitHubConstants.java diff --git a/src/main/java/com/axonivy/market/constants/MavenConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/MavenConstants.java similarity index 100% rename from src/main/java/com/axonivy/market/constants/MavenConstants.java rename to marketplace-service/src/main/java/com/axonivy/market/constants/MavenConstants.java diff --git a/src/main/java/com/axonivy/market/constants/NonStandardProductPackageConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/NonStandardProductPackageConstants.java similarity index 100% rename from src/main/java/com/axonivy/market/constants/NonStandardProductPackageConstants.java rename to marketplace-service/src/main/java/com/axonivy/market/constants/NonStandardProductPackageConstants.java diff --git a/src/main/java/com/axonivy/market/constants/ProductJsonConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/ProductJsonConstants.java similarity index 100% rename from src/main/java/com/axonivy/market/constants/ProductJsonConstants.java rename to marketplace-service/src/main/java/com/axonivy/market/constants/ProductJsonConstants.java diff --git a/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java similarity index 100% rename from src/main/java/com/axonivy/market/constants/RequestMappingConstants.java rename to marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java diff --git a/src/main/java/com/axonivy/market/controller/AppController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/AppController.java similarity index 100% rename from src/main/java/com/axonivy/market/controller/AppController.java rename to marketplace-service/src/main/java/com/axonivy/market/controller/AppController.java diff --git a/src/main/java/com/axonivy/market/controller/ProductController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java similarity index 100% rename from src/main/java/com/axonivy/market/controller/ProductController.java rename to marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java diff --git a/src/main/java/com/axonivy/market/controller/ProductDetailsController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDetailsController.java similarity index 100% rename from src/main/java/com/axonivy/market/controller/ProductDetailsController.java rename to marketplace-service/src/main/java/com/axonivy/market/controller/ProductDetailsController.java diff --git a/src/main/java/com/axonivy/market/controller/UserController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/UserController.java similarity index 100% rename from src/main/java/com/axonivy/market/controller/UserController.java rename to marketplace-service/src/main/java/com/axonivy/market/controller/UserController.java diff --git a/src/main/java/com/axonivy/market/entity/GitHubRepoMeta.java b/marketplace-service/src/main/java/com/axonivy/market/entity/GitHubRepoMeta.java similarity index 100% rename from src/main/java/com/axonivy/market/entity/GitHubRepoMeta.java rename to marketplace-service/src/main/java/com/axonivy/market/entity/GitHubRepoMeta.java diff --git a/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java b/marketplace-service/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java similarity index 100% rename from src/main/java/com/axonivy/market/entity/MavenArtifactModel.java rename to marketplace-service/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java diff --git a/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java b/marketplace-service/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java similarity index 100% rename from src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java rename to marketplace-service/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java diff --git a/src/main/java/com/axonivy/market/entity/Product.java b/marketplace-service/src/main/java/com/axonivy/market/entity/Product.java similarity index 100% rename from src/main/java/com/axonivy/market/entity/Product.java rename to marketplace-service/src/main/java/com/axonivy/market/entity/Product.java diff --git a/src/main/java/com/axonivy/market/entity/User.java b/marketplace-service/src/main/java/com/axonivy/market/entity/User.java similarity index 100% rename from src/main/java/com/axonivy/market/entity/User.java rename to marketplace-service/src/main/java/com/axonivy/market/entity/User.java diff --git a/src/main/java/com/axonivy/market/enums/ErrorCode.java b/marketplace-service/src/main/java/com/axonivy/market/enums/ErrorCode.java similarity index 100% rename from src/main/java/com/axonivy/market/enums/ErrorCode.java rename to marketplace-service/src/main/java/com/axonivy/market/enums/ErrorCode.java diff --git a/src/main/java/com/axonivy/market/enums/FileStatus.java b/marketplace-service/src/main/java/com/axonivy/market/enums/FileStatus.java similarity index 100% rename from src/main/java/com/axonivy/market/enums/FileStatus.java rename to marketplace-service/src/main/java/com/axonivy/market/enums/FileStatus.java diff --git a/src/main/java/com/axonivy/market/enums/FileType.java b/marketplace-service/src/main/java/com/axonivy/market/enums/FileType.java similarity index 100% rename from src/main/java/com/axonivy/market/enums/FileType.java rename to marketplace-service/src/main/java/com/axonivy/market/enums/FileType.java diff --git a/src/main/java/com/axonivy/market/enums/Language.java b/marketplace-service/src/main/java/com/axonivy/market/enums/Language.java similarity index 100% rename from src/main/java/com/axonivy/market/enums/Language.java rename to marketplace-service/src/main/java/com/axonivy/market/enums/Language.java diff --git a/src/main/java/com/axonivy/market/enums/SortOption.java b/marketplace-service/src/main/java/com/axonivy/market/enums/SortOption.java similarity index 100% rename from src/main/java/com/axonivy/market/enums/SortOption.java rename to marketplace-service/src/main/java/com/axonivy/market/enums/SortOption.java diff --git a/src/main/java/com/axonivy/market/enums/TypeOption.java b/marketplace-service/src/main/java/com/axonivy/market/enums/TypeOption.java similarity index 100% rename from src/main/java/com/axonivy/market/enums/TypeOption.java rename to marketplace-service/src/main/java/com/axonivy/market/enums/TypeOption.java diff --git a/src/main/java/com/axonivy/market/exceptions/ExceptionHandlers.java b/marketplace-service/src/main/java/com/axonivy/market/exceptions/ExceptionHandlers.java similarity index 100% rename from src/main/java/com/axonivy/market/exceptions/ExceptionHandlers.java rename to marketplace-service/src/main/java/com/axonivy/market/exceptions/ExceptionHandlers.java diff --git a/src/main/java/com/axonivy/market/exceptions/model/InvalidParamException.java b/marketplace-service/src/main/java/com/axonivy/market/exceptions/model/InvalidParamException.java similarity index 100% rename from src/main/java/com/axonivy/market/exceptions/model/InvalidParamException.java rename to marketplace-service/src/main/java/com/axonivy/market/exceptions/model/InvalidParamException.java diff --git a/src/main/java/com/axonivy/market/exceptions/model/MissingHeaderException.java b/marketplace-service/src/main/java/com/axonivy/market/exceptions/model/MissingHeaderException.java similarity index 100% rename from src/main/java/com/axonivy/market/exceptions/model/MissingHeaderException.java rename to marketplace-service/src/main/java/com/axonivy/market/exceptions/model/MissingHeaderException.java diff --git a/src/main/java/com/axonivy/market/exceptions/model/NotFoundException.java b/marketplace-service/src/main/java/com/axonivy/market/exceptions/model/NotFoundException.java similarity index 100% rename from src/main/java/com/axonivy/market/exceptions/model/NotFoundException.java rename to marketplace-service/src/main/java/com/axonivy/market/exceptions/model/NotFoundException.java diff --git a/src/main/java/com/axonivy/market/factory/ProductFactory.java b/marketplace-service/src/main/java/com/axonivy/market/factory/ProductFactory.java similarity index 100% rename from src/main/java/com/axonivy/market/factory/ProductFactory.java rename to marketplace-service/src/main/java/com/axonivy/market/factory/ProductFactory.java diff --git a/src/main/java/com/axonivy/market/github/model/ArchivedArtifact.java b/marketplace-service/src/main/java/com/axonivy/market/github/model/ArchivedArtifact.java similarity index 100% rename from src/main/java/com/axonivy/market/github/model/ArchivedArtifact.java rename to marketplace-service/src/main/java/com/axonivy/market/github/model/ArchivedArtifact.java diff --git a/src/main/java/com/axonivy/market/github/model/GitHubFile.java b/marketplace-service/src/main/java/com/axonivy/market/github/model/GitHubFile.java similarity index 100% rename from src/main/java/com/axonivy/market/github/model/GitHubFile.java rename to marketplace-service/src/main/java/com/axonivy/market/github/model/GitHubFile.java diff --git a/src/main/java/com/axonivy/market/github/model/MavenArtifact.java b/marketplace-service/src/main/java/com/axonivy/market/github/model/MavenArtifact.java similarity index 100% rename from src/main/java/com/axonivy/market/github/model/MavenArtifact.java rename to marketplace-service/src/main/java/com/axonivy/market/github/model/MavenArtifact.java diff --git a/src/main/java/com/axonivy/market/github/model/Meta.java b/marketplace-service/src/main/java/com/axonivy/market/github/model/Meta.java similarity index 100% rename from src/main/java/com/axonivy/market/github/model/Meta.java rename to marketplace-service/src/main/java/com/axonivy/market/github/model/Meta.java diff --git a/src/main/java/com/axonivy/market/github/service/GHAxonIvyMarketRepoService.java b/marketplace-service/src/main/java/com/axonivy/market/github/service/GHAxonIvyMarketRepoService.java similarity index 100% rename from src/main/java/com/axonivy/market/github/service/GHAxonIvyMarketRepoService.java rename to marketplace-service/src/main/java/com/axonivy/market/github/service/GHAxonIvyMarketRepoService.java diff --git a/src/main/java/com/axonivy/market/github/service/GHAxonIvyProductRepoService.java b/marketplace-service/src/main/java/com/axonivy/market/github/service/GHAxonIvyProductRepoService.java similarity index 100% rename from src/main/java/com/axonivy/market/github/service/GHAxonIvyProductRepoService.java rename to marketplace-service/src/main/java/com/axonivy/market/github/service/GHAxonIvyProductRepoService.java diff --git a/src/main/java/com/axonivy/market/github/service/GitHubService.java b/marketplace-service/src/main/java/com/axonivy/market/github/service/GitHubService.java similarity index 100% rename from src/main/java/com/axonivy/market/github/service/GitHubService.java rename to marketplace-service/src/main/java/com/axonivy/market/github/service/GitHubService.java diff --git a/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java similarity index 100% rename from src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java rename to marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java diff --git a/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java similarity index 100% rename from src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java rename to marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java diff --git a/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java similarity index 100% rename from src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java rename to marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java diff --git a/src/main/java/com/axonivy/market/github/util/GitHubUtils.java b/marketplace-service/src/main/java/com/axonivy/market/github/util/GitHubUtils.java similarity index 100% rename from src/main/java/com/axonivy/market/github/util/GitHubUtils.java rename to marketplace-service/src/main/java/com/axonivy/market/github/util/GitHubUtils.java diff --git a/src/main/java/com/axonivy/market/model/DisplayValue.java b/marketplace-service/src/main/java/com/axonivy/market/model/DisplayValue.java similarity index 100% rename from src/main/java/com/axonivy/market/model/DisplayValue.java rename to marketplace-service/src/main/java/com/axonivy/market/model/DisplayValue.java diff --git a/src/main/java/com/axonivy/market/model/MavenArtifactVersionModel.java b/marketplace-service/src/main/java/com/axonivy/market/model/MavenArtifactVersionModel.java similarity index 100% rename from src/main/java/com/axonivy/market/model/MavenArtifactVersionModel.java rename to marketplace-service/src/main/java/com/axonivy/market/model/MavenArtifactVersionModel.java diff --git a/src/main/java/com/axonivy/market/model/Message.java b/marketplace-service/src/main/java/com/axonivy/market/model/Message.java similarity index 100% rename from src/main/java/com/axonivy/market/model/Message.java rename to marketplace-service/src/main/java/com/axonivy/market/model/Message.java diff --git a/src/main/java/com/axonivy/market/model/MultilingualismValue.java b/marketplace-service/src/main/java/com/axonivy/market/model/MultilingualismValue.java similarity index 100% rename from src/main/java/com/axonivy/market/model/MultilingualismValue.java rename to marketplace-service/src/main/java/com/axonivy/market/model/MultilingualismValue.java diff --git a/src/main/java/com/axonivy/market/model/ProductModel.java b/marketplace-service/src/main/java/com/axonivy/market/model/ProductModel.java similarity index 100% rename from src/main/java/com/axonivy/market/model/ProductModel.java rename to marketplace-service/src/main/java/com/axonivy/market/model/ProductModel.java diff --git a/src/main/java/com/axonivy/market/repository/GitHubRepoMetaRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/GitHubRepoMetaRepository.java similarity index 100% rename from src/main/java/com/axonivy/market/repository/GitHubRepoMetaRepository.java rename to marketplace-service/src/main/java/com/axonivy/market/repository/GitHubRepoMetaRepository.java diff --git a/src/main/java/com/axonivy/market/repository/MavenArtifactVersionRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/MavenArtifactVersionRepository.java similarity index 100% rename from src/main/java/com/axonivy/market/repository/MavenArtifactVersionRepository.java rename to marketplace-service/src/main/java/com/axonivy/market/repository/MavenArtifactVersionRepository.java diff --git a/src/main/java/com/axonivy/market/repository/ProductRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java similarity index 100% rename from src/main/java/com/axonivy/market/repository/ProductRepository.java rename to marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java diff --git a/src/main/java/com/axonivy/market/repository/UserRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/UserRepository.java similarity index 100% rename from src/main/java/com/axonivy/market/repository/UserRepository.java rename to marketplace-service/src/main/java/com/axonivy/market/repository/UserRepository.java diff --git a/src/main/java/com/axonivy/market/schedulingtask/ScheduledTasks.java b/marketplace-service/src/main/java/com/axonivy/market/schedulingtask/ScheduledTasks.java similarity index 100% rename from src/main/java/com/axonivy/market/schedulingtask/ScheduledTasks.java rename to marketplace-service/src/main/java/com/axonivy/market/schedulingtask/ScheduledTasks.java diff --git a/src/main/java/com/axonivy/market/service/ProductService.java b/marketplace-service/src/main/java/com/axonivy/market/service/ProductService.java similarity index 100% rename from src/main/java/com/axonivy/market/service/ProductService.java rename to marketplace-service/src/main/java/com/axonivy/market/service/ProductService.java diff --git a/src/main/java/com/axonivy/market/service/UserService.java b/marketplace-service/src/main/java/com/axonivy/market/service/UserService.java similarity index 100% rename from src/main/java/com/axonivy/market/service/UserService.java rename to marketplace-service/src/main/java/com/axonivy/market/service/UserService.java diff --git a/src/main/java/com/axonivy/market/service/VersionService.java b/marketplace-service/src/main/java/com/axonivy/market/service/VersionService.java similarity index 100% rename from src/main/java/com/axonivy/market/service/VersionService.java rename to marketplace-service/src/main/java/com/axonivy/market/service/VersionService.java diff --git a/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java similarity index 100% rename from src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java rename to marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java diff --git a/src/main/java/com/axonivy/market/service/impl/UserServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/UserServiceImpl.java similarity index 100% rename from src/main/java/com/axonivy/market/service/impl/UserServiceImpl.java rename to marketplace-service/src/main/java/com/axonivy/market/service/impl/UserServiceImpl.java diff --git a/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java similarity index 100% rename from src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java rename to marketplace-service/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java diff --git a/src/main/java/com/axonivy/market/utils/XmlReaderUtils.java b/marketplace-service/src/main/java/com/axonivy/market/utils/XmlReaderUtils.java similarity index 100% rename from src/main/java/com/axonivy/market/utils/XmlReaderUtils.java rename to marketplace-service/src/main/java/com/axonivy/market/utils/XmlReaderUtils.java diff --git a/src/main/resources/application.properties b/marketplace-service/src/main/resources/application.properties similarity index 100% rename from src/main/resources/application.properties rename to marketplace-service/src/main/resources/application.properties diff --git a/src/test/java/com/axonivy/market/controller/AppControllerTest.java b/marketplace-service/src/test/java/com/axonivy/market/controller/AppControllerTest.java similarity index 100% rename from src/test/java/com/axonivy/market/controller/AppControllerTest.java rename to marketplace-service/src/test/java/com/axonivy/market/controller/AppControllerTest.java diff --git a/src/test/java/com/axonivy/market/controller/ProductControllerTest.java b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductControllerTest.java similarity index 100% rename from src/test/java/com/axonivy/market/controller/ProductControllerTest.java rename to marketplace-service/src/test/java/com/axonivy/market/controller/ProductControllerTest.java diff --git a/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java similarity index 100% rename from src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java rename to marketplace-service/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java diff --git a/src/test/java/com/axonivy/market/controller/UserControllerTest.java b/marketplace-service/src/test/java/com/axonivy/market/controller/UserControllerTest.java similarity index 100% rename from src/test/java/com/axonivy/market/controller/UserControllerTest.java rename to marketplace-service/src/test/java/com/axonivy/market/controller/UserControllerTest.java diff --git a/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java b/marketplace-service/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java similarity index 100% rename from src/test/java/com/axonivy/market/factory/ProductFactoryTest.java rename to marketplace-service/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java diff --git a/src/test/java/com/axonivy/market/github/service/GHAxonIvyProductRepoServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/github/service/GHAxonIvyProductRepoServiceImplTest.java similarity index 100% rename from src/test/java/com/axonivy/market/github/service/GHAxonIvyProductRepoServiceImplTest.java rename to marketplace-service/src/test/java/com/axonivy/market/github/service/GHAxonIvyProductRepoServiceImplTest.java diff --git a/src/test/java/com/axonivy/market/handler/ExceptionHandlersTest.java b/marketplace-service/src/test/java/com/axonivy/market/handler/ExceptionHandlersTest.java similarity index 100% rename from src/test/java/com/axonivy/market/handler/ExceptionHandlersTest.java rename to marketplace-service/src/test/java/com/axonivy/market/handler/ExceptionHandlersTest.java diff --git a/src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java similarity index 100% rename from src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java rename to marketplace-service/src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java diff --git a/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java similarity index 100% rename from src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java rename to marketplace-service/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java diff --git a/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java similarity index 100% rename from src/test/java/com/axonivy/market/service/ProductServiceImplTest.java rename to marketplace-service/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java diff --git a/src/test/java/com/axonivy/market/service/SchedulingTasksTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/SchedulingTasksTest.java similarity index 100% rename from src/test/java/com/axonivy/market/service/SchedulingTasksTest.java rename to marketplace-service/src/test/java/com/axonivy/market/service/SchedulingTasksTest.java diff --git a/src/test/java/com/axonivy/market/service/UserServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/UserServiceImplTest.java similarity index 100% rename from src/test/java/com/axonivy/market/service/UserServiceImplTest.java rename to marketplace-service/src/test/java/com/axonivy/market/service/UserServiceImplTest.java diff --git a/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java similarity index 100% rename from src/test/java/com/axonivy/market/service/VersionServiceImplTest.java rename to marketplace-service/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java diff --git a/src/test/java/com/axonivy/market/utils/XmlReaderUtilsTest.java b/marketplace-service/src/test/java/com/axonivy/market/utils/XmlReaderUtilsTest.java similarity index 100% rename from src/test/java/com/axonivy/market/utils/XmlReaderUtilsTest.java rename to marketplace-service/src/test/java/com/axonivy/market/utils/XmlReaderUtilsTest.java diff --git a/src/test/resources/meta.json b/marketplace-service/src/test/resources/meta.json similarity index 100% rename from src/test/resources/meta.json rename to marketplace-service/src/test/resources/meta.json diff --git a/marketplace-ui/.editorconfig b/marketplace-ui/.editorconfig new file mode 100644 index 000000000..59d9a3a3e --- /dev/null +++ b/marketplace-ui/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/marketplace-ui/.gitignore b/marketplace-ui/.gitignore new file mode 100644 index 000000000..2e11c9dab --- /dev/null +++ b/marketplace-ui/.gitignore @@ -0,0 +1,48 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings + +# System files +.DS_Store +Thumbs.db + +# Coverage +/coverage +/reports + +**/assets/market-cache diff --git a/marketplace-ui/.prettierrc b/marketplace-ui/.prettierrc new file mode 100644 index 000000000..035febea0 --- /dev/null +++ b/marketplace-ui/.prettierrc @@ -0,0 +1,15 @@ +{ + "overrides": [ + { + "files": "*.html", + "options": { + "parser": "angular" + } + } + ], + "singleQuote": true, + "htmlWhitespaceSensitivity": "ignore", + "bracketSameLine": true, + "trailingComma": "none", + "arrowParens": "avoid" +} \ No newline at end of file diff --git a/marketplace-ui/.vscode/extensions.json b/marketplace-ui/.vscode/extensions.json new file mode 100644 index 000000000..77b374577 --- /dev/null +++ b/marketplace-ui/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 + "recommendations": ["angular.ng-template"] +} diff --git a/marketplace-ui/.vscode/launch.json b/marketplace-ui/.vscode/launch.json new file mode 100644 index 000000000..2c137b20d --- /dev/null +++ b/marketplace-ui/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Chrome Debug", + "request": "launch", + "type": "chrome", + "url": "http://localhost:4200", + "webRoot": "${workspaceFolder}" + }, + { + "name": "ng test", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: test", + "url": "http://localhost:9876/debug.html" + } + ] +} diff --git a/marketplace-ui/.vscode/tasks.json b/marketplace-ui/.vscode/tasks.json new file mode 100644 index 000000000..a298b5bd8 --- /dev/null +++ b/marketplace-ui/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "start", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + }, + { + "type": "npm", + "script": "test", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + } + ] +} diff --git a/marketplace-ui/README.md b/marketplace-ui/README.md new file mode 100644 index 000000000..9237bee55 --- /dev/null +++ b/marketplace-ui/README.md @@ -0,0 +1,27 @@ +# MarketplaceUi + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 18.0.0. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. diff --git a/marketplace-ui/angular.json b/marketplace-ui/angular.json new file mode 100644 index 000000000..7b92f41be --- /dev/null +++ b/marketplace-ui/angular.json @@ -0,0 +1,124 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "marketplace-ui": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": "dist", + "index": "src/index.html", + "browser": "src/main.ts", + "polyfills": [], + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets", + "src/assets/_market" + ], + "styles": [ + "node_modules/bootstrap/dist/css/bootstrap.min.css", + "src/styles.scss", + "node_modules/@fortawesome/fontawesome-free/css/all.min.css" + ], + "scripts": [ + "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" + ] + }, + "configurations": { + "production": { + "optimization": { + "scripts": true, + "styles": { + "minify": true, + "inlineCritical": false + }, + "fonts": true + }, + "budgets": [ + { + "type": "initial", + "maximumWarning": "1mb", + "maximumError": "5mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.development.ts" + } + ] + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "marketplace-ui:build:production" + }, + "development": { + "buildTarget": "marketplace-ui:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "buildTarget": "marketplace-ui:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "codeCoverage": true, + "polyfills": [ + "zone.js", + "zone.js/testing" + ], + "tsConfig": "tsconfig.spec.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/assets" + ], + "styles": [ + "src/styles.scss" + ], + "scripts": [], + "karmaConfig": "karma.conf.js", + "sourceMap": true, + "watch": false + } + } + } + } + }, + "cli": { + "analytics": false + } +} \ No newline at end of file diff --git a/marketplace-ui/karma.conf.js b/marketplace-ui/karma.conf.js new file mode 100644 index 000000000..4a568c025 --- /dev/null +++ b/marketplace-ui/karma.conf.js @@ -0,0 +1,58 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular', 'viewport'], + plugins: [ + require('karma-jasmine'), + require('karma-webpack'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage'), + require('@angular-devkit/build-angular/plugins/karma'), + require('karma-viewport') + ], + + client: { + jasmine: { + // you can add configuration options for Jasmine here + // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html + // for example, you can disable the random execution with `random: false` + // or set a specific seed with `seed: 4321` + stopSpecOnExpectationFailure: true, + failFast: true, + timeoutInterval: 60000, + random: false + }, + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + jasmineHtmlReporter: { + suppressAll: true // removes the duplicated traces + }, + coverageReporter: { + dir: require('path').join(__dirname, './coverage'), + subdir: '.', + reporters: [{ type: 'html' }, { type: 'text-summary' }, { type: 'lcov' }] + }, + preprocessors: { + // source files, that you wanna generate coverage for + // do not include tests or libraries + // (these files will be instrumented by Istanbul) + 'src/**/mocks/**': ['coverage'] + }, + angularCli: { + environment: 'dev' + }, + reporters: ['progress', 'coverage'], + browsers: ['ChromeHeadlessNoSandbox'], + customLaunchers: { + ChromeHeadlessNoSandbox: { + base: 'ChromeHeadless', + flags: ['--no-sandbox'] + } + }, + restartOnFileChange: true + }); +}; diff --git a/marketplace-ui/package-lock.json b/marketplace-ui/package-lock.json new file mode 100644 index 000000000..dc4bce9eb --- /dev/null +++ b/marketplace-ui/package-lock.json @@ -0,0 +1,14063 @@ +{ + "name": "marketplace-ui", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "marketplace-ui", + "version": "0.0.0", + "dependencies": { + "@angular/animations": "^18.0.0", + "@angular/common": "^18.0.0", + "@angular/compiler": "^18.0.0", + "@angular/core": "^18.0.0", + "@angular/forms": "^18.0.0", + "@angular/platform-browser": "^18.0.0", + "@angular/platform-browser-dynamic": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/router": "^18.0.0", + "@fortawesome/fontawesome-free": "^6.5.2", + "@ngx-translate/core": "^15.0.0", + "@ngx-translate/http-loader": "^8.0.0", + "bootstrap": "^5.3.3", + "bootstrap-icons": "^1.11.3", + "karma-viewport": "^1.0.9", + "rxjs": "~7.8.0", + "tslib": "^2.3.0", + "yaml": "^2.4.2", + "zone.js": "~0.14.3" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^18.0.0", + "@angular/cli": "^18.0.0", + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@types/bootstrap": "^5.2.10", + "@types/jasmine": "~5.1.0", + "@types/node": "^18.18.0", + "jasmine": "^5.1.0", + "jasmine-core": "~5.1.0", + "karma": "~6.4.0", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "karma-jsdom-launcher": "^17.0.0", + "karma-junit-reporter": "^2.0.1", + "karma-sonarqube-reporter": "^1.4.0", + "karma-webpack": "^5.0.1", + "prettier": "^3.2.5", + "typescript": "~5.4.2" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.1800.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1800.0.tgz", + "integrity": "sha512-B28h/+Og1F8/QWlizmOl3Iv3svH9uIJ456gw331RgtUMrYszU6WPlk1izG38PV++NKK9vv9NcqQsJCEvxY9ipg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "18.0.0", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-angular": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.0.0.tgz", + "integrity": "sha512-EZDn/2h24mldx8c8zbJ5BAz8YmXmPhdbFOILPixsTInJJ9/iKX+cFioyscqzRDkVuISMA8AagC+5E2ZIhCjiPQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.1800.0", + "@angular-devkit/build-webpack": "0.1800.0", + "@angular-devkit/core": "18.0.0", + "@angular/build": "18.0.0", + "@babel/core": "7.24.5", + "@babel/generator": "7.24.5", + "@babel/helper-annotate-as-pure": "7.22.5", + "@babel/helper-split-export-declaration": "7.24.5", + "@babel/plugin-transform-async-generator-functions": "7.24.3", + "@babel/plugin-transform-async-to-generator": "7.24.1", + "@babel/plugin-transform-runtime": "7.24.3", + "@babel/preset-env": "7.24.5", + "@babel/runtime": "7.24.5", + "@discoveryjs/json-ext": "0.5.7", + "@ngtools/webpack": "18.0.0", + "@vitejs/plugin-basic-ssl": "1.1.0", + "ansi-colors": "4.1.3", + "autoprefixer": "10.4.19", + "babel-loader": "9.1.3", + "babel-plugin-istanbul": "6.1.1", + "browserslist": "^4.21.5", + "copy-webpack-plugin": "11.0.0", + "critters": "0.0.22", + "css-loader": "7.1.1", + "esbuild-wasm": "0.21.3", + "fast-glob": "3.3.2", + "http-proxy-middleware": "3.0.0", + "https-proxy-agent": "7.0.4", + "inquirer": "9.2.22", + "jsonc-parser": "3.2.1", + "karma-source-map-support": "1.4.0", + "less": "4.2.0", + "less-loader": "12.2.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.2.1", + "magic-string": "0.30.10", + "mini-css-extract-plugin": "2.9.0", + "mrmime": "2.0.0", + "open": "8.4.2", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "4.0.2", + "piscina": "4.5.0", + "postcss": "8.4.38", + "postcss-loader": "8.1.1", + "resolve-url-loader": "5.0.0", + "rxjs": "7.8.1", + "sass": "1.77.2", + "sass-loader": "14.2.1", + "semver": "7.6.2", + "source-map-loader": "5.0.0", + "source-map-support": "0.5.21", + "terser": "5.31.0", + "tree-kill": "1.2.2", + "tslib": "2.6.2", + "undici": "6.18.0", + "vite": "5.2.11", + "watchpack": "2.4.1", + "webpack": "5.91.0", + "webpack-dev-middleware": "7.2.1", + "webpack-dev-server": "5.0.4", + "webpack-merge": "5.10.0", + "webpack-subresource-integrity": "5.1.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "esbuild": "0.21.3" + }, + "peerDependencies": { + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/service-worker": "^18.0.0", + "@web/test-runner": "^0.18.0", + "browser-sync": "^3.0.2", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", + "karma": "^6.3.0", + "ng-packagr": "^18.0.0", + "protractor": "^7.0.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=5.4 <5.5" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "@web/test-runner": { + "optional": true + }, + "browser-sync": { + "optional": true + }, + "jest": { + "optional": true + }, + "jest-environment-jsdom": { + "optional": true + }, + "karma": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "protractor": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/core": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", + "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.24.5", + "@babel/helpers": "^7.24.5", + "@babel/parser": "^7.24.5", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.5", + "@babel/types": "^7.24.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@angular-devkit/build-angular/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular-devkit/build-webpack": { + "version": "0.1800.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1800.0.tgz", + "integrity": "sha512-L61mW+aGK+opsokUZkj7q1/gnSyF3qz+FsAqdVyTvwBta3KKr8xzNR75fwvzZ9+qD8bum5oAOgtyw+tvPMMt3g==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1800.0", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^5.0.2" + } + }, + "node_modules/@angular-devkit/core": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.0.0.tgz", + "integrity": "sha512-mFD4QgyM1SwPjk6slJsqAXX7oTNduYbA5zgyf29/9wNUagUaz0vdonwxFlHv+D5pPmX/tRY5mqxYD68F7FiC9g==", + "dev": true, + "dependencies": { + "ajv": "8.13.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/core/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.0.0.tgz", + "integrity": "sha512-whvMDjnLd5ObyfO+HGZdPMtY8Ac+kVyVq2RigpKQmOoQOk8eMZw4iRsTOGzvaKXhFcFnTbT5O3c6Pvo42aCaAA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "18.0.0", + "jsonc-parser": "3.2.1", + "magic-string": "0.30.10", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/animations": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.0.0.tgz", + "integrity": "sha512-An/IqDBCyWZXVC23+jRKdmvJB/b4P1BVljZxGxF+CiocNd/xvVVeBYuuxzp3vhhVobyO8A9iD12itPudLOpt2Q==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/core": "18.0.0" + } + }, + "node_modules/@angular/build": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.0.0.tgz", + "integrity": "sha512-CVE/08mH7LhcHte0UN9ETZ+d7ewPPLbtdMXYnCNvbbAqfOCaPQ62agDzBE9sHOLlyn6fkFX2G4mwyKV+AQbQnw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.1800.0", + "@babel/core": "7.24.5", + "@babel/helper-annotate-as-pure": "7.22.5", + "@babel/helper-split-export-declaration": "7.24.5", + "@vitejs/plugin-basic-ssl": "1.1.0", + "ansi-colors": "4.1.3", + "browserslist": "^4.23.0", + "critters": "0.0.22", + "esbuild": "0.21.3", + "fast-glob": "3.3.2", + "https-proxy-agent": "7.0.4", + "inquirer": "9.2.22", + "lmdb": "3.0.8", + "magic-string": "0.30.10", + "mrmime": "2.0.0", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "4.0.2", + "piscina": "4.5.0", + "sass": "1.77.2", + "semver": "7.6.2", + "undici": "6.18.0", + "vite": "5.2.11", + "watchpack": "2.4.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/service-worker": "^18.0.0", + "less": "^4.2.0", + "postcss": "^8.4.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=5.4 <5.5" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "less": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular/build/node_modules/@babel/core": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", + "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.24.5", + "@babel/helpers": "^7.24.5", + "@babel/parser": "^7.24.5", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.5", + "@babel/types": "^7.24.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@angular/build/node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular/build/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@angular/build/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/cli": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.0.0.tgz", + "integrity": "sha512-SzPMju4L7Lr59k72PNmEznCSfHGtoDSmDl3lbLoumnIKlZoejnIgEipzXSjTkBk23rHAAUevlpDUUhkOIoAppg==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1800.0", + "@angular-devkit/core": "18.0.0", + "@angular-devkit/schematics": "18.0.0", + "@schematics/angular": "18.0.0", + "@yarnpkg/lockfile": "1.1.0", + "ansi-colors": "4.1.3", + "ini": "4.1.2", + "inquirer": "9.2.22", + "jsonc-parser": "3.2.1", + "npm-package-arg": "11.0.2", + "npm-pick-manifest": "9.0.1", + "ora": "5.4.1", + "pacote": "18.0.6", + "resolve": "1.22.8", + "semver": "7.6.2", + "symbol-observable": "4.0.0", + "yargs": "17.7.2" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/common": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.0.0.tgz", + "integrity": "sha512-s43ZcOhXTUlkdOPMiMtr4Pz1qKIS8nClXhaahY0JBQZYGsOSn7NR42SoEeB8/ixktfY60s3SLhizXTKMAYtOTA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/core": "18.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.0.0.tgz", + "integrity": "sha512-KbyjUfpdVE8+6fiHqo4PgVrGppYUhlU1JVAj6dqeUug9lQ5HBcANfiZ7p8CA2lU3gvIZ1cj+ZDKA1NEB1wvvtQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/core": "18.0.0" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + } + } + }, + "node_modules/@angular/compiler-cli": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.0.0.tgz", + "integrity": "sha512-fy9MBSHDM/YAyrIWa15JV1ZrpuSc51HHUSA3W/UKrDqUqSfYyj11/0PeYkdIWUD/dACZSrEge3nVnYCjdyJqPA==", + "dev": true, + "dependencies": { + "@babel/core": "7.24.4", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^3.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js", + "ngcc": "bundles/ngcc/index.js" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/compiler": "18.0.0", + "typescript": ">=5.4 <5.5" + } + }, + "node_modules/@angular/core": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.0.0.tgz", + "integrity": "sha512-tpR7HIY4MJuM9ETpG15IvBr1wsI8Cyec3ZxYFe/27FKHARvxDbqIrT9QevmC6lxg1NdfD990G2XphYML1EyJ8g==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.14.0" + } + }, + "node_modules/@angular/forms": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.0.0.tgz", + "integrity": "sha512-Q+4WExdgALP7VJ5lKSYmpz8CtAFZI4f3n09JhExIZoPTLD/mqOJcxxO7wTc9lXG4jKSE8BlfgK2txKz1cQvrEQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/common": "18.0.0", + "@angular/core": "18.0.0", + "@angular/platform-browser": "18.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/localize": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-18.0.0.tgz", + "integrity": "sha512-DW3wB5Cj0a+Ph5SppddRcXTH6igX+W5x7wK+VDsLefiAC2cHRG4DjEL2mpoVYrkDUPNQRaf+X4GTEKHtTzjvNw==", + "dev": true, + "dependencies": { + "@babel/core": "7.24.4", + "@types/babel__core": "7.20.5", + "fast-glob": "3.3.2", + "yargs": "^17.2.1" + }, + "bin": { + "localize-extract": "tools/bundles/src/extract/cli.js", + "localize-migrate": "tools/bundles/src/migrate/cli.js", + "localize-translate": "tools/bundles/src/translate/cli.js" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/compiler": "18.0.0", + "@angular/compiler-cli": "18.0.0" + } + }, + "node_modules/@angular/platform-browser": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.0.0.tgz", + "integrity": "sha512-fOqXQn15H33xGTGgNBUwXAg5KRpqcdsVfipFBuD1GMbjMLQAx/AagxsBavRiq3mKEdHZyQ+hI4mvaKQWOPKUOQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/animations": "18.0.0", + "@angular/common": "18.0.0", + "@angular/core": "18.0.0" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@angular/platform-browser-dynamic": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.0.0.tgz", + "integrity": "sha512-Z7Y2qzEuFgCrkgcKPuyHGStEnZ89L3gr3SIgqoVlz4kauf0Fa70H6dxyd/RXV61OZwLXx0yt9rV5d8v+Ay+3fQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/common": "18.0.0", + "@angular/compiler": "18.0.0", + "@angular/core": "18.0.0", + "@angular/platform-browser": "18.0.0" + } + }, + "node_modules/@angular/platform-server": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-18.0.0.tgz", + "integrity": "sha512-xn/E1zYEWnvoeSGDcMjxOmUhOIkTQ4wSmoAEr3lNt8znB/+K3PnMsV6sHPSgOkfjzXuX7PFhW2tgvp4TbMgfbA==", + "dependencies": { + "tslib": "^2.3.0", + "xhr2": "^0.2.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/animations": "18.0.0", + "@angular/common": "18.0.0", + "@angular/compiler": "18.0.0", + "@angular/core": "18.0.0", + "@angular/platform-browser": "18.0.0" + } + }, + "node_modules/@angular/router": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-18.0.0.tgz", + "integrity": "sha512-bytfTypkJbHDv2QkD8jT2w63DWKicSYi5l7N+LPukb9/0pl3XYXKJ8cjlVLbiFvoo5Oz2oBFWYFucWsaPqDw3A==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/common": "18.0.0", + "@angular/core": "18.0.0", + "@angular/platform-browser": "18.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", + "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.4", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.24.4", + "@babel/parser": "^7.24.4", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", + "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.5", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz", + "integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.24.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.24.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.24.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz", + "integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", + "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.24.3", + "@babel/helper-simple-access": "^7.24.5", + "@babel/helper-split-export-declaration": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", + "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", + "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", + "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", + "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", + "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz", + "integrity": "sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.23.0", + "@babel/template": "^7.24.0", + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", + "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.5", + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", + "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.5", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", + "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.5.tgz", + "integrity": "sha512-LdXRi1wEMTrHVR4Zc9F8OewC3vdm5h4QB6L71zy6StmYeqGi1b3ttIO8UC+BfZKcH9jdr4aI249rBkm+3+YvHw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", + "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", + "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz", + "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", + "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz", + "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", + "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz", + "integrity": "sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz", + "integrity": "sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-remap-async-to-generator": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", + "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz", + "integrity": "sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", + "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz", + "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.4", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz", + "integrity": "sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-replace-supers": "^7.24.1", + "@babel/helper-split-export-declaration": "^7.24.5", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", + "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/template": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz", + "integrity": "sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", + "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", + "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", + "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", + "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", + "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", + "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", + "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz", + "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", + "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz", + "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", + "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", + "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", + "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", + "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", + "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", + "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", + "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz", + "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz", + "integrity": "sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", + "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-replace-supers": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz", + "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz", + "integrity": "sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz", + "integrity": "sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", + "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz", + "integrity": "sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.5", + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", + "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", + "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", + "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.3.tgz", + "integrity": "sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.3", + "@babel/helper-plugin-utils": "^7.24.0", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", + "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", + "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", + "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", + "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz", + "integrity": "sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", + "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz", + "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", + "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz", + "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.5.tgz", + "integrity": "sha512-UGK2ifKtcC8i5AI4cH+sbLLuLc2ktYSFJgBAXorKAsHUZmrQ1q6aQ6i3BvU24wWs2AAKqQB6kq3N9V9Gw1HiMQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.24.4", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.24.1", + "@babel/plugin-syntax-import-attributes": "^7.24.1", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.1", + "@babel/plugin-transform-async-generator-functions": "^7.24.3", + "@babel/plugin-transform-async-to-generator": "^7.24.1", + "@babel/plugin-transform-block-scoped-functions": "^7.24.1", + "@babel/plugin-transform-block-scoping": "^7.24.5", + "@babel/plugin-transform-class-properties": "^7.24.1", + "@babel/plugin-transform-class-static-block": "^7.24.4", + "@babel/plugin-transform-classes": "^7.24.5", + "@babel/plugin-transform-computed-properties": "^7.24.1", + "@babel/plugin-transform-destructuring": "^7.24.5", + "@babel/plugin-transform-dotall-regex": "^7.24.1", + "@babel/plugin-transform-duplicate-keys": "^7.24.1", + "@babel/plugin-transform-dynamic-import": "^7.24.1", + "@babel/plugin-transform-exponentiation-operator": "^7.24.1", + "@babel/plugin-transform-export-namespace-from": "^7.24.1", + "@babel/plugin-transform-for-of": "^7.24.1", + "@babel/plugin-transform-function-name": "^7.24.1", + "@babel/plugin-transform-json-strings": "^7.24.1", + "@babel/plugin-transform-literals": "^7.24.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", + "@babel/plugin-transform-member-expression-literals": "^7.24.1", + "@babel/plugin-transform-modules-amd": "^7.24.1", + "@babel/plugin-transform-modules-commonjs": "^7.24.1", + "@babel/plugin-transform-modules-systemjs": "^7.24.1", + "@babel/plugin-transform-modules-umd": "^7.24.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.24.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", + "@babel/plugin-transform-numeric-separator": "^7.24.1", + "@babel/plugin-transform-object-rest-spread": "^7.24.5", + "@babel/plugin-transform-object-super": "^7.24.1", + "@babel/plugin-transform-optional-catch-binding": "^7.24.1", + "@babel/plugin-transform-optional-chaining": "^7.24.5", + "@babel/plugin-transform-parameters": "^7.24.5", + "@babel/plugin-transform-private-methods": "^7.24.1", + "@babel/plugin-transform-private-property-in-object": "^7.24.5", + "@babel/plugin-transform-property-literals": "^7.24.1", + "@babel/plugin-transform-regenerator": "^7.24.1", + "@babel/plugin-transform-reserved-words": "^7.24.1", + "@babel/plugin-transform-shorthand-properties": "^7.24.1", + "@babel/plugin-transform-spread": "^7.24.1", + "@babel/plugin-transform-sticky-regex": "^7.24.1", + "@babel/plugin-transform-template-literals": "^7.24.1", + "@babel/plugin-transform-typeof-symbol": "^7.24.5", + "@babel/plugin-transform-unicode-escapes": "^7.24.1", + "@babel/plugin-transform-unicode-property-regex": "^7.24.1", + "@babel/plugin-transform-unicode-regex": "^7.24.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", + "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", + "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.24.5", + "@babel/parser": "^7.24.5", + "@babel/types": "^7.24.5", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", + "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.24.1", + "@babel/helper-validator-identifier": "^7.24.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dev": true, + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.3.tgz", + "integrity": "sha512-yTgnwQpFVYfvvo4SvRFB0SwrW8YjOxEoT7wfMT7Ol5v7v5LDNvSGo67aExmxOb87nQNeWPVvaGBNfQ7BXcrZ9w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.3.tgz", + "integrity": "sha512-bviJOLMgurLJtF1/mAoJLxDZDL6oU5/ztMHnJQRejbJrSc9FFu0QoUoFhvi6qSKJEw9y5oGyvr9fuDtzJ30rNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.3.tgz", + "integrity": "sha512-c+ty9necz3zB1Y+d/N+mC6KVVkGUUOcm4ZmT5i/Fk5arOaY3i6CA3P5wo/7+XzV8cb4GrI/Zjp8NuOQ9Lfsosw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.3.tgz", + "integrity": "sha512-JReHfYCRK3FVX4Ra+y5EBH1b9e16TV2OxrPAvzMsGeES0X2Ndm9ImQRI4Ket757vhc5XBOuGperw63upesclRw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.3.tgz", + "integrity": "sha512-U3fuQ0xNiAkXOmQ6w5dKpEvXQRSpHOnbw7gEfHCRXPeTKW9sBzVck6C5Yneb8LfJm0l6le4NQfkNPnWMSlTFUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.3.tgz", + "integrity": "sha512-3m1CEB7F07s19wmaMNI2KANLcnaqryJxO1fXHUV5j1rWn+wMxdUYoPyO2TnAbfRZdi7ADRwJClmOwgT13qlP3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.3.tgz", + "integrity": "sha512-fsNAAl5pU6wmKHq91cHWQT0Fz0vtyE1JauMzKotrwqIKAswwP5cpHUCxZNSTuA/JlqtScq20/5KZ+TxQdovU/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.3.tgz", + "integrity": "sha512-tci+UJ4zP5EGF4rp8XlZIdq1q1a/1h9XuronfxTMCNBslpCtmk97Q/5qqy1Mu4zIc0yswN/yP/BLX+NTUC1bXA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.3.tgz", + "integrity": "sha512-f6kz2QpSuyHHg01cDawj0vkyMwuIvN62UAguQfnNVzbge2uWLhA7TCXOn83DT0ZvyJmBI943MItgTovUob36SQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.3.tgz", + "integrity": "sha512-vvG6R5g5ieB4eCJBQevyDMb31LMHthLpXTc2IGkFnPWS/GzIFDnaYFp558O+XybTmYrVjxnryru7QRleJvmZ6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.3.tgz", + "integrity": "sha512-HjCWhH7K96Na+66TacDLJmOI9R8iDWDDiqe17C7znGvvE4sW1ECt9ly0AJ3dJH62jHyVqW9xpxZEU1jKdt+29A==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.3.tgz", + "integrity": "sha512-BGpimEccmHBZRcAhdlRIxMp7x9PyJxUtj7apL2IuoG9VxvU/l/v1z015nFs7Si7tXUwEsvjc1rOJdZCn4QTU+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.3.tgz", + "integrity": "sha512-5rMOWkp7FQGtAH3QJddP4w3s47iT20hwftqdm7b+loe95o8JU8ro3qZbhgMRy0VuFU0DizymF1pBKkn3YHWtsw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.3.tgz", + "integrity": "sha512-h0zj1ldel89V5sjPLo5H1SyMzp4VrgN1tPkN29TmjvO1/r0MuMRwJxL8QY05SmfsZRs6TF0c/IDH3u7XYYmbAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.3.tgz", + "integrity": "sha512-dkAKcTsTJ+CRX6bnO17qDJbLoW37npd5gSNtSzjYQr0svghLJYGYB0NF1SNcU1vDcjXLYS5pO4qOW4YbFama4A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.3.tgz", + "integrity": "sha512-vnD1YUkovEdnZWEuMmy2X2JmzsHQqPpZElXx6dxENcIwTu+Cu5ERax6+Ke1QsE814Zf3c6rxCfwQdCTQ7tPuXA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.3.tgz", + "integrity": "sha512-IOXOIm9WaK7plL2gMhsWJd+l2bfrhfilv0uPTptoRoSb2p09RghhQQp9YY6ZJhk/kqmeRt6siRdMSLLwzuT0KQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.3.tgz", + "integrity": "sha512-uTgCwsvQ5+vCQnqM//EfDSuomo2LhdWhFPS8VL8xKf+PKTCrcT/2kPPoWMTs22aB63MLdGMJiE3f1PHvCDmUOw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.3.tgz", + "integrity": "sha512-vNAkR17Ub2MgEud2Wag/OE4HTSI6zlb291UYzHez/psiKarp0J8PKGDnAhMBcHFoOHMXHfExzmjMojJNbAStrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.3.tgz", + "integrity": "sha512-W8H9jlGiSBomkgmouaRoTXo49j4w4Kfbl6I1bIdO/vT0+0u4f20ko3ELzV3hPI6XV6JNBVX+8BC+ajHkvffIJA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.3.tgz", + "integrity": "sha512-EjEomwyLSCg8Ag3LDILIqYCZAq/y3diJ04PnqGRgq8/4O3VNlXyMd54j/saShaN4h5o5mivOjAzmU6C3X4v0xw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.3.tgz", + "integrity": "sha512-WGiE/GgbsEwR33++5rzjiYsKyHywE8QSZPF7Rfx9EBfK3Qn3xyR6IjyCr5Uk38Kg8fG4/2phN7sXp4NPWd3fcw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.3.tgz", + "integrity": "sha512-xRxC0jaJWDLYvcUvjQmHCJSfMrgmUuvsoXgDeU/wTorQ1ngDdUBuFtgY3W1Pc5sprGAvZBtWdJX7RPg/iZZUqA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@fortawesome/fontawesome-free": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.2.tgz", + "integrity": "sha512-hRILoInAx8GNT5IMkrtIt9blOdrqHOnPBH+k70aWUAqPZPgopb9G5EQJFpaBx/S8zp2fC+mPW349Bziuk1o28Q==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.2.tgz", + "integrity": "sha512-4F1MBwVr3c/m4bAUef6LgkvBfSjzwH+OfldgHqcuacWwSUetFebM2wi58WfG9uk1rR98U6GwLed4asLJbwdV5w==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.0.4.tgz", + "integrity": "sha512-aOcSN4MeAtFROysrbqG137b7gaDDSmVrl5mpo6sT/w+kcXpWnzhMjmY/Fh/sDx26NBxyIE7MB1seqLeCAzy9Sg==", + "dev": true, + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.1.3.tgz", + "integrity": "sha512-g//kkF4kOwUjemValCtOc/xiYzmwMRmWq3Bn+YnzOzuZLHq2PpMOxxIayN3cKbo7Ko2Np65t6D9H81IvXbXhqg==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true + }, + "node_modules/@ljharb/through": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", + "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.0.8.tgz", + "integrity": "sha512-+lFwFvU+zQ9zVIFETNtmW++syh3Ps5JS8MPQ8zOYtQZoU+dTR8ivWHTaE2QVk1JG2payGDLUAvpndLAjGMdeeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.0.8.tgz", + "integrity": "sha512-T98rfsgfdQMS5/mqdsPb6oHSJ+iBYNa+PQDLtXLh6rzTEBsYP9x2uXxIj6VS4qXVDWXVi8rv85NCOG+UBOsHXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.0.8.tgz", + "integrity": "sha512-gVNCi3bYWatdPMeFpFjuZl6bzVL55FkeZU3sPeU+NsMRXC+Zl3qOx3M6cM4OMlJWbhHjYjf2b8q83K0mczaiWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.0.8.tgz", + "integrity": "sha512-uEBGCQIChsixpykL0pjCxfF64btv64vzsb1NoM5u0qvabKvKEvErhXGoqovyldDu9u1T/fswD8Kf6ih0vJEvDQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.0.8.tgz", + "integrity": "sha512-6v0B4sa9ulNezmDZtVpLjNHmA0qZzUl3001YJ2RF0naxsuv/Jq/xEwNYpOzfcdizHfpCE0oBkWzk/r+Slr+0zw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.0.8.tgz", + "integrity": "sha512-lDLGRIMqdwYD39vinwNqqZUxCdL2m2iIdn+0HyQgIHEiT0g5rIAlzaMKzoGWon5NQumfxXFk9y0DarttkR7C1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz", + "integrity": "sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.2.tgz", + "integrity": "sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.2.tgz", + "integrity": "sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.2.tgz", + "integrity": "sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.2.tgz", + "integrity": "sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz", + "integrity": "sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@ngtools/webpack": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.0.0.tgz", + "integrity": "sha512-wcJp15H52RgEiZOcq/8YlgF53dNR2C+ap6mof8HziD5lTXmkPLKn1US0gqHixu5njkWKslCzu2td/k2Fg6r5Kg==", + "dev": true, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^18.0.0", + "typescript": ">=5.4 <5.5", + "webpack": "^5.54.0" + } + }, + "node_modules/@ngx-translate/core": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-15.0.0.tgz", + "integrity": "sha512-Am5uiuR0bOOxyoercDnAA3rJVizo4RRqJHo8N3RqJ+XfzVP/I845yEnMADykOHvM6HkVm4SZSnJBOiz0Anx5BA==", + "engines": { + "node": "^16.13.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "rxjs": "^6.5.5 || ^7.4.0" + } + }, + "node_modules/@ngx-translate/http-loader": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-8.0.0.tgz", + "integrity": "sha512-SFMsdUcmHF5OdZkL1CHEoSAwbP5EbAOPTLLboOCRRoOg21P4GJx+51jxGdJeGve6LSKLf4Pay7BkTwmE6vxYlg==", + "engines": { + "node": "^16.13.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "@ngx-translate/core": ">=15.0.0", + "rxjs": "^6.5.5 || ^7.4.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", + "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/@npmcli/fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.7.tgz", + "integrity": "sha512-WaOVvto604d5IpdCRV2KjQu8PzkfE96d50CQGKgywXh2GxXmDeUO5EWcBC4V57uFyrNqx83+MewuJh3WTR3xPA==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^4.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", + "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "dev": true, + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.1.0.tgz", + "integrity": "sha512-1aL4TuVrLS9sf8quCLerU3H9J4vtCtgu8VauYozrmEyU57i/EdKleCnsQ7vpnABIH6c9mnTxcH5sFkO3BlV8wQ==", + "dev": true, + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^4.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/glob": { + "version": "10.3.16", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.16.tgz", + "integrity": "sha512-JDKXl1DiuuHJ6fVS2FXjownaavciiHNUU4mOvV/B793RLh05vZL1rcPnCSaOgv1hDT6RDlY7AB7ZUvFYAtPgAw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.11.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/package-json/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", + "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "dev": true, + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.0.tgz", + "integrity": "sha512-SEjCPAVHWYUIQR+Yn03kJmrJjZDtJLYpj300m3HV9OTRZNpC5YpbMsM3eTkECyT4aWj8lDr9WeY6TWefpubtYQ==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", + "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "proc-log": "^4.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz", + "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz", + "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz", + "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz", + "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz", + "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz", + "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz", + "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz", + "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz", + "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz", + "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz", + "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz", + "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz", + "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz", + "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz", + "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz", + "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@schematics/angular": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.0.0.tgz", + "integrity": "sha512-cFah74mKIg+mCGur1Q1BmsQ/u+Ne/0MOwIxe2oYSlzDpktOuKAUItPFe4GHxm9Mu5qZzOX0Z4RRnSojU8XgZEw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "18.0.0", + "@angular-devkit/schematics": "18.0.0", + "jsonc-parser": "3.2.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@sigstore/bundle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", + "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", + "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.2.tgz", + "integrity": "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", + "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^13.0.1", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", + "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^2.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", + "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.1.0", + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "dev": true + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", + "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", + "dev": true, + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/bootstrap": { + "version": "5.2.10", + "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.10.tgz", + "integrity": "sha512-F2X+cd6551tep0MvVZ6nM8v7XgGN/twpdNDjqS1TUM7YFNEtQYWk+dKAnH+T1gr6QgCoGMPl487xw/9hXooa2g==", + "dev": true, + "dependencies": { + "@popperjs/core": "^2.9.2" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.1.tgz", + "integrity": "sha512-ej0phymbFLoCB26dbbq5PGScsf2JAJ4IJHjG10LalgUV36XKTmA4GdA+PVllKvRk0sEKt64X8975qFnkSi0hqA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/http-proxy": { + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/jasmine": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.4.tgz", + "integrity": "sha512-px7OMFO/ncXxixDe1zR13V1iycqWae0MxTaw62RpFlksUi5QuNWgQJFkTQjIOvrmutJbI7Fp2Y2N1F6D2R4G6w==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/karma": { + "version": "6.3.8", + "resolved": "https://registry.npmjs.org/@types/karma/-/karma-6.3.8.tgz", + "integrity": "sha512-+QGoOPhb1f6Oli8pG+hxdnGDzVhIrpsHaFSJ4UJg15Xj+QBtluKELkJY+L4Li532HmT3l5K5o1FoUZHRQeOOaQ==", + "dependencies": { + "@types/node": "*", + "log4js": "^6.4.1" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, + "node_modules/@types/mkdirp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.2.tgz", + "integrity": "sha512-o0K1tSO0Dx5X6xlU5F1D6625FawhC3dU3iqr25lluNv/+/QIVH8RLNEiVokgIZo+mz+87w/3Mkg/VvQS+J51fQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "18.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.33.tgz", + "integrity": "sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", + "integrity": "sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==", + "dev": true, + "engines": { + "node": ">=14.6.0" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/adjust-sourcemap-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "peer": true + }, + "node_modules/autoprefixer": { + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-loader": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", + "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "dev": true, + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/bonjour-service": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/bootstrap-icons": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz", + "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ] + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "18.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.3.tgz", + "integrity": "sha512-qXCd4rh6I07cnDqh8V48/94Tc/WSfj+o3Gn6NZ0aZovS255bUx8O13uKxRFd2eWG0xgsco7+YItQNPaa5E85hg==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.3.16", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.16.tgz", + "integrity": "sha512-JDKXl1DiuuHJ6fVS2FXjownaavciiHNUU4mOvV/B793RLh05vZL1rcPnCSaOgv1hDT6RDlY7AB7ZUvFYAtPgAw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.11.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "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.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001621", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001621.tgz", + "integrity": "sha512-+NLXZiviFFKX0fk8Piwv3PfLPGtRqJeq2TiNoUff/qB5KJgwecJTvCXDpmlyP/eCI/GUEmp/h/y5j0yckiiZrA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-regexp": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz", + "integrity": "sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==", + "dev": true, + "dependencies": { + "is-regexp": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dev": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dev": true, + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "peer": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/core-js-compat": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", + "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/critters": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.22.tgz", + "integrity": "sha512-NU7DEcQZM2Dy8XTKFHxtdnIM/drE312j2T4PCVaSUcS0oBeyT/NImpRw/Ap0zOr/1SE7SgPK9tGPg1WK/sVakw==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "css-select": "^5.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.2", + "htmlparser2": "^8.0.2", + "postcss": "^8.4.23", + "postcss-media-query-parser": "^0.2.3" + } + }, + "node_modules/critters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/critters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/critters/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/critters/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/critters/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/critters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.1.tgz", + "integrity": "sha512-OxIR5P2mjO1PSXk44bWuQ8XtMK4dpEqpIyERCx3ewOo3I8EmbcxMPUc5ScLtQfgXtOojoMv57So4V/C02HQLsw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", + "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==", + "dev": true, + "peer": true, + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "dev": true + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "peer": true, + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true, + "peer": true + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", + "dev": true, + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.777", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.777.tgz", + "integrity": "sha512-n02NCwLJ3wexLfK/yQeqfywCblZqLcXphzmid5e8yVPdtEcida7li0A5WQKghHNG0FeOMCzeFOzEbtAh5riXFw==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/engine.io": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", + "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "dev": true + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.3.tgz", + "integrity": "sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.3.tgz", + "integrity": "sha512-Kgq0/ZsAPzKrbOjCQcjoSmPoWhlcVnGAUo7jvaLHoxW1Drto0KGkR1xBNg2Cp43b9ImvxmPEJZ9xkfcnqPsfBw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.3", + "@esbuild/android-arm": "0.21.3", + "@esbuild/android-arm64": "0.21.3", + "@esbuild/android-x64": "0.21.3", + "@esbuild/darwin-arm64": "0.21.3", + "@esbuild/darwin-x64": "0.21.3", + "@esbuild/freebsd-arm64": "0.21.3", + "@esbuild/freebsd-x64": "0.21.3", + "@esbuild/linux-arm": "0.21.3", + "@esbuild/linux-arm64": "0.21.3", + "@esbuild/linux-ia32": "0.21.3", + "@esbuild/linux-loong64": "0.21.3", + "@esbuild/linux-mips64el": "0.21.3", + "@esbuild/linux-ppc64": "0.21.3", + "@esbuild/linux-riscv64": "0.21.3", + "@esbuild/linux-s390x": "0.21.3", + "@esbuild/linux-x64": "0.21.3", + "@esbuild/netbsd-x64": "0.21.3", + "@esbuild/openbsd-x64": "0.21.3", + "@esbuild/sunos-x64": "0.21.3", + "@esbuild/win32-arm64": "0.21.3", + "@esbuild/win32-ia32": "0.21.3", + "@esbuild/win32-x64": "0.21.3" + } + }, + "node_modules/esbuild-wasm": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.21.3.tgz", + "integrity": "sha512-DMOV+eeVra0yVq3XIojfczdEQsz+RiFnpEj7lqs8Gux9mlTpN7yIbw0a4KzLspn0Uhw6UVEH3nUAidSqc/rcQg==", + "dev": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "dev": true + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "dev": true + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "dev": true, + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "peer": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "peer": true, + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-middleware": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.0.tgz", + "integrity": "sha512-36AV1fIaI2cWRzHo+rbcxhe3M3jUDCNzc4D5zRl57sEWRAxdXYtw7FSQKYY6PDKssiAKjLYypbssHk+xs/kMXw==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.10", + "debug": "^4.3.4", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", + "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "dev": true, + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ignore-walk/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", + "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", + "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/inquirer": { + "version": "9.2.22", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.22.tgz", + "integrity": "sha512-SqLLa/Oe5rZUagTR9z+Zd6izyatHglbmbvVofo1KzuVB54YHleWzeHNLoR7FOICGOeQSqeLh1cordb3MzhGcEw==", + "dev": true, + "dependencies": { + "@inquirer/figures": "^1.0.2", + "@ljharb/through": "^2.3.13", + "ansi-escapes": "^4.3.2", + "chalk": "^5.3.0", + "cli-cursor": "^3.1.0", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "lodash": "^4.17.21", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "peer": true + }, + "node_modules/is-regexp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz", + "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz", + "integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jasmine": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-5.1.0.tgz", + "integrity": "sha512-prmJlC1dbLhti4nE4XAPDWmfJesYO15sjGXVp7Cs7Ym5I9Xtwa/hUHxxJXjnpfLO72+ySttA0Ztf8g/RiVnUKw==", + "dev": true, + "dependencies": { + "glob": "^10.2.2", + "jasmine-core": "~5.1.0" + }, + "bin": { + "jasmine": "bin/jasmine.js" + } + }, + "node_modules/jasmine-core": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz", + "integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==", + "dev": true + }, + "node_modules/jasmine/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/jasmine/node_modules/glob": { + "version": "10.3.16", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.16.tgz", + "integrity": "sha512-JDKXl1DiuuHJ6fVS2FXjownaavciiHNUU4mOvV/B793RLh05vZL1rcPnCSaOgv1hDT6RDlY7AB7ZUvFYAtPgAw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.11.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jasmine/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, + "node_modules/jsdom": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.0.0.tgz", + "integrity": "sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==", + "dev": true, + "peer": true, + "dependencies": { + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.7", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.3", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.16.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/ws": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", + "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/jsonschema": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", + "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", + "engines": { + "node": "*" + } + }, + "node_modules/karma": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.3.tgz", + "integrity": "sha512-LuucC/RE92tJ8mlCwqEoRWXP38UMAqpnq98vktmS9SznSoUPPUJQbc91dHcxcunROvfQjdORVA/YFviH+Xci9Q==", + "dev": true, + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.7.2", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/karma-chrome-launcher": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", + "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", + "dev": true, + "dependencies": { + "which": "^1.2.1" + } + }, + "node_modules/karma-coverage": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.1.tgz", + "integrity": "sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.0.5", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/karma-jasmine": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", + "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==", + "dev": true, + "dependencies": { + "jasmine-core": "^4.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "karma": "^6.0.0" + } + }, + "node_modules/karma-jasmine-html-reporter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz", + "integrity": "sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ==", + "dev": true, + "peerDependencies": { + "jasmine-core": "^4.0.0 || ^5.0.0", + "karma": "^6.0.0", + "karma-jasmine": "^5.0.0" + } + }, + "node_modules/karma-jasmine/node_modules/jasmine-core": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.0.tgz", + "integrity": "sha512-O236+gd0ZXS8YAjFx8xKaJ94/erqUliEkJTDedyE7iHvv4ZVqi+q+8acJxu05/WJDKm512EUNn809In37nWlAQ==", + "dev": true + }, + "node_modules/karma-jsdom-launcher": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/karma-jsdom-launcher/-/karma-jsdom-launcher-17.0.0.tgz", + "integrity": "sha512-imnZAd77BPrWTuk+JPtl8VgaoUv4a05j85VIn9Z7iXWqYtG+fF8YiR1ftGZIno2U/peyL9XNogiXWTz49qffOg==", + "dev": true, + "peerDependencies": { + "jsdom": ">=17 <=24", + "karma": ">=2 <=6" + } + }, + "node_modules/karma-junit-reporter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-2.0.1.tgz", + "integrity": "sha512-VtcGfE0JE4OE1wn0LK8xxDKaTP7slN8DO3I+4xg6gAi1IoAHAXOJ1V9G/y45Xg6sxdxPOR3THCFtDlAfBo9Afw==", + "dev": true, + "dependencies": { + "path-is-absolute": "^1.0.0", + "xmlbuilder": "12.0.0" + }, + "engines": { + "node": ">= 8" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-sonarqube-reporter": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-sonarqube-reporter/-/karma-sonarqube-reporter-1.4.0.tgz", + "integrity": "sha512-Dywucb6ZZMrH+aDFMgSh+b3tKQl1nc+ldVgEyRB9jfP1eraChk/ahO3eFCMPX4K2iqxerQ6l2VazabU3IXz2Sg==", + "dev": true, + "dependencies": { + "@types/glob": "^7.1.2", + "@types/karma": "*", + "@types/mkdirp": "^1.0.0", + "clone-regexp": "^2.0.0", + "glob": "^7.1.2", + "js2xmlparser": "^4.0.0", + "mkdirp": "^1.0.0", + "winston": "^3.0.0" + } + }, + "node_modules/karma-sonarqube-reporter/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/karma-source-map-support": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", + "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "dev": true, + "dependencies": { + "source-map-support": "^0.5.5" + } + }, + "node_modules/karma-viewport": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/karma-viewport/-/karma-viewport-1.0.9.tgz", + "integrity": "sha512-E1xVe66vBQtI66TGOtZMzV5nf6BW5tW4TQVUqPK+oakVLdsG/ZUG688tGK0lL1q0t7nfQD1dwLD8Z9Guu/RVdg==", + "dependencies": { + "@types/karma": "^6.3.3", + "jsonschema": "^1.4.0" + } + }, + "node_modules/karma-webpack": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.1.tgz", + "integrity": "sha512-oo38O+P3W2mSPCSUrQdySSPv1LvPpXP+f+bBimNomS5sW+1V4SuhCuW8TfJzV+rDv921w2fDSDw0xJbPe6U+kQ==", + "dev": true, + "dependencies": { + "glob": "^7.1.3", + "minimatch": "^9.0.3", + "webpack-merge": "^4.1.5" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/karma-webpack/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/karma-webpack/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/karma-webpack/node_modules/webpack-merge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", + "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/karma/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/karma/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/karma/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/karma/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/karma/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/karma/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/karma/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/karma/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "dev": true + }, + "node_modules/launch-editor": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", + "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/less": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", + "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less-loader": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.2.0.tgz", + "integrity": "sha512-MYUxjSQSBUQmowc0l5nPieOYwMzGPUaTzB6inNW/bdPEG9zOL3eAAD1Qw5ZxSPk7we5dMojHwNODYMV1hq4EVg==", + "dev": true, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/less/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/less/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/less/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/license-webpack-plugin": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", + "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "dev": true, + "dependencies": { + "webpack-sources": "^3.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/lmdb": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.0.8.tgz", + "integrity": "sha512-9rp8JT4jPhCRJUL7vRARa2N06OLSYzLwQsEkhC6Qu5XbcLyM/XBLMzDlgS/K7l7c5CdURLdDk9uE+hPFIogHTQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "msgpackr": "^1.9.9", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.1.1", + "ordered-binary": "^1.4.1", + "weak-lru-cache": "^1.2.2" + }, + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "3.0.8", + "@lmdb/lmdb-darwin-x64": "3.0.8", + "@lmdb/lmdb-linux-arm": "3.0.8", + "@lmdb/lmdb-linux-arm64": "3.0.8", + "@lmdb/lmdb-linux-x64": "3.0.8", + "@lmdb/lmdb-win32-x64": "3.0.8" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/logform": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", + "integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==", + "dev": true, + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-fetch-happen": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", + "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "dev": true, + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.9.2.tgz", + "integrity": "sha512-f16coDZlTG1jskq3mxarwB+fGRrd0uXWt+o1WIhRfOwbXQZqUDsTVxQBFK9JjRQHblg8eAG2JSbprDXKjc7ijQ==", + "dev": true, + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.1.2", + "sonic-forest": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.6.tgz", + "integrity": "sha512-Y4Ypn3oujJYxJcMacVgcs92wofTHxp9FzfDpQON4msDefoC0lb3ETvQLOdLcbhSwU1bz8HrL/1sygfBIHudrkQ==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", + "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/minipass-json-stream/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-json-stream/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/msgpackr": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.10.2.tgz", + "integrity": "sha512-L60rsPynBvNE+8BWipKKZ9jHcSGbtyJYIwjRq0VrIvQ08cRjntGXJYW/tmciZ2IHWIY8WEW32Qa2xbh5+SKBZA==", + "dev": true, + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.2.tgz", + "integrity": "sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.0.7" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.2", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.2" + } + }, + "node_modules/msgpackr-extract/node_modules/node-gyp-build-optional-packages": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz", + "integrity": "sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==", + "dev": true, + "optional": true, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/nice-napi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", + "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "!win32" + ], + "dependencies": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "node_modules/nice-napi/node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "optional": true + }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.1.0.tgz", + "integrity": "sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", + "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", + "dev": true, + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", + "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-gyp/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "10.3.16", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.16.tgz", + "integrity": "sha512-JDKXl1DiuuHJ6fVS2FXjownaavciiHNUU4mOvV/B793RLh05vZL1rcPnCSaOgv1hDT6RDlY7AB7ZUvFYAtPgAw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.11.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.1.tgz", + "integrity": "sha512-6rvCfeRW+OEZagAB4lMLSNuTNYZWLVtKccK79VSTf//yTY5VOCgcpH80O+bZK8Neps7pUnd5G+QlMg1yV/2iZQ==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", + "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.2.tgz", + "integrity": "sha512-IGN0IAwmhDJwy13Wc8k+4PEbTPhpJnMtfR53ZbOyjkvmEcLS4nCwp6mvMWjS5sUjeiW3mpx6cHmuhKEu9XmcQw==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "dev": true, + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.0.1.tgz", + "integrity": "sha512-Udm1f0l2nXb3wxDpKjfohwgdFUSV50UVwzEIpDXVsbDMXVIEF81a/i0UhuQbhrPMMmdiq3+YMFLFIRVLs3hxQw==", + "dev": true, + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.0.1.tgz", + "integrity": "sha512-fLu9MTdZTlJAHUek/VLklE6EpIiP3VZpTiuN7OOMCt2Sd67NCpSEetMaxHHEZiZxllp8ZLsUpvbEszqTFEc+wA==", + "dev": true, + "dependencies": { + "@npmcli/redact": "^2.0.0", + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz", + "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==", + "dev": true, + "peer": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dev": true, + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ora/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ordered-binary": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.1.tgz", + "integrity": "sha512-5VyHfHY3cd0iza71JepYG50My+YUbrFtGoUz2ooEydPyPM7Aai/JW098juLr+RG6+rDJuzNNTsEQu2DZa1A41A==", + "dev": true + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.0.tgz", + "integrity": "sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pacote": { + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", + "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", + "dev": true, + "dependencies": { + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/package-json": "^5.1.0", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^8.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^17.0.0", + "proc-log": "^4.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^2.2.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz", + "integrity": "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==", + "dev": true, + "dependencies": { + "entities": "^4.3.0", + "parse5": "^7.0.0", + "parse5-sax-parser": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-sax-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", + "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", + "dev": true, + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/piscina": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.5.0.tgz", + "integrity": "sha512-iBaLWI56PFP81cfBSomWTmhOo9W2/yhIOL+Tk8O1vBCpK39cM0tGxB+wgYjG31qq4ohGvysfXSdnj8h7g4rZxA==", + "dev": true, + "optionalDependencies": { + "nice-napi": "^1.0.2" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dev": true, + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-loader": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", + "dev": true, + "dependencies": { + "cosmiconfig": "^9.0.0", + "jiti": "^1.20.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", + "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true, + "peer": true + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "peer": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz", + "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==", + "dev": true + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-url-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", + "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", + "dev": true, + "dependencies": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.14", + "source-map": "0.6.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/resolve-url-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/resolve-url-loader/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz", + "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.17.2", + "@rollup/rollup-android-arm64": "4.17.2", + "@rollup/rollup-darwin-arm64": "4.17.2", + "@rollup/rollup-darwin-x64": "4.17.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.17.2", + "@rollup/rollup-linux-arm-musleabihf": "4.17.2", + "@rollup/rollup-linux-arm64-gnu": "4.17.2", + "@rollup/rollup-linux-arm64-musl": "4.17.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2", + "@rollup/rollup-linux-riscv64-gnu": "4.17.2", + "@rollup/rollup-linux-s390x-gnu": "4.17.2", + "@rollup/rollup-linux-x64-gnu": "4.17.2", + "@rollup/rollup-linux-x64-musl": "4.17.2", + "@rollup/rollup-win32-arm64-msvc": "4.17.2", + "@rollup/rollup-win32-ia32-msvc": "4.17.2", + "@rollup/rollup-win32-x64-msvc": "4.17.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", + "dev": true, + "peer": true + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/sass": { + "version": "1.77.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.2.tgz", + "integrity": "sha512-eb4GZt1C3avsX3heBNlrc7I09nyT00IUuo4eFhAbeXWU2fvA7oXI53SxODVAA+zgZCk9aunAZgO+losjR3fAwA==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-loader": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.2.1.tgz", + "integrity": "sha512-G0VcnMYU18a4N7VoNDegg2OuMjYtxnqzQWARVWCIVSZwJeiL9kg8QMsuIZOplsJgTzZLF6jGxI3AClj8I9nRdQ==", + "dev": true, + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", + "dev": true, + "optional": true + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "peer": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sigstore": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", + "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/sign": "^2.3.2", + "@sigstore/tuf": "^2.3.4", + "@sigstore/verify": "^1.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "dev": true + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", + "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", + "dev": true, + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", + "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/sonic-forest": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sonic-forest/-/sonic-forest-1.0.3.tgz", + "integrity": "sha512-dtwajos6IWMEWXdEbW1IkEkyL2gztCAgDplRIX+OT5aRKnEd5e7r7YCxRgXZdhRP1FBdOBf8axeTPhzDv8T4wQ==", + "dev": true, + "dependencies": { + "tree-dump": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "dev": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-loader/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "dev": true + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "peer": true + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/terser": { + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz", + "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "dev": true + }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "peer": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "peer": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tree-dump": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.1.tgz", + "integrity": "sha512-WCkcRBVPSlHHq1dc/px9iOfqklvzCbdRwvlNfxGZsrHqf6aZttfPrd7DJTt6oR10dwUfpFFQeVTkPbBIZxX/YA==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "dev": true, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/tuf-js": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", + "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", + "dev": true, + "dependencies": { + "@tufjs/models": "2.0.1", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-assert": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", + "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "dev": true + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.37.tgz", + "integrity": "sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/undici": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.18.0.tgz", + "integrity": "sha512-nT8jjv/fE9Et1ilR6QoW8ingRTY2Pp4l2RUrdzV5Yz35RJDrtPc1DXvuNqcpsJSGIRHFdt3YKKktTzJA6r0fTA==", + "dev": true, + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "peer": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", + "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "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 + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "peer": true, + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/watchpack": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/webpack": { + "version": "5.91.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", + "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.16.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.2.1.tgz", + "integrity": "sha512-hRLz+jPQXo999Nx9fXVdKlg/aehsw1ajA9skAneGmT03xwmyuhvF93p6HUKKbWhXdcERtGTzUCtIQr+2IQegrA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.6.0", + "mime-types": "^2.1.31", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz", + "integrity": "sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.4.0", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "rimraf": "^5.0.5", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.1.0", + "ws": "^8.16.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/glob": { + "version": "10.3.16", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.16.tgz", + "integrity": "sha512-JDKXl1DiuuHJ6fVS2FXjownaavciiHNUU4mOvV/B793RLh05vZL1rcPnCSaOgv1hDT6RDlY7AB7ZUvFYAtPgAw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.11.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dev": true, + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/rimraf": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz", + "integrity": "sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", + "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-subresource-integrity": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", + "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", + "dev": true, + "dependencies": { + "typed-assert": "^1.0.8" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "html-webpack-plugin": ">= 5.0.0-beta.1 < 6", + "webpack": "^5.12.0" + }, + "peerDependenciesMeta": { + "html-webpack-plugin": { + "optional": true + } + } + }, + "node_modules/webpack/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "peer": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dev": true, + "peer": true, + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/winston": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.0.tgz", + "integrity": "sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==", + "dev": true, + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.7.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.0.tgz", + "integrity": "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==", + "dev": true, + "dependencies": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xhr2": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.2.1.tgz", + "integrity": "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlbuilder": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-12.0.0.tgz", + "integrity": "sha512-lMo8DJ8u6JRWp0/Y4XLa/atVDr75H9litKlb2E5j3V3MesoL50EBgZDWoLT3F/LztVnG67GjPXLZpqcky/UMnQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "peer": true + }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz", + "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zone.js": { + "version": "0.14.6", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.6.tgz", + "integrity": "sha512-vyRNFqofdaHVdWAy7v3Bzmn84a1JHWSjpuTZROT/uYn8I3p2cmo7Ro9twFmYRQDPhiYOV7QLk0hhY4JJQVqS6Q==" + } + } +} diff --git a/marketplace-ui/package.json b/marketplace-ui/package.json new file mode 100644 index 000000000..f742e45fb --- /dev/null +++ b/marketplace-ui/package.json @@ -0,0 +1,55 @@ +{ + "name": "marketplace-ui", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test" + }, + "private": true, + "dependencies": { + "@angular/animations": "^18.0.0", + "@angular/common": "^18.0.0", + "@angular/compiler": "^18.0.0", + "@angular/core": "^18.0.0", + "@angular/forms": "^18.0.0", + "@angular/platform-browser": "^18.0.0", + "@angular/platform-browser-dynamic": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/router": "^18.0.0", + "@fortawesome/fontawesome-free": "^6.5.2", + "@ngx-translate/core": "^15.0.0", + "@ngx-translate/http-loader": "^8.0.0", + "bootstrap": "^5.3.3", + "bootstrap-icons": "^1.11.3", + "karma-viewport": "^1.0.9", + "rxjs": "~7.8.0", + "tslib": "^2.3.0", + "yaml": "^2.4.2", + "zone.js": "~0.14.3" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^18.0.0", + "@angular/cli": "^18.0.0", + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@types/bootstrap": "^5.2.10", + "@types/jasmine": "~5.1.0", + "@types/node": "^18.18.0", + "jasmine": "^5.1.0", + "jasmine-core": "~5.1.0", + "karma": "~6.4.0", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "karma-jsdom-launcher": "^17.0.0", + "karma-junit-reporter": "^2.0.1", + "karma-sonarqube-reporter": "^1.4.0", + "karma-webpack": "^5.0.1", + "prettier": "^3.2.5", + "typescript": "~5.4.2" + } +} diff --git a/marketplace-ui/sonar-project.properties b/marketplace-ui/sonar-project.properties new file mode 100644 index 000000000..e2453c704 --- /dev/null +++ b/marketplace-ui/sonar-project.properties @@ -0,0 +1,5 @@ +sonar.sources=src +sonar.tests=src +sonar.exclusions=**/node_modules/**, src/assets/**, **/*.html, **/*.scss, src/app/shared/mocks/**, **/*.constant.ts, **/*.enum.ts, **/*.routes.ts, **/*.model.ts, **/*.config.ts, src/environments/** +sonar.test.inclusions=**/*.spec.ts +sonar.typescript.lcov.reportPaths=coverage/lcov.info \ No newline at end of file diff --git a/marketplace-ui/src/app/app.component.html b/marketplace-ui/src/app/app.component.html new file mode 100644 index 000000000..347c01580 --- /dev/null +++ b/marketplace-ui/src/app/app.component.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + +@if (loadingService.isLoading()) { + + + +} diff --git a/marketplace-ui/src/app/app.component.scss b/marketplace-ui/src/app/app.component.scss new file mode 100644 index 000000000..bc547d095 --- /dev/null +++ b/marketplace-ui/src/app/app.component.scss @@ -0,0 +1,44 @@ +html, +body { + margin: 0; + padding: 0; + font-family: 'Roboto', 'Helvetica Neue', Arial, sans-serif; +} + +header { + border-bottom: 0.5px solid var(--header-border-color); +} + +footer { + height: 409px; + padding: 0 0 56px 0; + border-top: 1px solid var(--ivy-secondary-border-color); + margin-top: 64.14px; +} + +@media all and (max-width: 992px) { + .w-md-100 { + width: -webkit-fill-available; + } + .m-t-5 { + margin-top: 5%; + } +} + +.spinner-container { + position: fixed; + height: 100%; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + top: 0; + left: 0; + background: rgba(0, 0, 0, 0.32); + z-index: 2000; +} + +.spinner-border { + width: 4rem; + height: 4rem; +} diff --git a/marketplace-ui/src/app/app.component.spec.ts b/marketplace-ui/src/app/app.component.spec.ts new file mode 100644 index 000000000..f1a06df2b --- /dev/null +++ b/marketplace-ui/src/app/app.component.spec.ts @@ -0,0 +1,30 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { AppComponent } from './app.component'; +import { By } from '@angular/platform-browser'; + +describe('AppComponent', () => { + let component: AppComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AppComponent, TranslateModule.forRoot()], + providers: [TranslateService] + }).compileComponents(); + fixture = TestBed.createComponent(AppComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create the app', () => { + expect(component).toBeTruthy(); + }); + + it('default active nav should be Market', () => { + const activeNav = fixture.debugElement.query( + By.css('a.nav-link.text-primary.fw-bold.active') + ).nativeElement; + expect(activeNav.innerHTML).toContain('common.nav.market'); + }); +}); diff --git a/marketplace-ui/src/app/app.component.ts b/marketplace-ui/src/app/app.component.ts new file mode 100644 index 000000000..6806884de --- /dev/null +++ b/marketplace-ui/src/app/app.component.ts @@ -0,0 +1,16 @@ +import { Component, inject } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; +import { FooterComponent } from './shared/components/footer/footer.component'; +import { HeaderComponent } from './shared/components/header/header.component'; +import { LoadingService } from './core/services/loading/loading.service'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [RouterOutlet, HeaderComponent, FooterComponent], + templateUrl: './app.component.html', + styleUrl: './app.component.scss' +}) +export class AppComponent { + loadingService = inject(LoadingService); +} diff --git a/marketplace-ui/src/app/app.config.ts b/marketplace-ui/src/app/app.config.ts new file mode 100644 index 000000000..14db020f5 --- /dev/null +++ b/marketplace-ui/src/app/app.config.ts @@ -0,0 +1,33 @@ +import { + HttpClient, + provideHttpClient, + withFetch, + withInterceptors +} from '@angular/common/http'; +import { + ApplicationConfig, + importProvidersFrom, + provideExperimentalZonelessChangeDetection +} from '@angular/core'; +import { provideRouter } from '@angular/router'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { routes } from './app.routes'; +import { httpLoaderFactory } from './core/configs/translate.config'; +import { apiInterceptor } from './core/interceptors/api.interceptor'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideExperimentalZonelessChangeDetection(), + provideRouter(routes), + provideHttpClient(withFetch(), withInterceptors([apiInterceptor])), + importProvidersFrom( + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: httpLoaderFactory, + deps: [HttpClient] + } + }) + ) + ] +}; diff --git a/marketplace-ui/src/app/app.routes.ts b/marketplace-ui/src/app/app.routes.ts new file mode 100644 index 000000000..4b9644f26 --- /dev/null +++ b/marketplace-ui/src/app/app.routes.ts @@ -0,0 +1,14 @@ +import { Routes } from '@angular/router'; + +export const routes: Routes = [ + { + path: '', + loadChildren: () => + import('./modules/home/home.routes').then((m) => m.routes), + }, + { + path: ':id', + loadChildren: () => + import('./modules/product/product.routes').then((m) => m.routes), + }, +]; diff --git a/marketplace-ui/src/app/core/configs/translate.config.ts b/marketplace-ui/src/app/core/configs/translate.config.ts new file mode 100644 index 000000000..9c6ad62c0 --- /dev/null +++ b/marketplace-ui/src/app/core/configs/translate.config.ts @@ -0,0 +1,21 @@ +import { HttpClient } from '@angular/common/http'; +import { TranslateLoader } from '@ngx-translate/core'; +import { Observable, map } from 'rxjs'; +import { parse } from 'yaml'; + +class TranslateYamlHttpLoader implements TranslateLoader { + constructor( + private readonly http: HttpClient, + public path = '/assets/i18n/' + ) {} + + public getTranslation(lang: string): Observable { + return this.http + .get(`${this.path}${lang}.yaml`, { responseType: 'text' }) + .pipe(map(data => parse(data))); + } +} + +export function httpLoaderFactory(httpClient: HttpClient) { + return new TranslateYamlHttpLoader(httpClient); +} diff --git a/marketplace-ui/src/app/core/interceptors/api.interceptor.ts b/marketplace-ui/src/app/core/interceptors/api.interceptor.ts new file mode 100644 index 000000000..936e55e82 --- /dev/null +++ b/marketplace-ui/src/app/core/interceptors/api.interceptor.ts @@ -0,0 +1,46 @@ +import { HttpHeaders, HttpContextToken, HttpInterceptorFn } from '@angular/common/http'; +import { environment } from '../../../environments/environment'; +import { LoadingService } from '../services/loading/loading.service'; +import { inject } from '@angular/core'; +import { finalize } from 'rxjs'; + +export const REQUEST_BY = "X-Requested-By"; +export const IVY = "ivy"; + +/** This is option for exclude loading api + * @Example return httpClient.get('apiEndPoint', { context: new HttpContext().set(SkipLoading, true) }) + */ +export const SkipLoading = new HttpContextToken(() => false); + +export const apiInterceptor: HttpInterceptorFn = (req, next) => { + const loadingService = inject(LoadingService); + + if (req.url.includes('i18n')) { + return next(req); + } + let requestURL = req.url; + const apiURL = environment.apiUrl; + if (!requestURL.startsWith(apiURL)) { + requestURL = `${apiURL}/${req.url}`; + } + const cloneReq = req.clone({ url: requestURL, headers: addIvyHeaders(req.headers) }); + + if (req.context.get(SkipLoading)) { + return next(cloneReq); + } + + loadingService.show(); + + return next(cloneReq).pipe( + finalize(() => { + loadingService.hide(); + }) + ); +}; + +function addIvyHeaders(headers: HttpHeaders): HttpHeaders { + if (headers.has(REQUEST_BY)) { + return headers; + } + return headers.append(REQUEST_BY, IVY); +} diff --git a/marketplace-ui/src/app/core/services/language/language.service.spec.ts b/marketplace-ui/src/app/core/services/language/language.service.spec.ts new file mode 100644 index 000000000..14a2b853e --- /dev/null +++ b/marketplace-ui/src/app/core/services/language/language.service.spec.ts @@ -0,0 +1,30 @@ +import { TestBed } from '@angular/core/testing'; +import { LanguageService } from './language.service'; +import { Language } from '../../../shared/enums/language.enum'; + +describe('LanguageService', () => { + let service: LanguageService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [], + providers: [LanguageService], + }); + service = TestBed.inject(LanguageService); + }); + + it('should be created', () => { + document.defaultView?.localStorage.clear(); + expect(service).toBeTruthy(); + }); + + it('should get default language en', () => { + document.defaultView?.localStorage.clear(); + expect(service.getSelectedLanguage()).toEqual(Language.EN); + }); + + it('should change to language de-DE', ()=> { + service.loadLanguage("de"); + expect(service.getSelectedLanguage()).toEqual(Language.DE); + }); +}); diff --git a/marketplace-ui/src/app/core/services/language/language.service.ts b/marketplace-ui/src/app/core/services/language/language.service.ts new file mode 100644 index 000000000..85a2732a3 --- /dev/null +++ b/marketplace-ui/src/app/core/services/language/language.service.ts @@ -0,0 +1,28 @@ +import { DOCUMENT } from '@angular/common'; +import { Inject, Injectable } from '@angular/core'; +import { Language } from '../../../shared/enums/language.enum'; + +const DATA_LANGUAGE = 'data-language'; + +@Injectable({ providedIn: 'root' }) +export class LanguageService { + constructor(@Inject(DOCUMENT) private readonly document: Document) { + const localStorage = this.document.defaultView?.localStorage; + if (localStorage) { + this.loadDefaultLanguage(localStorage); + } + } + + loadDefaultLanguage(localStorage: Storage) { + const language = localStorage.getItem(DATA_LANGUAGE); + this.loadLanguage(language ?? Language.EN); + } + + loadLanguage(language: string): void { + localStorage.setItem(DATA_LANGUAGE, language); + } + + getSelectedLanguage(): Language { + return localStorage.getItem(DATA_LANGUAGE) as Language ?? Language.EN; + } +} diff --git a/marketplace-ui/src/app/core/services/loading/loading.service.spec.ts b/marketplace-ui/src/app/core/services/loading/loading.service.spec.ts new file mode 100644 index 000000000..d6640c9e5 --- /dev/null +++ b/marketplace-ui/src/app/core/services/loading/loading.service.spec.ts @@ -0,0 +1,26 @@ +import { TestBed } from '@angular/core/testing'; + +import { LoadingService } from './loading.service'; + +describe('LoadingService', () => { + let service: LoadingService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(LoadingService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('show should update isLoading to true', () => { + service.show(); + expect(service.isLoading()).toBeTrue(); + }) + + it('hide should update isLoading to false', () => { + service.hide(); + expect(service.isLoading()).toBeFalse(); + }) +}); diff --git a/marketplace-ui/src/app/core/services/loading/loading.service.ts b/marketplace-ui/src/app/core/services/loading/loading.service.ts new file mode 100644 index 000000000..0536aa06c --- /dev/null +++ b/marketplace-ui/src/app/core/services/loading/loading.service.ts @@ -0,0 +1,17 @@ +import { Injectable, computed, signal } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class LoadingService { + private readonly isShow = signal(false); + isLoading = computed(() => this.isShow()); + + show() { + this.isShow.set(true); + } + + hide() { + this.isShow.set(false); + } +} diff --git a/marketplace-ui/src/app/core/services/theme/theme.service.spec.ts b/marketplace-ui/src/app/core/services/theme/theme.service.spec.ts new file mode 100644 index 000000000..d6d7a87f1 --- /dev/null +++ b/marketplace-ui/src/app/core/services/theme/theme.service.spec.ts @@ -0,0 +1,42 @@ +import { TestBed } from '@angular/core/testing'; +import { Theme } from '../../../shared/enums/theme.enum'; +import { ThemeService } from './theme.service'; + +describe('ThemeService', () => { + let service: ThemeService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [], + providers: [ThemeService], + }); + service = TestBed.inject(ThemeService); + }); + + it('should be created', () => { + document.defaultView?.localStorage.clear(); + expect(service).toBeTruthy(); + }); + + it('setTheme light', () => { + service.setTheme(Theme.LIGHT); + expect(service.theme()).toEqual(Theme.LIGHT); + }); + + it('setTheme dark', () => { + service.setTheme(Theme.DARK); + expect(service.theme()).toEqual(Theme.DARK); + }); + + it('changeTheme to light', () => { + service.setTheme(Theme.DARK); + service.changeTheme(); + expect(service.theme()).toEqual(Theme.LIGHT); + }); + + it('changeTheme to dark', () => { + service.setTheme(Theme.LIGHT); + service.changeTheme(); + expect(service.theme()).toEqual(Theme.DARK); + }); +}); diff --git a/marketplace-ui/src/app/core/services/theme/theme.service.ts b/marketplace-ui/src/app/core/services/theme/theme.service.ts new file mode 100644 index 000000000..acc332e75 --- /dev/null +++ b/marketplace-ui/src/app/core/services/theme/theme.service.ts @@ -0,0 +1,46 @@ +import { DOCUMENT } from '@angular/common'; +import { Inject, Injectable, WritableSignal, signal } from '@angular/core'; +import { Theme } from '../../../shared/enums/theme.enum'; + +const DATA_THEME = 'data-bs-theme'; + +@Injectable({ providedIn: 'root' }) +export class ThemeService { + isDarkMode: WritableSignal = signal(false); + theme: WritableSignal = signal(Theme.DARK); + + constructor(@Inject(DOCUMENT) private readonly document: Document) { + const localStorage = this.document.defaultView?.localStorage; + if (localStorage) { + this.loadDefaultTheme(localStorage); + } + } + + loadDefaultTheme(localStorage: Storage) { + const theme = localStorage.getItem(DATA_THEME) as Theme; + if (theme) { + this.setTheme(theme); + } else { + this.setTheme(Theme.LIGHT); + } + } + + setTheme(theme: Theme) { + this.theme.set(theme); + localStorage.setItem(DATA_THEME, theme); + const html = this.document.querySelector('html'); + if (html) { + html.setAttribute(DATA_THEME, theme); + } + this.isDarkMode.set(this.theme() === Theme.DARK); + } + + changeTheme() { + if (this.theme() === Theme.DARK) { + this.theme.set(Theme.LIGHT); + } else { + this.theme.set(Theme.DARK); + } + this.setTheme(this.theme()); + } +} diff --git a/marketplace-ui/src/app/modules/home/home.component.html b/marketplace-ui/src/app/modules/home/home.component.html new file mode 100644 index 000000000..906929081 --- /dev/null +++ b/marketplace-ui/src/app/modules/home/home.component.html @@ -0,0 +1,3 @@ + + + diff --git a/marketplace-ui/src/app/modules/home/home.component.scss b/marketplace-ui/src/app/modules/home/home.component.scss new file mode 100644 index 000000000..36e5ce5a9 --- /dev/null +++ b/marketplace-ui/src/app/modules/home/home.component.scss @@ -0,0 +1,7 @@ +.home { + display: flex; + justify-content: center; + align-items: center; + width: 500px; + height: 500px; +} \ No newline at end of file diff --git a/marketplace-ui/src/app/modules/home/home.component.spec.ts b/marketplace-ui/src/app/modules/home/home.component.spec.ts new file mode 100644 index 000000000..453fa72b3 --- /dev/null +++ b/marketplace-ui/src/app/modules/home/home.component.spec.ts @@ -0,0 +1,33 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HomeComponent } from './home.component'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; +import { + provideHttpClient, + withInterceptorsFromDi +} from '@angular/common/http'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; + +describe('HomeComponent', () => { + let component: HomeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [HomeComponent, TranslateModule.forRoot()], + providers: [ + provideHttpClient(withInterceptorsFromDi()), + provideHttpClientTesting(), + TranslateService + ] + }).compileComponents(); + + fixture = TestBed.createComponent(HomeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/marketplace-ui/src/app/modules/home/home.component.ts b/marketplace-ui/src/app/modules/home/home.component.ts new file mode 100644 index 000000000..915e2b378 --- /dev/null +++ b/marketplace-ui/src/app/modules/home/home.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; +import { ProductComponent } from '../product/product.component'; + +@Component({ + selector: 'app-home', + standalone: true, + imports: [ProductComponent], + templateUrl: './home.component.html', + styleUrl: './home.component.scss', +}) +export class HomeComponent {} diff --git a/marketplace-ui/src/app/modules/home/home.routes.ts b/marketplace-ui/src/app/modules/home/home.routes.ts new file mode 100644 index 000000000..231b8bef1 --- /dev/null +++ b/marketplace-ui/src/app/modules/home/home.routes.ts @@ -0,0 +1,9 @@ +import { Route } from '@angular/router'; + +export const routes: Route[] = [ + { + path: '', + loadComponent: () => + import('./home.component').then((m) => m.HomeComponent), + }, +]; diff --git a/marketplace-ui/src/app/modules/product/product-card/product-card.component.html b/marketplace-ui/src/app/modules/product/product-card/product-card.component.html new file mode 100644 index 000000000..dc91c8667 --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product-card/product-card.component.html @@ -0,0 +1,28 @@ + + + + + {{ 'common.filter.value.' + product.type | translate }} + + + + + {{ + product.names | multilingualism: languageService.getSelectedLanguage() + }} + + + {{ + product.shortDescriptions + | multilingualism: languageService.getSelectedLanguage() + }} + + + diff --git a/marketplace-ui/src/app/modules/product/product-card/product-card.component.scss b/marketplace-ui/src/app/modules/product/product-card/product-card.component.scss new file mode 100644 index 000000000..46683984f --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product-card/product-card.component.scss @@ -0,0 +1,26 @@ +img { + width: 70px; + height: 70px; +} + +.card { + background-color: var(--ivy-secondary-bg); + border: 1px solid var(--ivy-border-color); +} + +.card__title { + font-weight: 600; + font-size: 20px; +} + +.card__tag { + border-radius: 5px; + background-color: var(--ivy-primary-bg); + font-size: 14px; +} + +.card__description { + font-weight: 400; + font-size: 16px; + color: var(--ivy-secondary-text-dark-color); +} diff --git a/marketplace-ui/src/app/modules/product/product-card/product-card.component.spec.ts b/marketplace-ui/src/app/modules/product/product-card/product-card.component.spec.ts new file mode 100644 index 000000000..79fe77af4 --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product-card/product-card.component.spec.ts @@ -0,0 +1,54 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { + MOCK_EMPTY_DE_VALUES_AND_NO_LOGO_URL_PRODUCTS, + MOCK_PRODUCTS +} from '../../../shared/mocks/mock-data'; +import { ProductCardComponent } from './product-card.component'; +import { Product } from '../../../shared/models/product.model'; +import { Language } from '../../../shared/enums/language.enum'; + +const products = MOCK_PRODUCTS._embedded.products as Product[]; +const noDeNameAndNoLogoUrlProducts = + MOCK_EMPTY_DE_VALUES_AND_NO_LOGO_URL_PRODUCTS._embedded.products as Product[]; + +describe('ProductCardComponent', () => { + let component: ProductCardComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProductCardComponent, TranslateModule.forRoot()], + providers: [TranslateService] + }).compileComponents(); + + fixture = TestBed.createComponent(ProductCardComponent); + component = fixture.componentInstance; + component.product = products[0]; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should load default value when german value is empty', () => { + component.product = noDeNameAndNoLogoUrlProducts[0]; + component.languageService.loadLanguage(Language.DE); + fixture.detectChanges(); + expect( + document + .getElementsByClassName('card__title') + .item(0) + ?.textContent?.trim() + ).toEqual('Amazon Comprehend'); + expect( + document + .getElementsByClassName('card__description') + .item(0) + ?.textContent?.trim() + ).toEqual( + 'Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data.' + ); + }); +}); diff --git a/marketplace-ui/src/app/modules/product/product-card/product-card.component.ts b/marketplace-ui/src/app/modules/product/product-card/product-card.component.ts new file mode 100644 index 000000000..307436298 --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product-card/product-card.component.ts @@ -0,0 +1,22 @@ +import { CommonModule, NgOptimizedImage } from '@angular/common'; +import { Component, Input, inject } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { LanguageService } from '../../../core/services/language/language.service'; +import { ThemeService } from '../../../core/services/theme/theme.service'; +import { Product } from '../../../shared/models/product.model'; +import { ProductLogoPipe } from '../../../shared/pipes/logo.pipe'; +import { MultilingualismPipe } from '../../../shared/pipes/multilingualism.pipe'; + +@Component({ + selector: 'app-product-card', + standalone: true, + imports: [CommonModule, ProductLogoPipe, MultilingualismPipe, TranslateModule, NgOptimizedImage], + templateUrl: './product-card.component.html', + styleUrl: './product-card.component.scss' +}) +export class ProductCardComponent { + themeService = inject(ThemeService); + languageService = inject(LanguageService); + + @Input() product!: Product; +} diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.html b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.html new file mode 100644 index 000000000..dfbb4262d --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.html @@ -0,0 +1,85 @@ + + + {{ 'common.product.detail.install.buttonLabel' | translate }} + + + {{ 'common.product.detail.download.buttonLabel' | translate }} + + @if (isDropDownDisplayed()) { + + + + + + {{ + 'common.product.detail.download.artifactSelector.label' + | translate + }} + + + @for (item of artifacts(); track $index) { + {{ item.name }} + } + + + + + {{ + 'common.product.detail.download.versionSelector.label' | translate + }} + + + @for (item of versions(); track $index) { + {{ item }} + } + + + + @if (isDevVersionsDisplayed()) { + {{ 'common.product.detail.download.hideDevVersions' | translate }} + } @else { + {{ 'common.product.detail.download.showDevVersions' | translate }} + } + + + + {{ 'common.product.detail.download.buttonLabel' | translate }} + + + + + } + diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.scss b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.scss new file mode 100644 index 000000000..e5d1aa35a --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.scss @@ -0,0 +1,178 @@ +::ng-deep .custom-tooltip { + --bs-tooltip-bg: var(--bs-body-bg); + --bs-tooltip-color: black; + --bs-tooltip-font-size: 12px; + --bs-tooltip-opacity: inherit !important; + background-color: var(--bs-body-bg); + min-width: 371px; + padding: 10px; + gap: 0px; + border-radius: 5px; + justify-content: space-between; + box-shadow: 0px 4px 30px 0px #0000001a; + border: 0.5px solid var(--ivy-secondary-border-color); + top: 0.8rem !important; + + .tooltip-arrow { + top: -0.8rem !important; + width: 0; + height: 0; + border-left: 13px solid transparent; + border-right: 13px solid transparent; + border-bottom: 13px solid var(--ivy-secondary-border-color); + &::before { + width: 0; + height: 0; + border-left: 12px solid transparent !important; + border-right: 12px solid transparent !important; + border-bottom: 12px solid var(--bs-body-bg) !important; + top: 0.15rem !important; + left: -0.75rem; + } + } + + .tooltip-inner { + padding: 0px; + min-width: 351px; + height: 30px; + gap: 0px; + font-weight: 400; + line-height: 15px; + letter-spacing: 0.01em; + text-align: left; + .ivy__link { + color: var(--ivy-link-corlor); + } + } +} + +.fs-md { + font-size: 12px; +} + +.fs-small { + font-size: 10px; +} + +.product-detail__versions-action { + .form-label { + font-weight: 400; + line-height: 14.52px; + text-align: left; + } + .border__dropdown { + border: 0.5px solid var(--ivy-secondary-border-color); + } + + .btn { + padding: 12px 32px; + gap: 10px; + font-weight: 500; + size: 16px; + line-height: 120%; + border-radius: 10px; + border: none; + } + + .btn__install { + margin-right: 10px; + border: 0px; + } + + .btn__download { + border: 0.5px solid #ebebeb; + } + + .primary-color { + color: var(--ivy-primary-bg); + } + + .dropdown-menu { + padding: 10px; + border-radius: 5px; + gap: 15px; + box-shadow: 0px 4px 30px 0px rgba(0, 0, 0, 0.1); + + &.maven-artifact-version__action { + left: 2rem; + min-width: 400px; + min-height: 204px; + top: 1rem; + @media (max-width: 500px) { + left: auto; + right: auto; + min-width: 80vw; + } + + .form-group { + gap: 7px; + + .form-select { + height: 32px; + padding: 9px 10px; + gap: 10px; + border-radius: 5px; + line-height: 12px; + text-align: start !important; + background-color: var(--ivy-text-normal-color); + option { + height: 44px; + padding: 15px; + gap: 10px; + font-size: 14px; + font-weight: 400; + line-height: 16.8px; + } + } + + .artifacts-selector__dropdown { + --bs-form-select-bg-img: none !important; + } + + .indicator-arrow__up { + --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 11 8 5 14 11'/%3e%3c/svg%3e") !important; + } + } + } + + .dev-versions__toggle { + font-size: 10px; + } + + .form-download__btn { + padding: 5px 16px; + gap: 6px; + border-radius: 5px; + font-size: 14px; + font-weight: 400; + line-height: 16.8px; + } + } + + .up-arrow { + transform: rotate(45deg); + top: 2.45rem; + z-index: 1001; + width: 20px; + height: 20px; + border-right-color: transparent; + border-bottom-color: transparent; + background-color: var(--bs-body-bg); + + &.up-arrow__action { + left: 11rem; + + @media (max-width: 992px) { + left: 11.5rem; + } + + @media (max-width: 767px) { + left: 18.5rem; + } + + @media (max-width: 575px) { + left: 54vw; + } + } + } +} diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts new file mode 100644 index 000000000..a926632a5 --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts @@ -0,0 +1,174 @@ +import { + ComponentFixture, + TestBed, + fakeAsync, + tick +} from '@angular/core/testing'; +import { of } from 'rxjs'; +import { ProductDetailVersionActionComponent } from './product-detail-version-action.component'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { ProductService } from '../../product.service'; +import { provideHttpClient } from '@angular/common/http'; +import { Artifact } from '../../../../shared/models/vesion-artifact.model'; +describe('ProductVersionActionComponent', () => { + let component: ProductDetailVersionActionComponent; + let fixture: ComponentFixture; + let productServiceMock: any; + + beforeEach(() => { + productServiceMock = jasmine.createSpyObj('ProductService', [ + 'sendRequestToProductDetailVersionAPI' + ]); + + TestBed.configureTestingModule({ + imports: [ProductDetailVersionActionComponent, TranslateModule.forRoot()], + providers: [ + TranslateService, + provideHttpClient(), + { provide: ProductService, useValue: productServiceMock } + ] + }).compileComponents(); + + fixture = TestBed.createComponent(ProductDetailVersionActionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('first artifact should be chosen when select corresponding version', () => { + component.onSelectVersion(); + expect(component.artifacts().length).toBe(0); + + const selectedVersion = 'Version 10.0.2'; + const artifact = { + name: 'Example Artifact', + downloadUrl: 'https://example.com/download', + isProductArtifact: true + } as Artifact; + component.versions.set([selectedVersion]); + component.versionMap.set(selectedVersion, [artifact]); + component.selectedVersion = selectedVersion; + component.onSelectVersion(); + + expect(component.artifacts().length).toBe(1); + expect(component.selectedArtifact).toEqual('https://example.com/download'); + }); + + it('all of state should be reset before call rest api', () => { + const selectedVersion = 'Version 10.0.2'; + const artifact = { + name: 'Example Artifact', + downloadUrl: 'https://example.com/download', + isProductArtifact: true + } as Artifact; + component.selectedVersion = selectedVersion; + component.selectedArtifact = artifact.downloadUrl; + component.versions().push(selectedVersion); + component.artifacts().push(artifact); + + expect(component.versions().length).toBe(1); + expect(component.artifacts().length).toBe(1); + expect(component.selectedVersion).toBe(selectedVersion); + expect(component.selectedArtifact).toBe('https://example.com/download'); + component.sanitizeDataBeforFetching(); + expect(component.versions().length).toBe(0); + expect(component.artifacts().length).toBe(0); + expect(component.selectedVersion).toEqual(''); + expect(component.selectedArtifact).toEqual(''); + }); + + it('should call sendRequestToProductDetailVersionAPI and update versions and versionMap', () => { + const { mockArtifct1, mockArtifct2 } = mockApiWithExpectedResponse(); + + component.getVersionWithArtifact(); + + expect( + productServiceMock.sendRequestToProductDetailVersionAPI + ).toHaveBeenCalledWith( + component.productId, + component.isDevVersionsDisplayed(), + component.designerVersion + ); + + expect(component.versions()).toEqual(['Version 1.0', 'Version 2.0']); + expect(component.versionMap.get('Version 1.0')).toEqual([mockArtifct1]); + expect(component.versionMap.get('Version 2.0')).toEqual([mockArtifct2]); + expect(component.selectedVersion).toBe('Version 1.0'); + }); + + it('should open the artifact download URL in a new window', () => { + spyOn(window, 'open'); + component.selectedArtifact = 'https://example.com/download'; + component.downloadArifact(); + expect(window.open).toHaveBeenCalledWith( + 'https://example.com/download', + '_blank' + ); + }); + it('should call getVersionWithArtifact and toggle isDropDownDisplayed', () => { + expect(component.isDropDownDisplayed()).toBeFalse(); + + mockApiWithExpectedResponse(); + component.onShowVersionAndArtifact(); + expect(component.isDropDownDisplayed()).toBeTrue(); + }); + + it('should send Api to get DevVersion', () => { + expect(component.isDevVersionsDisplayed()).toBeFalse(); + mockApiWithExpectedResponse(); + const event = new Event('click'); + spyOn(event, 'preventDefault'); + component.onShowDevVersion(event); + expect(event.preventDefault).toHaveBeenCalled(); + expect(component.isDevVersionsDisplayed()).toBeTrue(); + }); + + function mockApiWithExpectedResponse() { + const mockArtifct1 = { + name: 'Example Artifact1', + downloadUrl: 'https://example.com/download', + isProductArtifact: true + } as Artifact; + const mockArtifct2 = { + name: 'Example Artifact2', + downloadUrl: 'https://example.com/download', + isProductArtifact: true + } as Artifact; + const mockData = [ + { + version: '1.0', + artifactsByVersion: [mockArtifct1] + }, + { + version: '2.0', + artifactsByVersion: [mockArtifct2] + } + ]; + + productServiceMock.sendRequestToProductDetailVersionAPI.and.returnValue( + of(mockData) + ); + return { mockArtifct1, mockArtifct2 }; + } + + it('should return correct class based on isVersionsDropDownShow', () => { + component.isVersionsDropDownShow.set(true); + expect(component.getIndicatorClass()).toBe('indicator-arrow__up'); + + component.isVersionsDropDownShow.set(false); + expect(component.getIndicatorClass()).toBe(''); + }); + + it('should toggle isVersionsDropDownShow on calling onShowVersions', () => { + const initialState = component.isVersionsDropDownShow(); + + component.onShowVersions(); + expect(component.isVersionsDropDownShow()).toBe(!initialState); + + component.onShowVersions(); + expect(component.isVersionsDropDownShow()).toBe(initialState); + }); +}); diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.ts new file mode 100644 index 000000000..e4a549022 --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.ts @@ -0,0 +1,133 @@ +import { + AfterViewInit, + Component, + inject, + Input, + signal, + WritableSignal +} from '@angular/core'; +import { ThemeService } from '../../../../core/services/theme/theme.service'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { ProductService } from '../../product.service'; +import { Artifact } from '../../../../shared/models/vesion-artifact.model'; +import { Tooltip } from 'bootstrap'; + +const delayTimeBeforeHideMessage = 2000; +@Component({ + selector: 'app-product-version-action', + standalone: true, + imports: [CommonModule, TranslateModule, FormsModule], + templateUrl: './product-detail-version-action.component.html', + styleUrl: './product-detail-version-action.component.scss' +}) +export class ProductDetailVersionActionComponent implements AfterViewInit { + ngAfterViewInit() { + const tooltipTriggerList = [].slice.call( + document.querySelectorAll('[data-bs-toggle="tooltip"]') + ); + tooltipTriggerList.map(function (tooltipTriggerEl) { + return new Tooltip(tooltipTriggerEl); + }); + } + + @Input() + productId!: string; + versions: WritableSignal = signal([]); + artifacts: WritableSignal = signal([]); + themeService = inject(ThemeService); + translateService = inject(TranslateService); + isDevVersionsDisplayed = signal(false); + isDropDownDisplayed = signal(false); + isVersionsDropDownShow = signal(false); + isDesignerEnvironment = signal(false); + isInvalidInstallationEnvironment = signal(false); + designerVersion = ''; + selectedArtifact = ''; + selectedVersion!: string; + productService = inject(ProductService); + versionMap: Map = new Map(); + + getIndicatorClass() { + if (this.isVersionsDropDownShow()) { + return 'indicator-arrow__up'; + } + return ''; + } + + onShowVersions() { + this.isVersionsDropDownShow.set(!this.isVersionsDropDownShow()); + } + + getInstallationTooltipText() { + return `Please open the + Axon Ivy Market + inside your + Axon Ivy Designer + (minimum version 9.2.0)`; + } + + onSelectVersion() { + this.artifacts.set(this.versionMap.get(this.selectedVersion) || []); + + if (this.artifacts().length !== 0) { + this.selectedArtifact = this.artifacts()[0].downloadUrl; + } + } + + onShowDevVersion(event: Event) { + event.preventDefault(); + this.isDevVersionsDisplayed.set(!this.isDevVersionsDisplayed()); + this.getVersionWithArtifact(); + } + + onShowVersionAndArtifact() { + if (!this.isDropDownDisplayed() && this.artifacts().length === 0) { + this.getVersionWithArtifact(); + } + this.isDropDownDisplayed.set(!this.isDropDownDisplayed()); + } + + getVersionWithArtifact() { + this.sanitizeDataBeforFetching(); + + this.productService + .sendRequestToProductDetailVersionAPI( + this.productId, + this.isDevVersionsDisplayed(), + this.designerVersion + ) + .subscribe(data => { + data.forEach(item => { + const version = 'Version '.concat(item.version); + this.versions.update(currentVersions => [ + ...currentVersions, + version + ]); + if (!this.versionMap.get(version)) { + this.versionMap.set(version, item.artifactsByVersion); + } + }); + if (this.versions().length !== 0) { + this.selectedVersion = this.versions()[0]; + this.onSelectVersion(); + } + }); + } + + sanitizeDataBeforFetching() { + this.versions.set([]); + this.artifacts.set([]); + this.selectedArtifact = ''; + this.selectedVersion = ''; + } + + downloadArifact() { + const newTab = window.open(this.selectedArtifact, '_blank'); + if (newTab) { + newTab.blur(); + } + window.focus(); + } +} diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.html b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.html new file mode 100644 index 000000000..5c3494f19 --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.html @@ -0,0 +1,18 @@ + + + + {{ + product.names | multilingualism: languageService.getSelectedLanguage() + }} + + + {{ + product.shortDescriptions + | multilingualism: languageService.getSelectedLanguage() + }} + + + + diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.scss b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.spec.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.spec.ts new file mode 100644 index 000000000..834b5b654 --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.spec.ts @@ -0,0 +1,48 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { MOCK_PRODUCTS } from '../../../shared/mocks/mock-data'; +import { MockProductService } from '../../../shared/mocks/mock-services'; +import { ProductService } from '../product.service'; +import { ProductDetailComponent } from './product-detail.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { Product } from '../../../shared/models/product.model'; + +const products = MOCK_PRODUCTS._embedded.products as Product[]; + +describe('ProductDetailComponent', () => { + let component: ProductDetailComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProductDetailComponent, TranslateModule.forRoot()], + providers: [ + { + provide: ActivatedRoute, + useValue: { + snapshot: { + params: { id: products[0].id } + } + } + } + ] + }) + .overrideComponent(ProductDetailComponent, { + remove: { providers: [ProductService] }, + add: { + providers: [{ provide: ProductService, useClass: MockProductService }] + } + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ProductDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component.product.names.en).toEqual(products[0].names.en); + }); +}); diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.ts new file mode 100644 index 000000000..1d965d7a7 --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.ts @@ -0,0 +1,33 @@ +import { Component, inject } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Product } from '../../../shared/models/product.model'; +import { ProductService } from '../product.service'; +import { LanguageService } from '../../../core/services/language/language.service'; +import { MultilingualismPipe } from '../../../shared/pipes/multilingualism.pipe'; +import { ProductDetailVersionActionComponent } from './product-detail-version-action/product-detail-version-action.component'; + +@Component({ + selector: 'app-product-detail', + standalone: true, + imports: [MultilingualismPipe, ProductDetailVersionActionComponent], + providers: [ProductService], + templateUrl: './product-detail.component.html', + styleUrl: './product-detail.component.scss' +}) +export class ProductDetailComponent { + product!: Product; + route = inject(ActivatedRoute); + productService = inject(ProductService); + languageService = inject(LanguageService); + productId!: string; + + constructor() { + const productId = this.route.snapshot.params['id']; + if (productId) { + this.productId = productId; + this.productService.getProductById(productId).subscribe(product => { + this.product = product; + }); + } + } +} \ No newline at end of file diff --git a/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.html b/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.html new file mode 100644 index 000000000..519f6db4c --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.html @@ -0,0 +1,79 @@ + + + {{ translateService.get('common.filter.label') | async }} + + + + + @for (type of types; track $index) { + + + {{ type.label | translate }} + + + } + + + + @for (type of types; track $index) { + + {{ type.label | translate }} + + } + + + + + + + {{ translateService.get('common.sort.label') | async }}: + + + @for (type of sorts; track $index) { + + {{ type.label | translate }} + + } + + + + + + + + + + + + + + diff --git a/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.scss b/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.scss new file mode 100644 index 000000000..df6c488d0 --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.scss @@ -0,0 +1,64 @@ +$rowHeight: 37px; + +.filter-container { + height: $rowHeight; +} + +.filter-container__button { + height: $rowHeight; +} + +.sort-container { + height: $rowHeight; + font-weight: 400; + font-size: 14px; +} + +.filter-type { + height: $rowHeight; + width: max-content; + line-height: 16.8px; + border-radius: 10px; + padding: 10px 15px; + margin: 0 15px 0 0; + gap: 10px; + cursor: default; +} + +.sort-container__label { + line-height: 25.2px; +} + +.form-select { + height: $rowHeight; + line-height: 16.8px; + font-weight: 400; + font-size: 14px; + text-align: left; + border-radius: 10px; + padding: 10px 15px; + gap: 10px; + opacity: 80%; +} + +.input__search { + height: 43px; +} + +@media only screen and (max-width: 768px) { + .form-select { + width: 100% !important; + } +} + +@media only screen and (max-width: 996px) { + .sort-container__label { + display: none; + } +} + +@media only screen and (max-width: 1366px) { + .filter-type { + font-size: 1rem; + } +} diff --git a/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.spec.ts b/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.spec.ts new file mode 100644 index 000000000..6ff88295c --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.spec.ts @@ -0,0 +1,76 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { By } from '@angular/platform-browser'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { TypeOption } from '../../../shared/enums/type-option.enum'; +import { SortOption } from '../../../shared/enums/sort-option.enum'; +import { ProductFilterComponent } from './product-filter.component'; +import { Viewport } from 'karma-viewport/dist/adapter/viewport'; + +declare const viewport: Viewport; + +describe('ProductFilterComponent', () => { + let component: ProductFilterComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProductFilterComponent, TranslateModule.forRoot()], + providers: [TranslateService] + }).compileComponents(); + + fixture = TestBed.createComponent(ProductFilterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('onSelectedType should update selectedTypeOption correctly', () => { + const filterElement = fixture.debugElement.queryAll( + By.css('.filter-type') + )[1].nativeElement as HTMLDivElement; + + filterElement.dispatchEvent(new Event('click')); + expect(component.selectedType).toEqual(TypeOption.CONNECTORS); + }); + + it('filter type should change to selectbox in small screen', () => { + viewport.set(540); + const filterSelect = fixture.debugElement.query( + By.css('.filter-type--select') + ); + + expect(getComputedStyle(filterSelect.nativeElement).display).not.toBe( + 'none' + ); + }); + + it('sort label should not display in small screen', () => { + viewport.set(900); + const sortLabel = fixture.debugElement.query( + By.css('.sort-container__label') + ); + expect(getComputedStyle(sortLabel.nativeElement).display).toBe('none'); + }); + + it('onSortChange should update selectedSortOption correctly', () => { + const select: HTMLSelectElement = fixture.debugElement.query( + By.css('.sort-type') + ).nativeElement; + select.value = select.options[2].value; + select.dispatchEvent(new Event('change')); + fixture.detectChanges(); + expect(component.selectedSort).toEqual(SortOption.RECENT); + }); + + it('search should update searchText correctly', () => { + const searchText = 'portal'; + const input = fixture.debugElement.query(By.css('input')).nativeElement; + input.value = searchText; + input.dispatchEvent(new Event('input')); + expect(component.searchText).toEqual(searchText); + }); +}); diff --git a/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.ts b/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.ts new file mode 100644 index 000000000..d8730c47a --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.ts @@ -0,0 +1,47 @@ +import { CommonModule } from '@angular/common'; +import { Component, EventEmitter, Output, inject } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { ThemeService } from '../../../core/services/theme/theme.service'; +import { + FILTER_TYPES, + SORT_TYPES +} from '../../../shared/constants/common.constant'; +import { TypeOption } from '../../../shared/enums/type-option.enum'; +import { SortOption } from '../../../shared/enums/sort-option.enum'; + +@Component({ + selector: 'app-product-filter', + standalone: true, + imports: [CommonModule, FormsModule, TranslateModule], + templateUrl: './product-filter.component.html', + styleUrl: './product-filter.component.scss' +}) +export class ProductFilterComponent { + @Output() searchChange = new EventEmitter(); + @Output() filterChange = new EventEmitter(); + @Output() sortChange = new EventEmitter(); + + selectedType = TypeOption.All_TYPES; + types = FILTER_TYPES; + selectedSort: SortOption = SortOption.POPULARITY; + sorts = SORT_TYPES; + + searchText = ''; + + themeService = inject(ThemeService); + translateService = inject(TranslateService); + + onSelectType(type: TypeOption) { + this.selectedType = type; + this.filterChange.emit(type); + } + + onSearchChanged(searchString: string) { + this.searchChange.next(searchString); + } + + onSortChange() { + this.sortChange.next(this.selectedSort); + } +} diff --git a/marketplace-ui/src/app/modules/product/product.component.html b/marketplace-ui/src/app/modules/product/product.component.html new file mode 100644 index 000000000..efe03108d --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product.component.html @@ -0,0 +1,45 @@ + + + + {{ translateService.get('common.branch') | async }} + + + + {{ translateService.get('common.introduction.about') | async }} + + + + + {{ translateService.get('common.introduction.contribute') | async }} + + + + + + + + @if (products().length > 0) { + + @for (product of products(); track $index) { + + + + } + + } @else { + + {{ 'common.nothingFound' | translate }} + + } + + + diff --git a/marketplace-ui/src/app/modules/product/product.component.scss b/marketplace-ui/src/app/modules/product/product.component.scss new file mode 100644 index 000000000..47ff283db --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product.component.scss @@ -0,0 +1,3 @@ +.product-container { + min-height: 6em; +} diff --git a/marketplace-ui/src/app/modules/product/product.component.spec.ts b/marketplace-ui/src/app/modules/product/product.component.spec.ts new file mode 100644 index 000000000..071c54229 --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product.component.spec.ts @@ -0,0 +1,159 @@ +import { + ComponentFixture, + TestBed, + fakeAsync, + tick +} from '@angular/core/testing'; + +import { provideHttpClient } from '@angular/common/http'; +import { Router } from '@angular/router'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { Subscription } from 'rxjs'; +import { TypeOption } from '../../shared/enums/type-option.enum'; +import { SortOption } from '../../shared/enums/sort-option.enum'; +import { ProductComponent } from './product.component'; +import { ProductService } from './product.service'; +import { MockProductService } from '../../shared/mocks/mock-services'; + +const router = { + navigate: jasmine.createSpy('navigate') +}; + +describe('ProductComponent', () => { + let component: ProductComponent; + let fixture: ComponentFixture; + let mockIntersectionObserver: any; + + beforeAll(() => { + mockIntersectionObserver = jasmine.createSpyObj('IntersectionObserver', ['observe', 'unobserve', 'disconnect']); + mockIntersectionObserver.observe.and.callFake(() => { }); + mockIntersectionObserver.unobserve.and.callFake(() => { }); + mockIntersectionObserver.disconnect.and.callFake(() => { }); + + (window as any).IntersectionObserver = function (callback: IntersectionObserverCallback) { + mockIntersectionObserver.callback = callback; + return mockIntersectionObserver; + }; + }); + + afterAll(() => { + delete (window as any).IntersectionObserver; + }); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProductComponent, TranslateModule.forRoot()], + providers: [ + { + provide: Router, + useValue: router + }, + ProductService, + TranslateService, + provideHttpClient() + ] + }) + .overrideComponent(ProductComponent, { + remove: { providers: [ProductService] }, + add: { + providers: [{ provide: ProductService, useClass: MockProductService }] + } + }) + .compileComponents(); + fixture = TestBed.createComponent(ProductComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('viewProductDetail should navigate', () => { + component.viewProductDetail('url'); + expect(router.navigate).toHaveBeenCalledWith(['', 'url']); + }); + + it('loadProductItems should return products with criteria', () => { + + component.loadProductItems(); + expect(component.loadProductItems).toBeTruthy(); + }) + + it('ngOnDestroy should unsubscribe all sub', () => { + const sub = new Subscription(); + component.subscriptions.push(sub); + component.ngOnDestroy(); + expect(component.ngOnDestroy).toBeTruthy(); + }); + + it('onFilterChange should filter products properly', () => { + component.onFilterChange(TypeOption.CONNECTORS); + component.products().forEach((product) => { + expect(product.type).toEqual('connector'); + }); + }); + + it('onSortChange should order products properly', () => { + component.onSearchChanged('cur'); + component.onSortChange(SortOption.ALPHABETICALLY); + for (let i = 0; i < component.products.length - 1; i++) { + expect( + component.products()[i + 1].names.en.localeCompare(component.products()[i].names.en) + ).toEqual(1); + } + }); + + it('search should return match products name', fakeAsync(() => { + const productName = 'amazon comprehend'; + component.onSearchChanged(productName); + tick(500); + component.products().forEach((product) => { + expect(product.names.en.toLowerCase()).toContain(productName); + }); + })); + + it('setupIntersectionObserver should not trigger when init page', () => { + component.ngAfterViewInit(); + expect(component.criteria.nextPageHref).toBeUndefined(); + }); + + it('should call loadProductItems when observerElement is intersecting and has more products', () => { + spyOn(component, 'loadProductItems').and.callThrough(); + spyOn(component, 'hasMore').and.returnValue(true); + + const entries = [{ isIntersecting: true }]; + const callback = mockIntersectionObserver.callback; + + callback(entries as IntersectionObserverEntry[]); + + expect(component.hasMore).toHaveBeenCalled(); + expect(component.loadProductItems).toHaveBeenCalled(); + }); + + it('should not call loadProductItems when observerElement is not intersecting', () => { + spyOn(component, 'loadProductItems').and.callThrough(); + spyOn(component, 'hasMore').and.returnValue(true); + + const entries = [{ isIntersecting: false }]; + const callback = mockIntersectionObserver.callback; + + callback(entries as IntersectionObserverEntry[]); + + expect(component.hasMore).not.toHaveBeenCalled(); + expect(component.loadProductItems).not.toHaveBeenCalled(); + }); + + it('should not call loadProductItems when there are no more products', () => { + spyOn(component, 'loadProductItems').and.callThrough(); + spyOn(component, 'hasMore').and.returnValue(false); + + const entries = [{ isIntersecting: true }]; + const callback = mockIntersectionObserver.callback; + + callback(entries as IntersectionObserverEntry[]); + + expect(component.hasMore).toHaveBeenCalled(); + expect(component.loadProductItems).not.toHaveBeenCalled(); + }); +}); diff --git a/marketplace-ui/src/app/modules/product/product.component.ts b/marketplace-ui/src/app/modules/product/product.component.ts new file mode 100644 index 000000000..00ae84665 --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product.component.ts @@ -0,0 +1,155 @@ +import { CommonModule } from '@angular/common'; +import { + AfterViewInit, + Component, + ElementRef, + OnDestroy, + ViewChild, + WritableSignal, + inject, + signal +} from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { Router } from '@angular/router'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { Subject, Subscription, debounceTime } from 'rxjs'; +import { ThemeService } from '../../core/services/theme/theme.service'; +import { TypeOption } from '../../shared/enums/type-option.enum'; +import { SortOption } from '../../shared/enums/sort-option.enum'; +import { Criteria } from '../../shared/models/criteria.model'; +import { Product } from '../../shared/models/product.model'; +import { ProductCardComponent } from './product-card/product-card.component'; +import { ProductFilterComponent } from './product-filter/product-filter.component'; +import { ProductService } from './product.service'; +import { ProductApiResponse } from '../../shared/models/apis/product-response.model'; +import { Link } from '../../shared/models/apis/link.model'; +import { Page } from '../../shared/models/apis/page.model'; +import { Language } from '../../shared/enums/language.enum'; +import { LanguageService } from '../../core/services/language/language.service'; + +const SEARCH_DEBOUNCE_TIME = 500; + +@Component({ + selector: 'app-product', + standalone: true, + imports: [ + CommonModule, + FormsModule, + TranslateModule, + ProductCardComponent, + ProductFilterComponent + ], + providers: [ProductService], + templateUrl: './product.component.html', + styleUrl: './product.component.scss' +}) +export class ProductComponent implements AfterViewInit, OnDestroy { + products: WritableSignal = signal([]); + subscriptions: Subscription[] = []; + searchTextChanged = new Subject(); + criteria: Criteria = { + search: '', + type: TypeOption.All_TYPES, + sort: SortOption.POPULARITY, + language: Language.EN + }; + responseLink!: Link; + responsePage!: Page; + + productService = inject(ProductService); + themeService = inject(ThemeService); + translateService = inject(TranslateService); + languageService = inject(LanguageService); + + router = inject(Router); + @ViewChild('observer', { static: true }) observerElement!: ElementRef; + + constructor() { + this.loadProductItems(); + this.subscriptions.push( + this.searchTextChanged + .pipe(debounceTime(SEARCH_DEBOUNCE_TIME)) + .subscribe(value => { + this.criteria = { + ...this.criteria, + search: value + }; + this.loadProductItems(true); + }) + ); + } + + ngAfterViewInit(): void { + this.setupIntersectionObserver(); + } + + viewProductDetail(productId: string) { + this.router.navigate(['', productId]); + } + + onFilterChange(selectedType: TypeOption) { + this.criteria = { + ...this.criteria, + nextPageHref: '', + type: selectedType + }; + this.loadProductItems(true); + } + + onSortChange(selectedSort: SortOption) { + this.criteria = { + ...this.criteria, + nextPageHref: '', + sort: selectedSort + }; + this.loadProductItems(true); + } + + onSearchChanged(searchString: string) { + this.searchTextChanged.next(searchString); + } + + loadProductItems(shouldCleanData = false) { + this.criteria.language = this.languageService.getSelectedLanguage(); + this.subscriptions.push( + this.productService.findProductsByCriteria(this.criteria).subscribe((response: ProductApiResponse) => { + const newProducts = response._embedded.products; + if (shouldCleanData) { + this.products.set(newProducts); + } else { + this.products.update(existingProducts => existingProducts.concat(newProducts)); + } + this.responseLink = response._links; + this.responsePage = response.page; + }) + ); + } + + setupIntersectionObserver() { + const options = { root: null, rootMargin: '0px', threshold: 0.1 }; + const observer = new IntersectionObserver(entries => { + entries.forEach(entry => { + if (entry.isIntersecting && this.hasMore()) { + this.criteria.nextPageHref = this.responseLink?.next?.href; + this.loadProductItems(); + } + }); + }, options); + + observer.observe(this.observerElement.nativeElement); + } + + hasMore() { + if (!this.responsePage || !this.responseLink) { + return false; + } + return this.responsePage.number < this.responsePage.totalPages + && this.responseLink?.next !== undefined; + } + + ngOnDestroy(): void { + this.subscriptions.forEach(sub => { + sub.unsubscribe(); + }); + } +} diff --git a/marketplace-ui/src/app/modules/product/product.routes.ts b/marketplace-ui/src/app/modules/product/product.routes.ts new file mode 100644 index 000000000..c4fa88ebd --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product.routes.ts @@ -0,0 +1,11 @@ +import { Route } from '@angular/router'; + +export const routes: Route[] = [ + { + path: '', + loadComponent: () => + import('./product-detail/product-detail.component').then( + (m) => m.ProductDetailComponent + ), + }, +]; diff --git a/marketplace-ui/src/app/modules/product/product.service.spec.ts b/marketplace-ui/src/app/modules/product/product.service.spec.ts new file mode 100644 index 000000000..df0deed1f --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product.service.spec.ts @@ -0,0 +1,183 @@ +import { TestBed } from '@angular/core/testing'; + +import { + provideHttpClient, + withInterceptorsFromDi +} from '@angular/common/http'; +import { + HttpTestingController, + provideHttpClientTesting +} from '@angular/common/http/testing'; +import { TypeOption } from '../../shared/enums/type-option.enum'; +import { SortOption } from '../../shared/enums/sort-option.enum'; +import { MOCK_PRODUCTS } from '../../shared/mocks/mock-data'; +import { Criteria } from '../../shared/models/criteria.model'; +import { ProductService } from './product.service'; +import { Product } from '../../shared/models/product.model'; +import { catchError } from 'rxjs'; +import { LoadingService } from '../../core/services/loading/loading.service'; +import { VersionData } from '../../shared/models/vesion-artifact.model'; +import { Language } from '../../shared/enums/language.enum'; + +const PRODUCT_ID = 'amazon-comprehend'; +const NOT_EXIST_ID = 'undefined'; + +describe('ProductService', () => { + let products = MOCK_PRODUCTS._embedded.products as Product[]; + let service: ProductService; + let httpMock: HttpTestingController; + let loadingServiceSpy: jasmine.SpyObj; + + beforeEach(() => { + const spyLoading = jasmine.createSpyObj('LoadingService', ['show', 'hide']); + + TestBed.configureTestingModule({ + imports: [], + providers: [ + ProductService, + provideHttpClient(withInterceptorsFromDi()), + provideHttpClientTesting(), + { provide: LoadingService, useValue: spyLoading } + ] + }); + service = TestBed.inject(ProductService); + httpMock = TestBed.inject(HttpTestingController); + loadingServiceSpy = TestBed.inject( + LoadingService + ) as jasmine.SpyObj; + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('getProductById should return a product', () => { + service.getProductById(PRODUCT_ID).subscribe(data => { + expect(data.id).toEqual(PRODUCT_ID); + }); + }); + + it('getProductById should return null product', () => { + service.getProductById(NOT_EXIST_ID).subscribe(data => { + expect(data).toEqual({} as Product); + }); + }); + + it('findProductsByCriteria with should return products properly', () => { + const searchString = 'Amazon Comprehend'; + const criteria: Criteria = { + search: searchString, + sort: SortOption.ALPHABETICALLY, + type: TypeOption.CONNECTORS, + language: Language.EN + }; + service.findProductsByCriteria(criteria).subscribe(response => { + let products = response._embedded.products; + for (let i = 0; i < products.length; i++) { + expect(products[i].type).toEqual(TypeOption.CONNECTORS); + expect(products[i].names.en.toLowerCase()).toContain(searchString); + if (products[i + 1]) { + expect(products[i + 1].names.en.localeCompare(products[i].names.en)).toEqual( + 1 + ); + } + } + }); + }); + + it('findProductsByCriteria with empty searchString', () => { + const criteria: Criteria = { + search: '', + sort: null, + type: null, + language: Language.EN + }; + service.findProductsByCriteria(criteria).subscribe(response => { + expect(response._embedded.products.length).toEqual(products.length); + }); + }); + + it('findProductsByCriteria with popularity order', () => { + const criteria: Criteria = { + search: '', + sort: SortOption.POPULARITY, + type: null, + language: Language.EN + }; + service.findProductsByCriteria(criteria).subscribe(response => { + let products = response._embedded.products; + for (let i = 0; i < products.length; i++) { + if ( + products[i].platformReview && + products[i + 1] && + products[i + 1].platformReview + ) { + expect(Number(products[i + 1].platformReview)).toBeGreaterThanOrEqual( + Number(products[i].platformReview) + ); + } + } + }); + }); + + it('findProductsByCriteria with default sort', () => { + const criteria: Criteria = { + search: '', + sort: SortOption.RECENT, + type: null, + language: Language.EN + }; + service.findProductsByCriteria(criteria).subscribe(response => { + expect(response._embedded.products.length).toEqual(products.length); + }); + }); + + it('findProductsByCriteria by next page url', () => { + const criteria: Criteria = { + nextPageHref: + 'http://localhost:8080/marketplace-service/api/product?type=all&page=1&size=20', + search: '', + sort: SortOption.RECENT, + type: TypeOption.All_TYPES, + language: Language.EN + }; + service.findProductsByCriteria(criteria).subscribe(response => { + expect(response._embedded.products.length).toEqual(0); + expect(response.page.number).toEqual(1); + }); + }); + + it('should call the API and return VersionData[]', () => { + const mockResponse: VersionData[] = [ + { version: '10.0.1', artifactsByVersion: [] } + ]; + + const productId = 'adobe-acrobat-connector'; + const showDevVersion = true; + const designerVersion = '10.0.1'; + + service + .sendRequestToProductDetailVersionAPI( + productId, + showDevVersion, + designerVersion + ) + .subscribe(data => { + expect(data).toEqual(mockResponse); + }); + + const req = httpMock.expectOne(request => { + return ( + request.url === `api/product-details/${productId}/versions` && + request.params.get('designerVersion') === designerVersion && + request.params.get('isShowDevVersion') === showDevVersion.toString() + ); + }); + + expect(req.request.method).toBe('GET'); + req.flush(mockResponse); + + expect(loadingServiceSpy.show).toHaveBeenCalled(); + expect(loadingServiceSpy.hide).toHaveBeenCalled(); + }); +}); diff --git a/marketplace-ui/src/app/modules/product/product.service.ts b/marketplace-ui/src/app/modules/product/product.service.ts new file mode 100644 index 000000000..fac5ed24c --- /dev/null +++ b/marketplace-ui/src/app/modules/product/product.service.ts @@ -0,0 +1,62 @@ +import { HttpClient, HttpContext, HttpParams } from '@angular/common/http'; +import { Injectable, inject } from '@angular/core'; +import { Observable, of, tap } from 'rxjs'; +import { MOCK_PRODUCTS } from '../../shared/mocks/mock-data'; +import { Criteria } from '../../shared/models/criteria.model'; +import { Product } from '../../shared/models/product.model'; +import { VersionData } from '../../shared/models/vesion-artifact.model'; +import { LoadingService } from '../../core/services/loading/loading.service'; +import { RequestParam } from '../../shared/enums/request-param'; +import { ProductApiResponse } from '../../shared/models/apis/product-response.model'; +import { SkipLoading } from '../../core/interceptors/api.interceptor'; + +const PRODUCT_API_URL = 'api/product'; +@Injectable() +export class ProductService { + httpClient = inject(HttpClient); + loadingService = inject(LoadingService); + + findProductsByCriteria(criteria: Criteria): Observable { + let requestParams = new HttpParams(); + let requestURL = PRODUCT_API_URL; + if (criteria.nextPageHref) { + requestURL = criteria.nextPageHref; + } else { + requestParams = requestParams + .set(RequestParam.TYPE, `${criteria.type}`) + .set(RequestParam.SORT, `${criteria.sort}`) + .set(RequestParam.KEYWORD, `${criteria.search}`) + .set(RequestParam.LANGUAGE, `${criteria.language}`); + } + return this.httpClient.get(requestURL, { + params: requestParams, + context: new HttpContext().set(SkipLoading, true) + }); + } + + getProductById(productId: string): Observable { + const products = MOCK_PRODUCTS._embedded.products; + const product = products.find(p => p.id === productId); + if (product) { + return of(product); + } + return of({} as Product); + } + + sendRequestToProductDetailVersionAPI( + productId: string, + showDevVersion: boolean, + designerVersion: string + ): Observable { + this.loadingService.show(); + const url = `api/product-details/${productId}/versions`; + const params = new HttpParams() + .append('designerVersion', designerVersion) + .append('isShowDevVersion', showDevVersion); + return this.httpClient.get(url, { params }).pipe( + tap(() => { + this.loadingService.hide(); + }) + ); + } +} diff --git a/marketplace-ui/src/app/shared/components/footer/footer.component.html b/marketplace-ui/src/app/shared/components/footer/footer.component.html new file mode 100644 index 000000000..53941ff9e --- /dev/null +++ b/marketplace-ui/src/app/shared/components/footer/footer.component.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + + {{ 'common.footer.downloadLatestLTSVersion' | translate }} + + + {{ 'common.footer.downloadLatestDevVersion' | translate }} + + + + + + + + + + + + + + + + + + diff --git a/marketplace-ui/src/app/shared/components/footer/footer.component.scss b/marketplace-ui/src/app/shared/components/footer/footer.component.scss new file mode 100644 index 000000000..85e4d8669 --- /dev/null +++ b/marketplace-ui/src/app/shared/components/footer/footer.component.scss @@ -0,0 +1,38 @@ +.logo { + margin: 0 0 2rem 0; +} + +.logo__image { + width: 132px; + height: 20px; + margin-top: 1rem; + vertical-align: initial; +} + +.nav-item { + margin: 0 0 0 30px; +} + +.nav-link { + font-weight: 600; + padding: 0 15px; +} + +.button-container { + margin: 24px 0 0 0; +} + +.link-icon { + width: 18px; + height: 18px; +} + +.link--left { + margin: 0 2rem 0 0; +} + +@media all and (max-width: 990px) { + .link--left { + margin: 0; + } +} diff --git a/marketplace-ui/src/app/shared/components/footer/footer.component.spec.ts b/marketplace-ui/src/app/shared/components/footer/footer.component.spec.ts new file mode 100644 index 000000000..5c276dfd6 --- /dev/null +++ b/marketplace-ui/src/app/shared/components/footer/footer.component.spec.ts @@ -0,0 +1,73 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FooterComponent } from './footer.component'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { By } from '@angular/platform-browser'; +import { Viewport } from 'karma-viewport/dist/adapter/viewport'; + +declare const viewport: Viewport; + +describe('FooterComponent', () => { + let component: FooterComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FooterComponent, TranslateModule.forRoot()], + providers: [TranslateService] + }).compileComponents(); + + fixture = TestBed.createComponent(FooterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('navbar should not display in mobile screen', () => { + viewport.set(540); + + const mobileSearch = fixture.debugElement.query(By.css('.footer__navbar')); + + expect(getComputedStyle(mobileSearch.nativeElement).display).toBe('none'); + }); + + it('social media section should be in the bottom of mobile screen', () => { + viewport.set(540); + + const footerSocialMedia = fixture.nativeElement.querySelector( + '.footer__social-media' + ); + const footerIvyPolicy = fixture.nativeElement.querySelector( + '.footer__ivy-policy' + ); + + expect(footerSocialMedia.getBoundingClientRect().top).toBeGreaterThan( + footerIvyPolicy.getBoundingClientRect().top + ); + }); + + it('Ivy tag in ivy policy section should be display in higher row', () => { + viewport.set(540); + + const ivyTag = fixture.nativeElement.querySelector('.footer__ivy-tag'); + + const ivyTermOfService = fixture.nativeElement.querySelector( + '.footer__ivy-term-of-service-tag' + ); + + expect(ivyTag.getBoundingClientRect().top).toBeLessThan( + ivyTermOfService.getBoundingClientRect().top + ); + }); + + it('content layout should be displayed in the center', () => { + viewport.set(480); + + const logo = fixture.debugElement.query(By.css('.logo__image')); + const ivyPolicy = fixture.debugElement.query(By.css('.footer__ivy-policy')); + expect(getComputedStyle(logo.nativeElement).textAlign).toBe('center'); + expect(getComputedStyle(ivyPolicy.nativeElement).textAlign).toBe('center'); + }); +}); diff --git a/marketplace-ui/src/app/shared/components/footer/footer.component.ts b/marketplace-ui/src/app/shared/components/footer/footer.component.ts new file mode 100644 index 000000000..4a3d088c0 --- /dev/null +++ b/marketplace-ui/src/app/shared/components/footer/footer.component.ts @@ -0,0 +1,24 @@ +import { CommonModule } from '@angular/common'; +import { Component, inject } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { ThemeService } from '../../../core/services/theme/theme.service'; +import { + IVY_FOOTER_LINKS, + NAV_ITEMS, + SOCIAL_MEDIA_LINK +} from '../../constants/common.constant'; +import { NavItem } from '../../models/nav-item.model'; + +@Component({ + selector: 'app-footer', + standalone: true, + imports: [CommonModule, TranslateModule], + templateUrl: './footer.component.html', + styleUrls: ['./footer.component.scss', '../../../app.component.scss'] +}) +export class FooterComponent { + themeService = inject(ThemeService); + socialMediaLinks = SOCIAL_MEDIA_LINK; + navItems: NavItem[] = NAV_ITEMS; + ivyFooterLinks = IVY_FOOTER_LINKS; +} diff --git a/marketplace-ui/src/app/shared/components/header/header.component.html b/marketplace-ui/src/app/shared/components/header/header.component.html new file mode 100644 index 000000000..b589d0446 --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/header.component.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/marketplace-ui/src/app/shared/components/header/header.component.scss b/marketplace-ui/src/app/shared/components/header/header.component.scss new file mode 100644 index 000000000..f05d6844e --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/header.component.scss @@ -0,0 +1,59 @@ +.navbar { + opacity: 0px; +} + +.navbar-brand { + padding-top: 0.8rem; +} + +.logo__image { + width: 130.5px; + height: 20px; + vertical-align: initial; +} + +.header__menu-button { + font-size: 24px; +} + +@media all and (max-width: 992px) { + .show, + .min-height-100 { + min-height: 85lvh; + } + + .form-control { + height: 43px; + } + + .navbar { + border: none; + } + + .navbar-brand { + padding: 14.42px 0; + } + + .nav-link { + height: 45px; + padding: 10px 0px 10px 15px; + gap: 0px; + border-radius: 5px; + opacity: 0px; + } + + .nav-link.active { + height: 45px; + padding: 10px 15px 10px -15px; + gap: 0px; + border-radius: 5px; + opacity: 0px; + background-color: var(--ivy-secondary-background); + } +} + +@media all and (max-width: 1200px) { + .header__navbar-content { + height: 90vh; + } +} diff --git a/marketplace-ui/src/app/shared/components/header/header.component.spec.ts b/marketplace-ui/src/app/shared/components/header/header.component.spec.ts new file mode 100644 index 000000000..72f6688f7 --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/header.component.spec.ts @@ -0,0 +1,105 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { HeaderComponent } from './header.component'; +import { Viewport } from 'karma-viewport/dist/adapter/viewport'; + +declare const viewport: Viewport; + +describe('HeaderComponent', () => { + let component: HeaderComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [HeaderComponent, TranslateModule.forRoot()], + providers: [TranslateService] + }).compileComponents(); + + fixture = TestBed.createComponent(HeaderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should toggle the mobile menu on click', () => { + const navbarToggler = fixture.debugElement.query(By.css('.bi.bi-list')); + + expect(component.isMobileMenuCollapsed()).toBeTrue(); + + // Click the mobile menu toggler + navbarToggler.triggerEventHandler('click', null); + fixture.detectChanges(); + + expect(component.isMobileMenuCollapsed()).toBeFalse(); + + // Click the mobile menu toggler again + navbarToggler.triggerEventHandler('click', null); + fixture.detectChanges(); + + expect(component.isMobileMenuCollapsed()).toBeTrue(); + }); + + // Responsive section + it('action section should display in the bottom of the view in mobile mode', () => { + viewport.set(540); + + const headerNavigation = fixture.nativeElement.querySelector( + '.header__navigation' + ); + const headerAction = fixture.nativeElement.querySelector('.header__action'); + + const headerNavigationBeforeShowNavBar = + headerNavigation.getBoundingClientRect(); + const headerActionBeforeShowNavBar = headerAction.getBoundingClientRect(); + + const menuButton = fixture.debugElement.query( + By.css('.header__menu-button') + ); + menuButton.triggerEventHandler('click', null); + fixture.detectChanges(); + const headerNavigationAfterShowNavBar = + headerNavigation.getBoundingClientRect(); + const headerActionAfterShowNavBar = headerAction.getBoundingClientRect(); + expect(headerNavigationBeforeShowNavBar.top).toBeLessThan( + headerActionAfterShowNavBar.top + ); + expect(headerActionBeforeShowNavBar.top).toBeLessThan( + headerNavigationAfterShowNavBar.top + ); + + expect(headerNavigationAfterShowNavBar.bottom).toBeLessThan( + headerActionAfterShowNavBar.top + ); + }); + + it('navigation section should display in vertical', () => { + viewport.set(540); + const menuButton = fixture.debugElement.query( + By.css('.header__menu-button') + ); + menuButton.triggerEventHandler('click', null); + + fixture.detectChanges(); + const navBar = fixture.debugElement.query( + By.css('.header__navbar-content') + ); + + expect(getComputedStyle(navBar.nativeElement).flexDirection).toBe('column'); + }); + + it('menu button should be in the right side of mobile view', () => { + viewport.set(540); + const menuButton = fixture.nativeElement.querySelector( + '.header__menu-button' + ); + + const logo = fixture.nativeElement.querySelector('.logo__image'); + expect(menuButton.getBoundingClientRect().left).toBeGreaterThan( + logo.getBoundingClientRect().right + ); + }); +}); diff --git a/marketplace-ui/src/app/shared/components/header/header.component.ts b/marketplace-ui/src/app/shared/components/header/header.component.ts new file mode 100644 index 000000000..e4d84e495 --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/header.component.ts @@ -0,0 +1,47 @@ +import { CommonModule } from '@angular/common'; +import { Component, WritableSignal, inject, signal } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { LanguageService } from '../../../core/services/language/language.service'; +import { ThemeService } from '../../../core/services/theme/theme.service'; +import { Language } from '../../enums/language.enum'; +import { LanguageSelectionComponent } from './language-selection/language-selection.component'; +import { NavigationComponent } from './navigation/navigation.component'; +import { SearchBarComponent } from './search-bar/search-bar.component'; +import { ThemeSelectionComponent } from './theme-selection/theme-selection.component'; + +@Component({ + selector: 'app-header', + standalone: true, + imports: [ + CommonModule, + FormsModule, + TranslateModule, + NavigationComponent, + ThemeSelectionComponent, + LanguageSelectionComponent, + SearchBarComponent + ], + templateUrl: './header.component.html', + styleUrls: ['./header.component.scss', '../../../app.component.scss'] +}) +export class HeaderComponent { + selectedNav = '/'; + selectedLanguage = Language.EN; + + isMobileMenuCollapsed: WritableSignal = signal(true); + + themeService = inject(ThemeService); + translateService = inject(TranslateService); + languageService = inject(LanguageService); + + constructor() { + this.selectedLanguage = this.languageService.getSelectedLanguage(); + this.translateService.setDefaultLang(this.selectedLanguage); + this.translateService.use(this.selectedLanguage); + } + + onCollapsedMobileMenu() { + this.isMobileMenuCollapsed.update(value => !value); + } +} diff --git a/marketplace-ui/src/app/shared/components/header/language-selection/language-selection.component.html b/marketplace-ui/src/app/shared/components/header/language-selection/language-selection.component.html new file mode 100644 index 000000000..6269cbb0b --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/language-selection/language-selection.component.html @@ -0,0 +1,14 @@ + + @for (language of languages; track $index) { + + {{ language.label }} + + } + diff --git a/marketplace-ui/src/app/shared/components/header/language-selection/language-selection.component.scss b/marketplace-ui/src/app/shared/components/header/language-selection/language-selection.component.scss new file mode 100644 index 000000000..a9b5a469d --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/language-selection/language-selection.component.scss @@ -0,0 +1,18 @@ +.language-item { + width: 22px; + height: 22px; + font-size: 16px; + text-align: center; + font-weight: 600; + margin: 0 0 0 10px; + background: none; + cursor: pointer; +} + +.language-item.active { + color: var(--ivy-active-color); +} + +.language-item.inactive { + color: #757575; +} diff --git a/marketplace-ui/src/app/shared/components/header/language-selection/language-selection.component.spec.ts b/marketplace-ui/src/app/shared/components/header/language-selection/language-selection.component.spec.ts new file mode 100644 index 000000000..a11d1f541 --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/language-selection/language-selection.component.spec.ts @@ -0,0 +1,30 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TranslateService, TranslateModule } from '@ngx-translate/core'; + +import { LanguageSelectionComponent } from './language-selection.component'; + +describe('LanguageSelectionComponent', () => { + let component: LanguageSelectionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [LanguageSelectionComponent, TranslateModule.forRoot()], + providers: [TranslateService] + }).compileComponents(); + + fixture = TestBed.createComponent(LanguageSelectionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('selectLanguage should call translateService', () => { + spyOn(component.translateService, 'use').and.stub(); + component.onSelectLanguage('en'); + expect(component.translateService.use).toHaveBeenCalled(); + }); +}); diff --git a/marketplace-ui/src/app/shared/components/header/language-selection/language-selection.component.ts b/marketplace-ui/src/app/shared/components/header/language-selection/language-selection.component.ts new file mode 100644 index 000000000..4edc57603 --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/language-selection/language-selection.component.ts @@ -0,0 +1,27 @@ +import { CommonModule } from '@angular/common'; +import { + Component, + inject +} from '@angular/core'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { LANGUAGES } from '../../../constants/common.constant'; +import { LanguageService } from '../../../../core/services/language/language.service'; + +@Component({ + selector: 'app-language-selection', + standalone: true, + imports: [CommonModule, TranslateModule], + templateUrl: './language-selection.component.html', + styleUrl: './language-selection.component.scss' +}) +export class LanguageSelectionComponent { + languages = LANGUAGES; + translateService = inject(TranslateService); + languageService = inject(LanguageService); + + onSelectLanguage(language: string) { + this.translateService.setDefaultLang(language); + this.translateService.use(language); + this.languageService.loadLanguage(language); + } +} diff --git a/marketplace-ui/src/app/shared/components/header/navigation/navigation.component.html b/marketplace-ui/src/app/shared/components/header/navigation/navigation.component.html new file mode 100644 index 000000000..1b4f513c6 --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/navigation/navigation.component.html @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + @for (item of navItems; track $index) { + + + {{ item.label | translate }} + + + + } + + diff --git a/marketplace-ui/src/app/shared/components/header/navigation/navigation.component.scss b/marketplace-ui/src/app/shared/components/header/navigation/navigation.component.scss new file mode 100644 index 000000000..ae3aca955 --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/navigation/navigation.component.scss @@ -0,0 +1,61 @@ +.navbar-nav { + height: 60px; + padding-top: 0.8rem; +} + +.nav-item { + padding: 0 4px; + margin: 0 1rem 0 0; +} + +.nav-link { + height: 22px; + line-height: 22.4px; + font-weight: 400; + font-size: 16px; +} + +.active-line { + position: relative; + height: 2px; + top: 2rem; + width: inherit; + background-color: var(--ivy-active-color); +} + +.header-mobile__search { + height: 43px; + border-radius: 10px; +} + +@media all and (max-width: 992px) { + .show, + .min-height-100 { + min-height: 85lvh; + } + + .form-control { + height: 43px; + } + + .navbar { + border: none; + } + + .nav-link { + height: 45px; + padding: 10px 0px 10px 15px; + gap: 0px; + border-radius: 5px; + opacity: 0px; + } + + .nav-link.active { + height: 45px; + padding: 10px 15px 10px -15px; + gap: 0px; + border-radius: 5px; + opacity: 0px; + background-color: var(--ivy-secondary-background); + } +} diff --git a/marketplace-ui/src/app/shared/components/header/navigation/navigation.component.spec.ts b/marketplace-ui/src/app/shared/components/header/navigation/navigation.component.spec.ts new file mode 100644 index 000000000..2370b4f25 --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/navigation/navigation.component.spec.ts @@ -0,0 +1,48 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { Viewport } from 'karma-viewport/dist/adapter/viewport'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { NavigationComponent } from './navigation.component'; +declare const viewport: Viewport; + +describe('NavigationComponent', () => { + let component: NavigationComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NavigationComponent, TranslateModule.forRoot()], + providers: [TranslateService] + }).compileComponents(); + + fixture = TestBed.createComponent(NavigationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('mobile search should display in small screen', () => { + viewport.set(540); + + const mobileSearch = fixture.debugElement.query( + By.css('.header-mobile__search') + ); + + expect(getComputedStyle(mobileSearch.nativeElement).display).not.toBe( + 'none' + ); + }); + + it('mobile search should not display in large screen', () => { + viewport.set(1920); + + const mobileSearch = fixture.debugElement.query( + By.css('.header-mobile__search') + ); + + expect(getComputedStyle(mobileSearch.nativeElement).display).toBe('none'); + }); +}); diff --git a/marketplace-ui/src/app/shared/components/header/navigation/navigation.component.ts b/marketplace-ui/src/app/shared/components/header/navigation/navigation.component.ts new file mode 100644 index 000000000..f03bf7198 --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/navigation/navigation.component.ts @@ -0,0 +1,18 @@ +import { CommonModule } from '@angular/common'; +import { Component, Input, inject } from '@angular/core'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { NAV_ITEMS } from '../../../constants/common.constant'; +import { NavItem } from '../../../models/nav-item.model'; + +@Component({ + selector: 'app-navigation', + standalone: true, + imports: [CommonModule, TranslateModule], + templateUrl: './navigation.component.html', + styleUrl: './navigation.component.scss' +}) +export class NavigationComponent { + @Input() navItems: NavItem[] = NAV_ITEMS; + + translateService = inject(TranslateService); +} diff --git a/marketplace-ui/src/app/shared/components/header/search-bar/search-bar.component.html b/marketplace-ui/src/app/shared/components/header/search-bar/search-bar.component.html new file mode 100644 index 000000000..e7c2fd853 --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/search-bar/search-bar.component.html @@ -0,0 +1,81 @@ + + @if (isSearchBarDisplayed()) { + + + + + + + + + + + + + + + + + } @else { + + + + + + + + + + + + + + + @if (isSearchBarDisplayed()) { + + + + + + + + + } @else { + + + + } + + + + + + {{ 'common.header.download' | translate }} + + + + + + } + diff --git a/marketplace-ui/src/app/shared/components/header/search-bar/search-bar.component.scss b/marketplace-ui/src/app/shared/components/header/search-bar/search-bar.component.scss new file mode 100644 index 000000000..e5650f91d --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/search-bar/search-bar.component.scss @@ -0,0 +1,33 @@ +.search-icon { + width: 22px; + height: 22px; + margin: 0 16px 0 16px; +} + +.bi-search { + font-size: 16.5px; +} + +.input-group { + border-radius: 10px; +} + +.header__search-input { + height: 37px; +} + +.header__download-button { + height: 35px; + border-radius: 7px; +} + +@media all and (max-width: 992px) { + .header__button { + padding: 10px 0; + margin: 20px 0 0 0; + } + + .border-top-md-gray { + border-top: 1px solid var(--header-border-color); + } +} diff --git a/marketplace-ui/src/app/shared/components/header/search-bar/search-bar.component.spec.ts b/marketplace-ui/src/app/shared/components/header/search-bar/search-bar.component.spec.ts new file mode 100644 index 000000000..11c8cb238 --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/search-bar/search-bar.component.spec.ts @@ -0,0 +1,74 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { SearchBarComponent } from './search-bar.component'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { Viewport } from 'karma-viewport/dist/adapter/viewport'; + +declare const viewport: Viewport; + +describe('SearchBarComponent', () => { + let component: SearchBarComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SearchBarComponent, TranslateModule.forRoot()], + providers: [TranslateService] + }).compileComponents(); + + fixture = TestBed.createComponent(SearchBarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should toggle the search input visibility on search icon click', () => { + viewport.set(1920); + const searchIcon = fixture.debugElement.query( + By.css('.header__search-button') + ); + + expect(component.isSearchBarDisplayed()).toBeFalse(); + + // Click the search icon + searchIcon.triggerEventHandler('click', null); + fixture.detectChanges(); + + expect(component.isSearchBarDisplayed()).toBeTrue(); + + const cancelIcon = fixture.debugElement.query( + By.css('.input-group-prepend.search__cancel-button') + ); + + // Click the cancel icon + cancelIcon.triggerEventHandler('click', null); + fixture.detectChanges(); + + expect(component.isSearchBarDisplayed()).toBeFalse(); + }); + + it('destop search should not display in small screen', () => { + viewport.set(540); + + const desktopSearch = fixture.debugElement.query( + By.css('.header__search-button') + ); + + expect(desktopSearch).toBeNull; + }); + + it('desktop search should display in large screen', () => { + viewport.set(1920); + + const desktopSearch = fixture.debugElement.query( + By.css('.header__search-button') + ); + + expect(getComputedStyle(desktopSearch.nativeElement).display).not.toBe( + 'none' + ); + }); +}); diff --git a/marketplace-ui/src/app/shared/components/header/search-bar/search-bar.component.ts b/marketplace-ui/src/app/shared/components/header/search-bar/search-bar.component.ts new file mode 100644 index 000000000..2565dbb1a --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/search-bar/search-bar.component.ts @@ -0,0 +1,50 @@ +import { CommonModule } from '@angular/common'; +import { + Component, + ElementRef, + EventEmitter, + HostListener, + Input, + Output, + inject, + signal +} from '@angular/core'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { LanguageSelectionComponent } from '../language-selection/language-selection.component'; +import { ThemeSelectionComponent } from '../theme-selection/theme-selection.component'; + +@Component({ + selector: 'app-search-bar', + standalone: true, + imports: [ + CommonModule, + TranslateModule, + ThemeSelectionComponent, + LanguageSelectionComponent + ], + templateUrl: './search-bar.component.html', + styleUrl: './search-bar.component.scss' +}) +export class SearchBarComponent { + @Input() isSearchBarDisplayed = signal(false); + @Output() isShowSearchBarChange = new EventEmitter(); + + translateService = inject(TranslateService); + + elementRef = inject(ElementRef); + + @HostListener('document:click', ['$event']) + handleClickOutside(event: MouseEvent) { + if (!this.elementRef.nativeElement.contains(event.target)) { + this.isSearchBarDisplayed.set(false); + } + } + + onClickSearchIcon() { + this.isSearchBarDisplayed.set(true); + } + + onHideSearch() { + this.isSearchBarDisplayed.set(false); + } +} diff --git a/marketplace-ui/src/app/shared/components/header/theme-selection/theme-selection.component.html b/marketplace-ui/src/app/shared/components/header/theme-selection/theme-selection.component.html new file mode 100644 index 000000000..f5f1e5e3e --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/theme-selection/theme-selection.component.html @@ -0,0 +1,9 @@ + + + @if (themeService.isDarkMode()) { + + } @else { + + } + + diff --git a/marketplace-ui/src/app/shared/components/header/theme-selection/theme-selection.component.scss b/marketplace-ui/src/app/shared/components/header/theme-selection/theme-selection.component.scss new file mode 100644 index 000000000..4db4a38be --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/theme-selection/theme-selection.component.scss @@ -0,0 +1,10 @@ +.header__theme-button { + height: 22px; + width: 22px; + margin: 0 0 0 16px; + text-align: center; +} + +i { + font-size: 16.5px; +} diff --git a/marketplace-ui/src/app/shared/components/header/theme-selection/theme-selection.component.spec.ts b/marketplace-ui/src/app/shared/components/header/theme-selection/theme-selection.component.spec.ts new file mode 100644 index 000000000..c0052ec79 --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/theme-selection/theme-selection.component.spec.ts @@ -0,0 +1,37 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { ThemeSelectionComponent } from './theme-selection.component'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; + +describe('ThemeSelectionComponent', () => { + let component: ThemeSelectionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ThemeSelectionComponent, TranslateModule.forRoot()], + providers: [TranslateService] + }).compileComponents(); + + fixture = TestBed.createComponent(ThemeSelectionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should toggle the theme on theme button click', () => { + spyOn(component.themeService, 'changeTheme').and.callThrough(); + const themeButton = fixture.debugElement.query( + By.css('.header__theme-button') + ); + + // Click the theme button + themeButton.triggerEventHandler('click', null); + fixture.detectChanges(); + + expect(component.themeService.changeTheme).toHaveBeenCalled(); + }); +}); diff --git a/marketplace-ui/src/app/shared/components/header/theme-selection/theme-selection.component.ts b/marketplace-ui/src/app/shared/components/header/theme-selection/theme-selection.component.ts new file mode 100644 index 000000000..aca45df49 --- /dev/null +++ b/marketplace-ui/src/app/shared/components/header/theme-selection/theme-selection.component.ts @@ -0,0 +1,13 @@ +import { Component, inject } from '@angular/core'; +import { ThemeService } from '../../../../core/services/theme/theme.service'; + +@Component({ + selector: 'app-theme-selection', + standalone: true, + imports: [], + templateUrl: './theme-selection.component.html', + styleUrl: './theme-selection.component.scss' +}) +export class ThemeSelectionComponent { + themeService = inject(ThemeService); +} diff --git a/marketplace-ui/src/app/shared/constants/common.constant.ts b/marketplace-ui/src/app/shared/constants/common.constant.ts new file mode 100644 index 000000000..968d80da2 --- /dev/null +++ b/marketplace-ui/src/app/shared/constants/common.constant.ts @@ -0,0 +1,110 @@ +import { TypeOption } from '../enums/type-option.enum'; +import { Language } from '../enums/language.enum'; +import { SortOption } from '../enums/sort-option.enum'; +import { NavItem } from '../models/nav-item.model'; + +export const NAV_ITEMS: NavItem[] = [ + { + label: 'common.nav.news', + link: 'https://developer.axonivy.com/news' + }, + { + label: 'common.nav.doc', + link: 'https://developer.axonivy.com/doc' + }, + { + label: 'common.nav.tutorial', + link: 'https://developer.axonivy.com/tutorial' + }, + { + label: 'common.nav.community', + link: 'https://community.axonivy.com/' + }, + { + label: 'common.nav.team', + link: 'https://developer.axonivy.com/team' + }, + { + label: 'common.nav.market', + link: '/' + } +]; + +export const SOCIAL_MEDIA_LINK = [ + { + styleClass: 'fab fa-linkedin', + url: '/' + }, + { + styleClass: 'fab fa-xing', + url: '/' + }, + { + styleClass: 'fab fa-youtube', + url: '/' + }, + { + styleClass: 'fab fa-facebook', + url: '/' + } +]; + +export const IVY_FOOTER_LINKS = [ + { + containerStyleClass: 'w-md-100 footer__ivy-tag', + label: 'common.footer.ivyCompanyInfo' + }, + { + containerStyleClass: 'footer__ivy-policy-tag', + label: 'common.footer.privacyPolicy' + }, + { + containerStyleClass: 'footer__ivy-term-of-service-tag', + label: 'common.footer.termsOfService' + } +]; + +export const LANGUAGES = [ + { + value: Language.DE, + label: 'DE' + }, + { + value: Language.EN, + label: 'EN' + } +]; + +export const FILTER_TYPES = [ + { + value: TypeOption.All_TYPES, + label: 'common.filter.value.allTypes' + }, + { + value: TypeOption.CONNECTORS, + label: 'common.filter.value.connector' + }, + { + value: TypeOption.UTILITIES, + label: 'common.filter.value.util' + }, + { + value: TypeOption.SOLUTION, + label: 'common.filter.value.solution' + } +]; + +export const SORT_TYPES = [ + { + value: SortOption.POPULARITY, + label: 'common.sort.value.popularity' + }, + { + value: SortOption.ALPHABETICALLY, + label: 'common.sort.value.alphabetically' + }, + { + value: SortOption.RECENT, + label: 'common.sort.value.recent' + } +]; diff --git a/marketplace-ui/src/app/shared/enums/language.enum.ts b/marketplace-ui/src/app/shared/enums/language.enum.ts new file mode 100644 index 000000000..7d38c08f1 --- /dev/null +++ b/marketplace-ui/src/app/shared/enums/language.enum.ts @@ -0,0 +1,4 @@ +export enum Language { + EN = 'en', + DE = 'de' +} diff --git a/marketplace-ui/src/app/shared/enums/request-param.ts b/marketplace-ui/src/app/shared/enums/request-param.ts new file mode 100644 index 000000000..207ea34f9 --- /dev/null +++ b/marketplace-ui/src/app/shared/enums/request-param.ts @@ -0,0 +1,6 @@ +export enum RequestParam { + TYPE = 'type', + KEYWORD = 'keyword', + SORT = 'sort', + LANGUAGE = 'language' +} \ No newline at end of file diff --git a/marketplace-ui/src/app/shared/enums/sort-option.enum.ts b/marketplace-ui/src/app/shared/enums/sort-option.enum.ts new file mode 100644 index 000000000..d2b3e179f --- /dev/null +++ b/marketplace-ui/src/app/shared/enums/sort-option.enum.ts @@ -0,0 +1,5 @@ +export enum SortOption { + POPULARITY = 'popularity', + ALPHABETICALLY = 'alphabetically', + RECENT = 'recent' +} diff --git a/marketplace-ui/src/app/shared/enums/theme.enum.ts b/marketplace-ui/src/app/shared/enums/theme.enum.ts new file mode 100644 index 000000000..6d4c78a96 --- /dev/null +++ b/marketplace-ui/src/app/shared/enums/theme.enum.ts @@ -0,0 +1,4 @@ +export enum Theme { + LIGHT = 'light', + DARK = 'dark', +} diff --git a/marketplace-ui/src/app/shared/enums/type-option.enum.ts b/marketplace-ui/src/app/shared/enums/type-option.enum.ts new file mode 100644 index 000000000..beb0ecf6b --- /dev/null +++ b/marketplace-ui/src/app/shared/enums/type-option.enum.ts @@ -0,0 +1,7 @@ +export enum TypeOption { + All_TYPES = 'all', + CONNECTORS = 'connectors', + UTILITIES = 'utilities', + DEMOS = 'demos', + SOLUTION = 'solutions' +} diff --git a/marketplace-ui/src/app/shared/mocks/mock-data.ts b/marketplace-ui/src/app/shared/mocks/mock-data.ts new file mode 100644 index 000000000..e8e43065d --- /dev/null +++ b/marketplace-ui/src/app/shared/mocks/mock-data.ts @@ -0,0 +1,209 @@ +import { ProductApiResponse } from "../models/apis/product-response.model"; + +export const MOCK_PRODUCTS = { + _embedded: { + products: [ + { + id: "amazon-comprehend", + names: { + en: "Amazon Comprehend", + de: "TODO Amazon Comprehend" + }, + shortDescriptions: { + en: "Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data.", + de: "Amazon Comprehend ist ein KI-Service, der maschinelles Lernen nutzt, um aus unstrukturierten Daten wertvolle Informationen zu generieren." + }, + logoUrl: "https://raw.githubusercontent.com/axonivy-market/market/master/market/connector/amazon-comprehend/logo.png", + type: "connector", + tags: [ + "AI" + ], + _links: { + self: { + href: "http://localhost:8080/marketplace-service/api/product-details/amazon-comprehend?type=connector" + } + } + } + ] + }, + _links: { + first: { + href: "http://localhost:8080/marketplace-service/api/product?type=all&page=0&size=20" + }, + self: { + href: "http://localhost:8080/marketplace-service/api/product?type=all&page=0&size=20" + }, + next: { + href: "http://localhost:8080/marketplace-service/api/product?type=all&page=1&size=20" + }, + last: { + href: "http://localhost:8080/marketplace-service/api/product?type=all&page=3&size=20" + } + }, + page: { + size: 20, + totalElements: 70, + totalPages: 4, + number: 0 + } +} as ProductApiResponse; + +export const MOCK_EMPTY_DE_VALUES_AND_NO_LOGO_URL_PRODUCTS = { + _embedded: { + products: [ + { + id: "amazon-comprehend", + names: { + en: "Amazon Comprehend", + de: "" + }, + shortDescriptions: { + en: "Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data.", + de: "" + }, + logoUrl: "", + type: "connector", + tags: [ + "AI" + ], + _links: { + self: { + href: "http://localhost:8080/marketplace-service/api/product-details/amazon-comprehend?type=connector" + } + } + } + ] + }, + _links: { + first: { + href: "http://localhost:8080/marketplace-service/api/product?type=all&page=0&size=20" + }, + self: { + href: "http://localhost:8080/marketplace-service/api/product?type=all&page=0&size=20" + }, + next: { + href: "http://localhost:8080/marketplace-service/api/product?type=all&page=1&size=20" + }, + last: { + href: "http://localhost:8080/marketplace-service/api/product?type=all&page=3&size=20" + } + }, + page: { + size: 20, + totalElements: 70, + totalPages: 4, + number: 0 + } +} as ProductApiResponse; + +export const MOCK_PRODUCTS_FILTER_CONNECTOR = { + _embedded: { + products: [ + { + id: "amazon-comprehend", + names: { + en: "Amazon Comprehend", + de: "TODO Amazon Comprehend" + }, + shortDescriptions: { + en: "Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data.", + de: "Amazon Comprehend ist ein KI-Service, der maschinelles Lernen nutzt, um aus unstrukturierten Daten wertvolle Informationen zu generieren." + }, + logoUrl: "https://raw.githubusercontent.com/axonivy-market/market/master/market/connector/amazon-comprehend/logo.png", + type: "connector", + tags: [ + "AI" + ], + _links: { + self: { + href: "http://localhost:8080/marketplace-service/api/product-details/amazon-comprehend?type=connector" + } + } + }, + { + id: "a-trust", + names: { + en: "A-Trust", + de: "A-Trust" + }, + shortDescriptions: { + en: "Clearly authenticate your Austrian customers with a mobile phone signature.", + de: "Clearly authenticate your Austrian customers with a mobile phone signature." + }, + logoUrl: "https://raw.githubusercontent.com/axonivy-market/market/master/market/connector/a-trust/logo.png", + type: "connector", + tags: [ + "e-signature" + ], + _links: { + self: { + href: "http://localhost:8080/marketplace-service/api/product-details/a-trust?type=connector" + } + } + }, + { + id: "mailstore-connector", + names: { + en: "Mailstore", + de: "Mailstore" + }, + shortDescriptions: { + en: "Enhance business processes by streamlining email management, supporting both IMAP and POP3 with robust SSL encryption.", + de: "Enhance business processes by streamlining email management, supporting both IMAP and POP3 with robust SSL encryption." + }, + logoUrl: "https://raw.githubusercontent.com/axonivy-market/market/master/market/connector/mailstore-connector/logo.png", + type: "connector", + tags: [ + "office", + "email" + ], + _links: { + self: { + href: "http://localhost:8080/marketplace-service/api/product-details/mailstore-connector?type=connector" + } + } + } + ] + }, + _links: { + first: { + href: "http://localhost:8080/marketplace-service/api/product?type=all&page=0&size=20" + }, + self: { + href: "http://localhost:8080/marketplace-service/api/product?type=all&page=0&size=20" + }, + next: { + href: "http://localhost:8080/marketplace-service/api/product?type=all&page=1&size=20" + }, + last: { + href: "http://localhost:8080/marketplace-service/api/product?type=all&page=3&size=20" + } + }, + page: { + size: 20, + totalElements: 70, + totalPages: 4, + number: 0 + } +} as ProductApiResponse; + + +export const MOCK_PRODUCTS_NEXT_PAGE = { + _embedded: { + products: [] + }, + _links: { + first: { + href: "http://localhost:8080/marketplace-service/api/product?type=all&page=0&size=20" + }, + self: { + href: "http://localhost:8080/marketplace-service/api/product?type=all&page=1&size=20" + } + }, + page: { + size: 20, + totalElements: 1, + totalPages: 1, + number: 1 + } +} as ProductApiResponse; \ No newline at end of file diff --git a/marketplace-ui/src/app/shared/mocks/mock-services.ts b/marketplace-ui/src/app/shared/mocks/mock-services.ts new file mode 100644 index 000000000..e7640bdfb --- /dev/null +++ b/marketplace-ui/src/app/shared/mocks/mock-services.ts @@ -0,0 +1,24 @@ +import { Observable, of } from 'rxjs'; +import { Product } from '../models/product.model'; +import { Criteria } from '../models/criteria.model'; +import { TypeOption } from '../enums/type-option.enum'; +import { MOCK_PRODUCTS, MOCK_PRODUCTS_FILTER_CONNECTOR, MOCK_PRODUCTS_NEXT_PAGE } from './mock-data'; +import { ProductApiResponse } from '../models/apis/product-response.model'; + +const products = MOCK_PRODUCTS._embedded.products as Product[]; +export class MockProductService { + + getProductById(id: string) { + return of(products.find(product => product.id === id)); + } + + findProductsByCriteria(criteria: Criteria): Observable { + let response = MOCK_PRODUCTS; + if (criteria.nextPageHref) { + response = MOCK_PRODUCTS_NEXT_PAGE; + } else if (criteria.type == TypeOption.CONNECTORS) { + response = MOCK_PRODUCTS_FILTER_CONNECTOR; + } + return of(response); + } +} diff --git a/marketplace-ui/src/app/shared/models/apis/link.model.ts b/marketplace-ui/src/app/shared/models/apis/link.model.ts new file mode 100644 index 000000000..057f5af38 --- /dev/null +++ b/marketplace-ui/src/app/shared/models/apis/link.model.ts @@ -0,0 +1,14 @@ +export interface Link { + self: { + href: string; + }; + first?: { + href: string; + }; + next?: { + href: string; + }; + last?: { + href: string; + }; +} \ No newline at end of file diff --git a/marketplace-ui/src/app/shared/models/apis/page.model.ts b/marketplace-ui/src/app/shared/models/apis/page.model.ts new file mode 100644 index 000000000..15b585275 --- /dev/null +++ b/marketplace-ui/src/app/shared/models/apis/page.model.ts @@ -0,0 +1,6 @@ +export interface Page { + size: number; + totalElements: number; + totalPages: number; + number: number; +} \ No newline at end of file diff --git a/marketplace-ui/src/app/shared/models/apis/product-response.model.ts b/marketplace-ui/src/app/shared/models/apis/product-response.model.ts new file mode 100644 index 000000000..d9020d593 --- /dev/null +++ b/marketplace-ui/src/app/shared/models/apis/product-response.model.ts @@ -0,0 +1,11 @@ +import { Product } from "../product.model"; +import { Link } from "./link.model"; +import { Page } from "./page.model"; + +export interface ProductApiResponse { + _embedded: { + products: Product[]; + }; + _links: Link; + page: Page; +} \ No newline at end of file diff --git a/marketplace-ui/src/app/shared/models/criteria.model.ts b/marketplace-ui/src/app/shared/models/criteria.model.ts new file mode 100644 index 000000000..fce18f832 --- /dev/null +++ b/marketplace-ui/src/app/shared/models/criteria.model.ts @@ -0,0 +1,10 @@ +import { Language } from "../enums/language.enum"; +import { SortOption } from "../enums/sort-option.enum"; +import { TypeOption } from "../enums/type-option.enum"; +export interface Criteria { + search: string; + sort: SortOption | null; + type: TypeOption | null; + language: Language; + nextPageHref?: string; +} diff --git a/marketplace-ui/src/app/shared/models/display-value.model.ts b/marketplace-ui/src/app/shared/models/display-value.model.ts new file mode 100644 index 000000000..84c9d4c2a --- /dev/null +++ b/marketplace-ui/src/app/shared/models/display-value.model.ts @@ -0,0 +1,5 @@ +export interface DisplayValue { + en: string, + de: string +} + diff --git a/marketplace-ui/src/app/shared/models/maven-artifact.model.ts b/marketplace-ui/src/app/shared/models/maven-artifact.model.ts new file mode 100644 index 000000000..89906806f --- /dev/null +++ b/marketplace-ui/src/app/shared/models/maven-artifact.model.ts @@ -0,0 +1,16 @@ +export interface ArchivedArtifact { + lastVersion: string; + groupId: string; + artifactId: string; +} + +export interface MavenArtifact { + repoUrl?: string; + key?: string; + name: string; + groupId: string; + artifactId: string; + archivedArtifacts?: ArchivedArtifact[]; + type?: string; + doc?: boolean; +} diff --git a/marketplace-ui/src/app/shared/models/nav-item.model.ts b/marketplace-ui/src/app/shared/models/nav-item.model.ts new file mode 100644 index 000000000..596a7c8e7 --- /dev/null +++ b/marketplace-ui/src/app/shared/models/nav-item.model.ts @@ -0,0 +1,4 @@ +export interface NavItem { + label: string; + link: string; +} diff --git a/marketplace-ui/src/app/shared/models/product.model.ts b/marketplace-ui/src/app/shared/models/product.model.ts new file mode 100644 index 000000000..fe73c9b7f --- /dev/null +++ b/marketplace-ui/src/app/shared/models/product.model.ts @@ -0,0 +1,33 @@ +import { DisplayValue } from './display-value.model'; +import { MavenArtifact } from './maven-artifact.model'; + +export interface Product { + id: string; + version: string; + names: DisplayValue; + shortDescriptions: DisplayValue; + type: string; + logoUrl: string; + cost: string; + platformReview: string; + vendor: string; + vendorImage: string; + vendorUrl: string; + sourceUrl: string; + statusBadgeUrl: string; + language: string; + industry: string; + listed: boolean; + compatibility: string; + tags: string[]; + validate: boolean; + versionDisplay: string; + installMatcher: string; + mavenArtifacts: MavenArtifact[]; + contactUs: boolean; + _links?: { + self: { + href: string; + }; + }; +} diff --git a/marketplace-ui/src/app/shared/models/vesion-artifact.model.ts b/marketplace-ui/src/app/shared/models/vesion-artifact.model.ts new file mode 100644 index 000000000..ab3f74b25 --- /dev/null +++ b/marketplace-ui/src/app/shared/models/vesion-artifact.model.ts @@ -0,0 +1,10 @@ +export interface Artifact { + name: string; + downloadUrl: string; + isProductArtifact: boolean | null; +} + +export interface VersionData { + version: string; + artifactsByVersion: Artifact[]; +} \ No newline at end of file diff --git a/marketplace-ui/src/app/shared/pipes/logo.pipe.ts b/marketplace-ui/src/app/shared/pipes/logo.pipe.ts new file mode 100644 index 000000000..93554c3e6 --- /dev/null +++ b/marketplace-ui/src/app/shared/pipes/logo.pipe.ts @@ -0,0 +1,16 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { Product } from '../models/product.model'; + +@Pipe({ + standalone: true, + name: 'logo' +}) +export class ProductLogoPipe implements PipeTransform { + transform(product: Product, _args?: []): string { + let logoUrl = product.logoUrl; + if (logoUrl === undefined || logoUrl === '') { + logoUrl = `/assets/images/misc/axonivy-logo-round.png`; + } + return logoUrl; + } +} diff --git a/marketplace-ui/src/app/shared/pipes/multilingualism.pipe.ts b/marketplace-ui/src/app/shared/pipes/multilingualism.pipe.ts new file mode 100644 index 000000000..154c344fd --- /dev/null +++ b/marketplace-ui/src/app/shared/pipes/multilingualism.pipe.ts @@ -0,0 +1,21 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { Language } from '../enums/language.enum'; +import { DisplayValue } from '../models/display-value.model'; + +@Pipe({ + standalone: true, + name: 'multilingualism' +}) +export class MultilingualismPipe implements PipeTransform { + transform(value: DisplayValue, language: Language, _args?: []): string { + let displayValue = ''; + if (value !== undefined) { + displayValue = value[language]; + if (displayValue === undefined || displayValue === '') { + displayValue = value[Language.EN]; + } + } + + return displayValue; + } +} diff --git a/marketplace-ui/src/assets/.gitkeep b/marketplace-ui/src/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/marketplace-ui/src/assets/fonts/inter.ttf b/marketplace-ui/src/assets/fonts/inter.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e72470871b8fc198da424b1e17ed729c202829cf GIT binary patch literal 804612 zcmd?S4_stb-T42xcP?}9aA#q5cXnZShuzr~S5#D7QBiS4#T7SFa?wyXWfc__m3C3l z$VMZ>A|u_5jEs!OXd|U0BPB&eqaq_C8;ul=jC>{|!);v0x!?D>GwdJpv3$Ng-{0%^ zdbqsLIrscOpYu8Ao_p_^8PP;!F@_;Ur6UGc#0%gq z$F?qQxc-vQK6*y6c)rjjdfwVgiT~pZR{l*g?i&$r=lc4Kx6~c-#Fh=j?;!qB^?0m& zW7(seH*lU`e`Qni+fP(Zh@8?Ta==d;uD)oC?<3w~kt^#tAHQ--b7Q7o^Af+3_~EOz zTzT=SxmT=L!l3wMNh^esc4QP z+1TRyn`{@qG>WOt_WH|Eu|zU^->wnKi~N0RZZ35BJK~7%b=SvZ{O-pmcYXeX?9w-6 zh3#fC`TD~vl;d}2RXzLnsYFZYa=$wDik{BHFb_ov8B}Yn_bx(ym9jn0%dm%YT{1lQ z>S9cZ?ktk8JDj;SJaf|rH_5Ps9Vb<{%wD~hpsx{5_Lj23v`E$7BE3jZRF(2YzwXld zCM{cJTfmFa)aC=x8u*BwovMn<+>Nk=x+BCtq`|cH!Vl? zqdv4dY?(H<+r2_ZZt3)Sd&ODh3Gen_?P+!0l%nN2J8^qRp=&v~#63^t5TG48?N}s! z)dp2FRkE8}v~1O$bZnK7KP^E0Q33Zvv_&bmilZ&Yty;sM7R12q`Y+*JwTwS)Y0B;T zQx{iTm49x^?fNgnty-)6m#5sWKfUg@T=|DmZr49AEh*){GUaytSJ93zp8RRyl-u=R zJ>Ne3*QDIapTBkb`ASX(<5)VpfwWtbOs_ZX)+H+=o_2fW(2Sp^-G(gB=uf*d}gE97wxGVUA@$E zZpvTNB;;A2cI$GiCta52kq!DIX@Bahr}NY@WRZS*+TSYy{pPgW#6Mk@W=Wx5p7!@i zz8+4yGi9CjTH0;Pq1tfT?U#@?pZ@|W&|1>|S+Y_~%b79b*Un7)2P9LgNV^w`rIn@K zi)5*GP};p%g6NRjN9foZnM=EuNT$4+b}yA&c_!`7+0$R>m|vbs`!B=&P};p*mP>cq zy@ENnBkc~!O4*ur=P}|hPrFw#x~tRfRg9dI((bT?sZpwKtEo?J+Pwys5UWA9$VGAm zu}+gF+zoOW=X?B5k!Jk25^|DUO=#UaV*X?JyT_aN-6R*wm6S1mzHhnj4E-Rn>xg{~ zY4(k|cgWr~uO#-RQcs=tjlai#PmTBZ?Hl*qVpNmoWz3oT#+>)vu=k>U@5qb^rH@JB>k%LO#NJtZP zQTbdfXK~($y_L|5$YmSuFgfg-j|#s`E|C+-smYCf6>*hIm8n9n^|mXl%|wYZWRE16&8w(2s{sS@TTsamA8VVf(*ZOATF{~O#os~!m>J0)q= zvTdBHnkiX_@l&ak?eo(PE_us?I7#YV=-AETsvmmr}J? zeVo!86?YS3UG>kNI_}9?jfsm$o64b(SZ8ylT01Y-!>OSnyjRWkEqA^)TaaiZ);u6(Y+3{#%!%~XrFx$>Nk zf0`@Xl%&o=LaJ7&p5HqpC1+Ll)U2rTQln?9JBO-XQCc%!gFPt^p&l2zI;v*et^Xj^ zBdBi?ay>?lOKZy^oNtjM@hg&}NLz#q_v9CTM}B)kjwSC6$YLES){*+ycgbhpy6j1N zG7@QIeo|UFKZ~aFDWY9U6P5n#i653K;$F>F=wh@+Y37N@SKkS-L^VLu&f1+^r%KDG(ZqH(%d8W@PB`__s9d;Pq}pk961Z-ZTE|_}I;}FU zOEQs6IGxhshz!Xk&H#Bu++3n0BR(l6r6`$@7TMv%IAwk8#8`EPvB^i}skx@>-- z6}mX3$ycI5Y>wcRrdo7RXq4D0zLJzG-6PM+UCu$hHS)aN=d9gvw)ARor{?kXve1pU zN2X2E=``h9$!6)4xuBKYA^ln~YufQ^+hsPMi{0t8BrJz@q2@Q@*sYR?TTY?YCVh#7 zQ>4{O!Z4gNtxOZnm0g2fu9Y~_(nA02a;}YMISukMcBa!LGj4t@GKpg{%?X{4)EKI2>)>k7(}bg1sO&7h9P_C$hPmqcgWkT&V<3QXgf+ilFrfR^7K zoRUG!P-hX%>NVnISKBuml893~fVEwdL2p7@wcH{9th8&!kjlkr88uX9J<4j8u4vpZ zF{ddSpO&~Jq6ncy`lb>|itV1VEHbO#IT@1<+HEAbeO3*i<7ouS>eOmgCCH^-;!PHo z=5O}LokmUS199@1t0AmK60W6HW05G8!MwKLEB)ISO*{EoOS`3#OtvP}?X2qHl$bnp zUHwR@3^Fe33zasJ8K`Y>dd&3E8-e=5&Wvb7b&DX3x%dlj$FIZgS zg!^&{E$_=ME!8Uf^DWf8zk(XJ4>VWOA%mNBLzAJoL;{nhYyP1Y3v;;IAJcM&YcQ?h zS`?~oxPCS)iQ#&~(5jUwiIFNJk(?O0r1UP$Khk2&YPF;JMH5>6XhkgM)QxT?etfh} z#T-?sjM1i@aV<33WX)-tm0PApn`0qKj#
+ {{ + product.shortDescriptions + | multilingualism: languageService.getSelectedLanguage() + }} +
+ {{ translateService.get('common.filter.label') | async }} +
+ {{ type.label | translate }} +