From c635686dc4bded6fabf5e8ff67c0d6604792d140 Mon Sep 17 00:00:00 2001 From: Stefan Wilke Date: Tue, 21 Nov 2023 08:56:18 +0100 Subject: [PATCH 1/2] add integration tests --- .github/workflows/ci.yml | 6 +- backend/pom.xml | 16 ++ .../binpastes/paste/api/PasteController.java | 45 ++++-- .../business/tracking/TrackingService.java | 7 +- .../github/binpastes/paste/domain/Paste.java | 23 +-- .../binpastes/paste/domain/PasteService.java | 8 +- .../com/github/binpastes/BinPastesTests.java | 12 -- .../binpastes/paste/api/OneTimePastesIT.java | 122 ++++++++++++++++ .../paste/api/PasteControllerTest.java | 15 +- .../binpastes/paste/api/PublicPastesIT.java | 124 ++++++++++++++++ .../binpastes/paste/api/SearchPastesIT.java | 74 ++++++++++ .../binpastes/paste/api/TrackingIT.java | 125 ++++++++++++++++ .../binpastes/paste/api/UnlistedPastesIT.java | 137 ++++++++++++++++++ 13 files changed, 655 insertions(+), 59 deletions(-) delete mode 100644 backend/src/test/java/com/github/binpastes/BinPastesTests.java create mode 100644 backend/src/test/java/com/github/binpastes/paste/api/OneTimePastesIT.java create mode 100644 backend/src/test/java/com/github/binpastes/paste/api/PublicPastesIT.java create mode 100644 backend/src/test/java/com/github/binpastes/paste/api/SearchPastesIT.java create mode 100644 backend/src/test/java/com/github/binpastes/paste/api/TrackingIT.java create mode 100644 backend/src/test/java/com/github/binpastes/paste/api/UnlistedPastesIT.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0353c8a..352b752 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,11 @@ jobs: java-version: '17' - name: Build with Maven - run: mvn clean package + run: mvn clean verify + + - name: Publish Test Report + if: success() || failure() + uses: scacap/action-surefire-report@v1 release: runs-on: ubuntu-latest diff --git a/backend/pom.xml b/backend/pom.xml index fd5f17a..204856b 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -79,6 +79,10 @@ h2 test + + org.awaitility + awaitility + @@ -107,6 +111,18 @@ + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + org.apache.maven.plugins maven-deploy-plugin diff --git a/backend/src/main/java/com/github/binpastes/paste/api/PasteController.java b/backend/src/main/java/com/github/binpastes/paste/api/PasteController.java index b094fe7..554540b 100644 --- a/backend/src/main/java/com/github/binpastes/paste/api/PasteController.java +++ b/backend/src/main/java/com/github/binpastes/paste/api/PasteController.java @@ -14,24 +14,35 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; +import org.springframework.http.CacheControl; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.support.WebExchangeBindException; import org.springframework.web.server.ResponseStatusException; import reactor.core.publisher.Mono; import java.time.Duration; import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.TimeUnit; import static com.github.binpastes.paste.api.model.ListView.ListItemView; @Validated @RestController -@RequestMapping("/api/v1/paste") +@RequestMapping({"/api/v1/paste", "/api/v1/paste/"}) class PasteController { private static final Logger log = LoggerFactory.getLogger(PasteController.class); @@ -49,23 +60,26 @@ public Mono findPaste(@PathVariable("pasteId") String pasteId, Serve .find(pasteId) .doOnNext(paste -> { if (paste.isOneTime()) { - response.getHeaders().add(HttpHeaders.CACHE_CONTROL, "no-store"); - } else { - if (!paste.isPermanent()) { - var in5min = LocalDateTime.now().plusMinutes(5); - if (in5min.isAfter(paste.getDateOfExpiry())) { - response.getHeaders().add(HttpHeaders.CACHE_CONTROL, "max-age=" + Duration.between(LocalDateTime.now(), paste.getDateOfExpiry()).toSeconds()); - return; - } - } - - response.getHeaders().add(HttpHeaders.CACHE_CONTROL, "max-age=300"); + response.getHeaders().setCacheControl(CacheControl.noStore()); + return; } + + if (paste.isPermanent() || isAfter(paste.getDateOfExpiry(), 5, ChronoUnit.MINUTES)) { + response.getHeaders().setCacheControl(CacheControl.maxAge(5, TimeUnit.MINUTES)); + return; + } + + response.getHeaders().setCacheControl( + CacheControl.maxAge(Duration.between(LocalDateTime.now(), paste.getDateOfExpiry()))); }) .map(reference -> SingleView.of(reference, remoteAddress(request))) .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND))); } + private static boolean isAfter(LocalDateTime dateTime, long amount, ChronoUnit unit) { + return LocalDateTime.now().plus(amount, unit).isBefore(dateTime); + } + @GetMapping public Mono findPastes() { return pasteService @@ -83,7 +97,7 @@ public Mono searchPastes( final String term, final ServerHttpResponse response ) { - response.getHeaders().add(HttpHeaders.CACHE_CONTROL, "max-age=60"); + response.getHeaders().setCacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS)); return pasteService .findByFullText(term) .map(paste -> SearchItemView.of(paste, term)) @@ -129,5 +143,4 @@ private static String remoteAddress(ServerHttpRequest request) { return request.getRemoteAddress().getAddress().getHostAddress(); } - } diff --git a/backend/src/main/java/com/github/binpastes/paste/business/tracking/TrackingService.java b/backend/src/main/java/com/github/binpastes/paste/business/tracking/TrackingService.java index d71240f..1382fac 100644 --- a/backend/src/main/java/com/github/binpastes/paste/business/tracking/TrackingService.java +++ b/backend/src/main/java/com/github/binpastes/paste/business/tracking/TrackingService.java @@ -1,6 +1,5 @@ package com.github.binpastes.paste.business.tracking; -import com.github.binpastes.paste.business.tracking.MessagingClient.Message; import com.github.binpastes.paste.domain.PasteRepository; import jakarta.annotation.PostConstruct; import org.slf4j.Logger; @@ -35,7 +34,7 @@ public TrackingService( private void run() { this.messagingClient .receiveMessage() - .doOnNext(this::receiveView) + .doOnNext(message -> receiveView(message.pasteId(), message.timeViewed())) .repeat() .subscribe(); } @@ -55,8 +54,4 @@ public void receiveView(String pasteId, Instant timeViewed) { .onErrorResume(OptimisticLockingFailureException.class, e -> Mono.empty()) .subscribe(); } - - private void receiveView(Message message) { - this.receiveView(message.pasteId(), message.timeViewed()); - } } diff --git a/backend/src/main/java/com/github/binpastes/paste/domain/Paste.java b/backend/src/main/java/com/github/binpastes/paste/domain/Paste.java index 48592db..1252b6a 100644 --- a/backend/src/main/java/com/github/binpastes/paste/domain/Paste.java +++ b/backend/src/main/java/com/github/binpastes/paste/domain/Paste.java @@ -11,6 +11,7 @@ import java.util.Objects; import static com.github.binpastes.paste.domain.Paste.PasteSchema; +import static java.util.Objects.isNull; @Table(PasteSchema.TABLE_NAME) public class Paste { @@ -119,19 +120,19 @@ public boolean isOneTime() { } public boolean isPermanent() { - return this.dateOfExpiry == null; + return isNull(this.dateOfExpiry); } public boolean isErasable(String remoteAddress) { - if (this.isUnlisted() || this.isOneTime()) { + if (isUnlisted() || isOneTime()) { return true; } - if (this.isPublic()) { - final var createdBySameAuthor = Objects.equals(remoteAddress, this.getRemoteAddress()); + if (isPublic()) { + final var createdBySameAuthor = Objects.equals(remoteAddress, getRemoteAddress()); if (createdBySameAuthor) { - return LocalDateTime.now().minusHours(1).isBefore(this.getDateCreated()); + return LocalDateTime.now().minusHours(1).isBefore(getDateCreated()); } } @@ -139,15 +140,19 @@ public boolean isErasable(String remoteAddress) { } public Paste trackView(LocalDateTime lastViewed) { - if (this.getLastViewed() == null || this.getLastViewed().isBefore(lastViewed)) { + if (isNull(getLastViewed()) || getLastViewed().isBefore(lastViewed)) { setLastViewed(lastViewed); } - return this.setViews(this.getViews() + 1); + return setViews(this.getViews() + 1); } public Paste markAsExpired() { - return this.setDateOfExpiry(LocalDateTime.now()); + if (isNull(getDateOfExpiry())) { + return setDateOfExpiry(LocalDateTime.now()); + } + + throw new IllegalStateException("Paste is already expired"); } protected Paste setId(final String id) { @@ -209,7 +214,7 @@ protected Paste setViews(final long views) { @Override public boolean equals(final Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (isNull(o) || getClass() != o.getClass()) return false; Paste paste = (Paste) o; return Objects.equals(id, paste.id); } diff --git a/backend/src/main/java/com/github/binpastes/paste/domain/PasteService.java b/backend/src/main/java/com/github/binpastes/paste/domain/PasteService.java index 0dd74f9..7ac8789 100644 --- a/backend/src/main/java/com/github/binpastes/paste/domain/PasteService.java +++ b/backend/src/main/java/com/github/binpastes/paste/domain/PasteService.java @@ -48,14 +48,14 @@ public Mono find(String id) { } private Mono trackAccess(Paste paste) { - trackingService.trackView(paste.getId()); - if (paste.isOneTime()) { return pasteRepository .save(paste.markAsExpired()) - .doOnSuccess(deletedPaste -> log.info("OneTime paste {} viewed and burnt", deletedPaste.getId())); + .doOnSuccess(deletedPaste -> log.info("OneTime paste {} viewed and burnt", deletedPaste.getId())) + .onErrorComplete(); } + trackingService.trackView(paste.getId()); return Mono.just(paste); } @@ -66,7 +66,7 @@ public Flux findAll() { public Flux findByFullText(String text) { return pasteRepository .searchAllLegitByFullText(text) - // TODO remove when fulltext search is finalised + // TODO remove when fulltext search is 'good enough' .collectList() .doOnSuccess(pastes -> log.info("Found {} pastes searching for: {}", pastes.size(), text)) .flatMapMany(Flux::fromIterable); diff --git a/backend/src/test/java/com/github/binpastes/BinPastesTests.java b/backend/src/test/java/com/github/binpastes/BinPastesTests.java deleted file mode 100644 index fa27f05..0000000 --- a/backend/src/test/java/com/github/binpastes/BinPastesTests.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.github.binpastes; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class BinPastesTests { - - @Test - void contextLoads() {} - -} diff --git a/backend/src/test/java/com/github/binpastes/paste/api/OneTimePastesIT.java b/backend/src/test/java/com/github/binpastes/paste/api/OneTimePastesIT.java new file mode 100644 index 0000000..89a9e11 --- /dev/null +++ b/backend/src/test/java/com/github/binpastes/paste/api/OneTimePastesIT.java @@ -0,0 +1,122 @@ +package com.github.binpastes.paste.api; + +import com.github.binpastes.paste.domain.Paste; +import com.github.binpastes.paste.domain.Paste.PasteExposure; +import com.github.binpastes.paste.domain.PasteRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.CacheControl; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.reactive.server.WebTestClient; + +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@AutoConfigureWebTestClient +@DirtiesContext +class OneTimePastesIT { + + @Autowired + private WebTestClient webClient; + + @Autowired + private PasteRepository pasteRepository; + + @BeforeEach + void setUp() { + pasteRepository.deleteAll().block(); + } + + @Test + @DisplayName("GET /{pasteId} - one-time paste is never cached") + void getOneTimePaste() { + var oneTimePaste = givenOneTimePaste(); + + webClient.get() + .uri("/api/v1/paste/" + oneTimePaste.getId()) + .exchange() + .expectStatus().isOk() + .expectHeader().cacheControl(CacheControl.noStore()); + } + + @Test + @DisplayName("GET /{pasteId} - one-time paste is read-once") + void getOneTimePasteTwice() { + var oneTimePaste = givenOneTimePaste(); + + webClient.get() + .uri("/api/v1/paste/" + oneTimePaste.getId()) + .exchange() + .expectStatus().isOk(); + + webClient.get() + .uri("/api/v1/paste/" + oneTimePaste.getId()) + .exchange() + .expectStatus().isNotFound(); + } + + @Test + @DisplayName("GET / - one-time paste is never listed") + void findAllPastes() { + givenOneTimePaste(); + + assertThat(pasteRepository.count().block()).isOne(); + webClient.get() + .uri("/api/v1/paste/") + .exchange() + .expectStatus().isOk() + .expectBody().jsonPath("pastes", emptyList()); + } + + @Test + @DisplayName("GET /search - one-time paste cannot be searched for") + void searchAllPastes() { + givenOneTimePaste(); + + assertThat(pasteRepository.count().block()).isOne(); + webClient.get() + .uri("/api/v1/paste/search?term={term}", "ipsum") + .exchange() + .expectStatus().isOk() + .expectBody().jsonPath("pastes", emptyList()); + } + + @Test + @DisplayName("DELETE /{pasteId} - one-time paste might always be deleted before reading") + void deleteOneTimePaste() { + var oneTimePaste = givenOneTimePaste(); + + webClient.delete() + .uri("/api/v1/paste/" + oneTimePaste.getId()) + .exchange() + .expectStatus().isNoContent() + .expectBody().isEmpty(); + + webClient.get() + .uri("/api/v1/paste/" + oneTimePaste.getId()) + .exchange() + .expectStatus().isNotFound(); + } + + private Paste givenOneTimePaste() { + return givenPaste( + Paste.newInstance( + "someTitle", + "Lorem ipsum dolor sit amet", + null, + false, + PasteExposure.ONCE, + "1.1.1.1" + ) + ); + } + + private Paste givenPaste(Paste paste) { + return pasteRepository.save(paste).block(); + } +} diff --git a/backend/src/test/java/com/github/binpastes/paste/api/PasteControllerTest.java b/backend/src/test/java/com/github/binpastes/paste/api/PasteControllerTest.java index dabed7b..e7c585a 100644 --- a/backend/src/test/java/com/github/binpastes/paste/api/PasteControllerTest.java +++ b/backend/src/test/java/com/github/binpastes/paste/api/PasteControllerTest.java @@ -10,7 +10,6 @@ import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.CacheControl; -import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; import reactor.core.publisher.Flux; @@ -45,7 +44,7 @@ void findUnknownPaste() { .uri("/api/v1/paste/" + somePasteId) .exchange() .expectStatus().isNotFound() - .expectHeader().doesNotExist(HttpHeaders.CACHE_CONTROL); + .expectHeader().cacheControl(CacheControl.empty()); } @Test @@ -69,7 +68,7 @@ void searchPastes() { .uri("/api/v1/paste/search?term={term}", "foobar") .exchange() .expectStatus().isOk() - .expectHeader().cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS)) + .expectHeader().cacheControl(CacheControl.maxAge(1, TimeUnit.MINUTES)) .expectBody().jsonPath("pastes", emptyList()); } @@ -92,20 +91,14 @@ void createPaste(Mono payload) { .contentType(MediaType.APPLICATION_JSON) .body(payload, String.class) .exchange() - .expectStatus().isBadRequest() - .expectBody().consumeWith(System.out::println); + .expectStatus().isBadRequest(); } private static Stream invalidPayloads() { return Stream.of( arguments(named("body is null", Mono.empty())), arguments(named("body blank", Mono.just(""))), - arguments(named("title too long", Mono.just(""" - { - "title": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", - "content": "validContent" - } - """))), + arguments(named("title too long", Mono.just("{\"content\": \"validContent\", \"title\": " + "X".repeat(256 + 1) + "\"}"))), arguments(named("content blank", Mono.just(""" { "content": " ", diff --git a/backend/src/test/java/com/github/binpastes/paste/api/PublicPastesIT.java b/backend/src/test/java/com/github/binpastes/paste/api/PublicPastesIT.java new file mode 100644 index 0000000..f6f10b3 --- /dev/null +++ b/backend/src/test/java/com/github/binpastes/paste/api/PublicPastesIT.java @@ -0,0 +1,124 @@ +package com.github.binpastes.paste.api; + +import com.github.binpastes.paste.domain.Paste; +import com.github.binpastes.paste.domain.Paste.PasteExposure; +import com.github.binpastes.paste.domain.PasteRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.CacheControl; +import org.springframework.http.HttpHeaders; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.reactive.server.WebTestClient; + +import java.time.LocalDateTime; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@AutoConfigureWebTestClient +@DirtiesContext +class PublicPastesIT { + + @Autowired + private WebTestClient webClient; + + @Autowired + private PasteRepository pasteRepository; + + @BeforeEach + void setUp() { + pasteRepository.deleteAll().block(); + } + + @Test + @DisplayName("GET /{pasteId} - public paste is cached") + void getPublicPaste() { + var paste = givenPublicPaste(); + + webClient.get() + .uri("/api/v1/paste/" + paste.getId()) + .exchange() + .expectStatus().isOk() + .expectHeader().cacheControl(CacheControl.maxAge(5, TimeUnit.MINUTES)); + } + + @Test + @DisplayName("GET /{pasteId} - public paste is cached until expiry") + void getExpiringPublicPaste() { + var paste = givenPaste(Paste.newInstance( + "someTitle", + "Lorem ipsum dolor sit amet", + LocalDateTime.now().plusMinutes(5).minusSeconds(1), // under 5min remaining + false, + PasteExposure.PUBLIC, + "1.1.1.1" + )); + + webClient.get() + .uri("/api/v1/paste/" + paste.getId()) + .exchange() + .expectStatus().isOk() + .expectHeader().value( + HttpHeaders.CACHE_CONTROL, + (value) -> assertThat(Long.valueOf(value.replace("max-age=", ""))) + .isLessThanOrEqualTo(TimeUnit.MINUTES.toSeconds(5))); + } + + @Test + @DisplayName("GET / - public paste is listed") + void findAllPastes() { + givenPublicPaste(); + + assertThat(pasteRepository.count().block()).isOne(); + webClient.get() + .uri("/api/v1/paste/") + .exchange() + .expectStatus().isOk() + .expectBody().jsonPath("$.pastes.length()", 1); + } + + @Test + @DisplayName("DELETE /{pasteId} - public paste might be deleted") + void deletePublicPaste() { + var paste = givenPublicPaste(); + + webClient.get() + .uri("/api/v1/paste/" + paste.getId()) + .exchange() + .expectStatus().isOk(); + + webClient.delete() + .uri("/api/v1/paste/" + paste.getId()) + .header("X-Forwarded-For", "someAuthor") + .exchange() + .expectStatus().isNoContent() + .expectBody().isEmpty(); + + webClient.get() + .uri("/api/v1/paste/" + paste.getId()) + .exchange() + .expectStatus().isNotFound(); + } + + private Paste givenPublicPaste() { + return givenPaste( + Paste.newInstance( + "someTitle", + "Lorem ipsum dolor sit amet", + null, + false, + PasteExposure.PUBLIC, + "someAuthor" + ) + ); + } + + private Paste givenPaste(Paste paste) { + return pasteRepository.save(paste).block(); + } +} diff --git a/backend/src/test/java/com/github/binpastes/paste/api/SearchPastesIT.java b/backend/src/test/java/com/github/binpastes/paste/api/SearchPastesIT.java new file mode 100644 index 0000000..bcfdfe5 --- /dev/null +++ b/backend/src/test/java/com/github/binpastes/paste/api/SearchPastesIT.java @@ -0,0 +1,74 @@ +package com.github.binpastes.paste.api; + +import com.github.binpastes.paste.domain.Paste; +import com.github.binpastes.paste.domain.PasteRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.reactive.server.WebTestClient; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@AutoConfigureWebTestClient +@DirtiesContext +class SearchPastesIT { + + @Autowired + private WebTestClient webClient; + + @Autowired + private PasteRepository pasteRepository; + + @BeforeEach + void setUp() { + pasteRepository.deleteAll().block(); + } + + @Test + @DisplayName("GET /search - paste is found if text matches") + void findIfMatch() { + givenPublicPaste(); + + assertThat(pasteRepository.count().block()).isOne(); + webClient.get() + .uri("/api/v1/paste/search?term={term}", "ipsum") + .exchange() + .expectStatus().isOk() + .expectBody().jsonPath("$.pastes.length()", 1); + } + + @Test + @DisplayName("GET /search - paste is not found if no match") + void findNothing() { + givenPublicPaste(); + + assertThat(pasteRepository.count().block()).isOne(); + webClient.get() + .uri("/api/v1/paste/search?term={term}", "foobar") + .exchange() + .expectStatus().isOk() + .expectBody().jsonPath("$.pastes.length()", 0); + } + + private Paste givenPublicPaste() { + return givenPaste( + Paste.newInstance( + "someTitle", + "Lorem ipsum dolor sit amet", + null, + false, + Paste.PasteExposure.PUBLIC, + "someAuthor" + ) + ); + } + + private Paste givenPaste(Paste paste) { + return pasteRepository.save(paste).block(); + } +} diff --git a/backend/src/test/java/com/github/binpastes/paste/api/TrackingIT.java b/backend/src/test/java/com/github/binpastes/paste/api/TrackingIT.java new file mode 100644 index 0000000..c91ed56 --- /dev/null +++ b/backend/src/test/java/com/github/binpastes/paste/api/TrackingIT.java @@ -0,0 +1,125 @@ +package com.github.binpastes.paste.api; + +import com.github.binpastes.paste.business.tracking.MessagingClient; +import com.github.binpastes.paste.business.tracking.TrackingService; +import com.github.binpastes.paste.domain.Paste; +import com.github.binpastes.paste.domain.Paste.PasteExposure; +import com.github.binpastes.paste.domain.PasteRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.reactive.server.WebTestClient; +import reactor.core.publisher.Flux; +import reactor.core.scheduler.Schedulers; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.awaitility.Awaitility.waitAtMost; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.timeout; +import static org.springframework.test.util.ReflectionTestUtils.setField; + +@SpringBootTest +@AutoConfigureWebTestClient +@DirtiesContext +class TrackingIT { + + @Autowired + private WebTestClient webClient; + + @Autowired + private PasteRepository pasteRepository; + + @Autowired + private TrackingService trackingService; + + @SpyBean + private MessagingClient messagingClient; + + @BeforeEach + void setUp() { + pasteRepository.deleteAll().block(); + } + + @Test + @DisplayName("tracking - paste is tracked on direct access eventually") + void trackPaste() { + var paste = givenPublicPaste(); + + assertThat(paste.getViews()).isZero(); + + webClient.get() + .uri("/api/v1/paste/" + paste.getId()) + .exchange() + .expectStatus().isOk(); + + waitAtMost(Duration.ofSeconds(1)).until( + () -> pasteRepository.findById(paste.getId()).block().getViews(), + equalTo(1L) + ); + } + + @Test + @DisplayName("tracking - deal with concurrent tracking events") + void trackConcurrentPasteViews() { + var intialPaste = givenPublicPaste(); + + Flux.fromStream(Stream.generate(intialPaste::getId)) + .take(1000) + .doOnNext(trackingService::trackView) + // enforce a concurrent update + .doOnNext(id -> pasteRepository.findById(id) + .doOnNext(paste -> setField(paste, "remoteAddress", "concurrentUpdate")) + .flatMap(paste -> pasteRepository.save(paste)) + .onErrorComplete() // ignore errors in test + .subscribe()) + .subscribeOn(Schedulers.parallel()) + .subscribe(); + + await().atMost(Duration.ofMinutes(1)).pollInterval(Duration.ofMillis(500)).until( + () -> pasteRepository.findById(intialPaste.getId()).block().getViews(), + equalTo(1000L) + ); + + Mockito.verify(messagingClient, atLeast(1001)).sendMessage(any(), any()); + } + + @Test + @DisplayName("tracking - unknown paste is not tracked indefinitely") + void trackUnknownPaste() { + trackingService.trackView("4711"); + + Mockito.verify(messagingClient).sendMessage(eq("4711"), any()); + Mockito.verify(messagingClient, timeout(TimeUnit.SECONDS.toMillis(1))).receiveMessage(); + } + + private Paste givenPublicPaste() { + return givenPaste( + Paste.newInstance( + "someTitle", + "Lorem ipsum dolor sit amet", + null, + false, + PasteExposure.PUBLIC, + "someAuthor" + ) + ); + } + + private Paste givenPaste(Paste paste) { + return pasteRepository.save(paste).block(); + } +} diff --git a/backend/src/test/java/com/github/binpastes/paste/api/UnlistedPastesIT.java b/backend/src/test/java/com/github/binpastes/paste/api/UnlistedPastesIT.java new file mode 100644 index 0000000..adc5b85 --- /dev/null +++ b/backend/src/test/java/com/github/binpastes/paste/api/UnlistedPastesIT.java @@ -0,0 +1,137 @@ +package com.github.binpastes.paste.api; + +import com.github.binpastes.paste.domain.Paste; +import com.github.binpastes.paste.domain.Paste.PasteExposure; +import com.github.binpastes.paste.domain.PasteRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.CacheControl; +import org.springframework.http.HttpHeaders; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.reactive.server.WebTestClient; + +import java.time.LocalDateTime; +import java.util.concurrent.TimeUnit; + +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@AutoConfigureWebTestClient +@DirtiesContext +class UnlistedPastesIT { + + @Autowired + private WebTestClient webClient; + + @Autowired + private PasteRepository pasteRepository; + + @BeforeEach + void setUp() { + pasteRepository.deleteAll().block(); + } + + @Test + @DisplayName("GET /{pasteId} - unlisted paste is cached") + void getUnlistedPaste() { + var unlistedPaste = givenUnlistedPaste(); + + webClient.get() + .uri("/api/v1/paste/" + unlistedPaste.getId()) + .exchange() + .expectStatus().isOk() + .expectHeader().cacheControl(CacheControl.maxAge(5, TimeUnit.MINUTES)); + } + + @Test + @DisplayName("GET /{pasteId} - unlisted paste is cached only until expiry") + void getExpiringUnlistedPaste() { + var unlistedPaste = givenPaste(Paste.newInstance( + "someTitle", + "Lorem ipsum dolor sit amet", + LocalDateTime.now().plusMinutes(5).minusSeconds(1), + false, + PasteExposure.UNLISTED, + "1.1.1.1" + )); + + webClient.get() + .uri("/api/v1/paste/" + unlistedPaste.getId()) + .exchange() + .expectStatus().isOk() + .expectHeader().value( + HttpHeaders.CACHE_CONTROL, + (value) -> assertThat(Long.valueOf(value.replace("max-age=", ""))) + .isLessThanOrEqualTo(TimeUnit.MINUTES.toSeconds(5))); + } + + @Test + @DisplayName("GET / - unlisted paste is never listed") + void findAllPastes() { + givenUnlistedPaste(); + + assertThat(pasteRepository.count().block()).isOne(); + webClient.get() + .uri("/api/v1/paste/") + .exchange() + .expectStatus().isOk() + .expectBody().jsonPath("pastes", emptyList()); + } + + @Test + @DisplayName("GET /search - unlisted paste cannot be searched for") + void searchAllPastes() { + givenUnlistedPaste(); + + assertThat(pasteRepository.count().block()).isOne(); + webClient.get() + .uri("/api/v1/paste/search?term={term}", "ipsum") + .exchange() + .expectStatus().isOk() + .expectBody().jsonPath("pastes", emptyList()); + } + + @Test + @DisplayName("DELETE /{pasteId} - unlisted paste might always be deleted") + void deleteUnlistedPaste() { + var unlistedPaste = givenUnlistedPaste(); + + webClient.get() + .uri("/api/v1/paste/" + unlistedPaste.getId()) + .exchange() + .expectStatus().isOk(); + + webClient.delete() + .uri("/api/v1/paste/" + unlistedPaste.getId()) + .exchange() + .expectStatus().isNoContent() + .expectBody().isEmpty(); + + webClient.get() + .uri("/api/v1/paste/" + unlistedPaste.getId()) + .exchange() + .expectStatus().isNotFound(); + } + + private Paste givenUnlistedPaste() { + return givenPaste( + Paste.newInstance( + "someTitle", + "Lorem ipsum dolor sit amet", + null, + false, + PasteExposure.UNLISTED, + "1.1.1.1" + ) + ); + } + + private Paste givenPaste(Paste paste) { + return pasteRepository.save(paste).block(); + } +} From 7871bff7ee00fc95fc417d2a8354bfbdef8ca7a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Nov 2023 14:27:38 +0000 Subject: [PATCH 2/2] Bump the frontend group in /frontend with 6 updates Bumps the frontend group in /frontend with 6 updates: | Package | From | To | | --- | --- | --- | | [linkify-element](https://github.com/Hypercontext/linkifyjs/tree/HEAD/packages/linkify-element) | `4.1.2` | `4.1.3` | | [linkifyjs](https://github.com/Hypercontext/linkifyjs/tree/HEAD/packages/linkifyjs) | `4.1.2` | `4.1.3` | | [solid-js](https://github.com/solidjs/solid) | `1.8.5` | `1.8.6` | | [pnpm](https://github.com/pnpm/pnpm) | `8.10.4` | `8.10.5` | | [typescript](https://github.com/Microsoft/TypeScript) | `5.2.2` | `5.3.2` | | [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `4.5.0` | `5.0.2` | Updates `linkify-element` from 4.1.2 to 4.1.3 - [Release notes](https://github.com/Hypercontext/linkifyjs/releases) - [Changelog](https://github.com/Hypercontext/linkifyjs/blob/main/CHANGELOG.md) - [Commits](https://github.com/Hypercontext/linkifyjs/commits/v4.1.3/packages/linkify-element) Updates `linkifyjs` from 4.1.2 to 4.1.3 - [Release notes](https://github.com/Hypercontext/linkifyjs/releases) - [Changelog](https://github.com/Hypercontext/linkifyjs/blob/main/CHANGELOG.md) - [Commits](https://github.com/Hypercontext/linkifyjs/commits/v4.1.3/packages/linkifyjs) Updates `solid-js` from 1.8.5 to 1.8.6 - [Release notes](https://github.com/solidjs/solid/releases) - [Changelog](https://github.com/solidjs/solid/blob/main/CHANGELOG.md) - [Commits](https://github.com/solidjs/solid/commits) Updates `pnpm` from 8.10.4 to 8.10.5 - [Release notes](https://github.com/pnpm/pnpm/releases) - [Commits](https://github.com/pnpm/pnpm/compare/v8.10.4...v8.10.5) Updates `typescript` from 5.2.2 to 5.3.2 - [Release notes](https://github.com/Microsoft/TypeScript/releases) - [Commits](https://github.com/Microsoft/TypeScript/compare/v5.2.2...v5.3.2) Updates `vite` from 4.5.0 to 5.0.2 - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.0.2/packages/vite) --- updated-dependencies: - dependency-name: linkify-element dependency-type: direct:production update-type: version-update:semver-patch dependency-group: frontend - dependency-name: linkifyjs dependency-type: direct:production update-type: version-update:semver-patch dependency-group: frontend - dependency-name: solid-js dependency-type: direct:production update-type: version-update:semver-patch dependency-group: frontend - dependency-name: pnpm dependency-type: direct:development update-type: version-update:semver-patch dependency-group: frontend - dependency-name: typescript dependency-type: direct:development update-type: version-update:semver-minor dependency-group: frontend - dependency-name: vite dependency-type: direct:development update-type: version-update:semver-major dependency-group: frontend ... Signed-off-by: dependabot[bot] --- frontend/package.json | 12 +- frontend/pnpm-lock.yaml | 346 ++++++++++++++++++++++++++-------------- 2 files changed, 233 insertions(+), 125 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 181c264..b08de69 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,17 +19,17 @@ }, "devDependencies": { "@types/crypto-js": "^4.2.1", - "pnpm": "^8.10.4", - "typescript": "^5.2.2", - "vite": "^4.5.0", + "pnpm": "^8.10.5", + "typescript": "^5.3.2", + "vite": "^5.0.2", "vite-plugin-solid": "^2.7.2" }, "dependencies": { "@solidjs/router": "^0.9.1", "crypto-js": "^4.2.0", - "linkify-element": "^4.1.2", - "linkifyjs": "^4.1.2", + "linkify-element": "^4.1.3", + "linkifyjs": "^4.1.3", "mvp.css": "^1.14.0", - "solid-js": "^1.8.5" + "solid-js": "^1.8.6" } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 5a46cfb..82d574c 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -7,39 +7,39 @@ settings: dependencies: '@solidjs/router': specifier: ^0.9.1 - version: 0.9.1(solid-js@1.8.5) + version: 0.9.1(solid-js@1.8.6) crypto-js: specifier: ^4.2.0 version: 4.2.0 linkify-element: - specifier: ^4.1.2 - version: 4.1.2(linkifyjs@4.1.2) + specifier: ^4.1.3 + version: 4.1.3(linkifyjs@4.1.3) linkifyjs: - specifier: ^4.1.2 - version: 4.1.2 + specifier: ^4.1.3 + version: 4.1.3 mvp.css: specifier: ^1.14.0 version: 1.14.0 solid-js: - specifier: ^1.8.5 - version: 1.8.5 + specifier: ^1.8.6 + version: 1.8.6 devDependencies: '@types/crypto-js': specifier: ^4.2.1 version: 4.2.1 pnpm: - specifier: ^8.10.4 - version: 8.10.4 + specifier: ^8.10.5 + version: 8.10.5 typescript: - specifier: ^5.2.2 - version: 5.2.2 + specifier: ^5.3.2 + version: 5.3.2 vite: - specifier: ^4.5.0 - version: 4.5.0 + specifier: ^5.0.2 + version: 5.0.2 vite-plugin-solid: specifier: ^2.7.2 - version: 2.7.2(solid-js@1.8.5)(vite@4.5.0) + version: 2.7.2(solid-js@1.8.6)(vite@5.0.2) packages: @@ -371,8 +371,8 @@ packages: to-fast-properties: 2.0.0 dev: true - /@esbuild/android-arm64@0.18.20: - resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + /@esbuild/android-arm64@0.19.7: + resolution: {integrity: sha512-YEDcw5IT7hW3sFKZBkCAQaOCJQLONVcD4bOyTXMZz5fr66pTHnAet46XAtbXAkJRfIn2YVhdC6R9g4xa27jQ1w==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -380,8 +380,8 @@ packages: dev: true optional: true - /@esbuild/android-arm@0.18.20: - resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + /@esbuild/android-arm@0.19.7: + resolution: {integrity: sha512-YGSPnndkcLo4PmVl2tKatEn+0mlVMr3yEpOOT0BeMria87PhvoJb5dg5f5Ft9fbCVgtAz4pWMzZVgSEGpDAlww==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -389,8 +389,8 @@ packages: dev: true optional: true - /@esbuild/android-x64@0.18.20: - resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + /@esbuild/android-x64@0.19.7: + resolution: {integrity: sha512-jhINx8DEjz68cChFvM72YzrqfwJuFbfvSxZAk4bebpngGfNNRm+zRl4rtT9oAX6N9b6gBcFaJHFew5Blf6CvUw==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -398,8 +398,8 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64@0.18.20: - resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + /@esbuild/darwin-arm64@0.19.7: + resolution: {integrity: sha512-dr81gbmWN//3ZnBIm6YNCl4p3pjnabg1/ZVOgz2fJoUO1a3mq9WQ/1iuEluMs7mCL+Zwv7AY5e3g1hjXqQZ9Iw==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -407,8 +407,8 @@ packages: dev: true optional: true - /@esbuild/darwin-x64@0.18.20: - resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + /@esbuild/darwin-x64@0.19.7: + resolution: {integrity: sha512-Lc0q5HouGlzQEwLkgEKnWcSazqr9l9OdV2HhVasWJzLKeOt0PLhHaUHuzb8s/UIya38DJDoUm74GToZ6Wc7NGQ==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -416,8 +416,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64@0.18.20: - resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + /@esbuild/freebsd-arm64@0.19.7: + resolution: {integrity: sha512-+y2YsUr0CxDFF7GWiegWjGtTUF6gac2zFasfFkRJPkMAuMy9O7+2EH550VlqVdpEEchWMynkdhC9ZjtnMiHImQ==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -425,8 +425,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64@0.18.20: - resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + /@esbuild/freebsd-x64@0.19.7: + resolution: {integrity: sha512-CdXOxIbIzPJmJhrpmJTLx+o35NoiKBIgOvmvT+jeSadYiWJn0vFKsl+0bSG/5lwjNHoIDEyMYc/GAPR9jxusTA==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -434,8 +434,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm64@0.18.20: - resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + /@esbuild/linux-arm64@0.19.7: + resolution: {integrity: sha512-inHqdOVCkUhHNvuQPT1oCB7cWz9qQ/Cz46xmVe0b7UXcuIJU3166aqSunsqkgSGMtUCWOZw3+KMwI6otINuC9g==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -443,8 +443,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm@0.18.20: - resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + /@esbuild/linux-arm@0.19.7: + resolution: {integrity: sha512-Y+SCmWxsJOdQtjcBxoacn/pGW9HDZpwsoof0ttL+2vGcHokFlfqV666JpfLCSP2xLxFpF1lj7T3Ox3sr95YXww==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -452,8 +452,8 @@ packages: dev: true optional: true - /@esbuild/linux-ia32@0.18.20: - resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + /@esbuild/linux-ia32@0.19.7: + resolution: {integrity: sha512-2BbiL7nLS5ZO96bxTQkdO0euGZIUQEUXMTrqLxKUmk/Y5pmrWU84f+CMJpM8+EHaBPfFSPnomEaQiG/+Gmh61g==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -461,8 +461,8 @@ packages: dev: true optional: true - /@esbuild/linux-loong64@0.18.20: - resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + /@esbuild/linux-loong64@0.19.7: + resolution: {integrity: sha512-BVFQla72KXv3yyTFCQXF7MORvpTo4uTA8FVFgmwVrqbB/4DsBFWilUm1i2Oq6zN36DOZKSVUTb16jbjedhfSHw==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -470,8 +470,8 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el@0.18.20: - resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + /@esbuild/linux-mips64el@0.19.7: + resolution: {integrity: sha512-DzAYckIaK+pS31Q/rGpvUKu7M+5/t+jI+cdleDgUwbU7KdG2eC3SUbZHlo6Q4P1CfVKZ1lUERRFP8+q0ob9i2w==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -479,8 +479,8 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64@0.18.20: - resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + /@esbuild/linux-ppc64@0.19.7: + resolution: {integrity: sha512-JQ1p0SmUteNdUaaiRtyS59GkkfTW0Edo+e0O2sihnY4FoZLz5glpWUQEKMSzMhA430ctkylkS7+vn8ziuhUugQ==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -488,8 +488,8 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64@0.18.20: - resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + /@esbuild/linux-riscv64@0.19.7: + resolution: {integrity: sha512-xGwVJ7eGhkprY/nB7L7MXysHduqjpzUl40+XoYDGC4UPLbnG+gsyS1wQPJ9lFPcxYAaDXbdRXd1ACs9AE9lxuw==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -497,8 +497,8 @@ packages: dev: true optional: true - /@esbuild/linux-s390x@0.18.20: - resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + /@esbuild/linux-s390x@0.19.7: + resolution: {integrity: sha512-U8Rhki5PVU0L0nvk+E8FjkV8r4Lh4hVEb9duR6Zl21eIEYEwXz8RScj4LZWA2i3V70V4UHVgiqMpszXvG0Yqhg==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -506,8 +506,8 @@ packages: dev: true optional: true - /@esbuild/linux-x64@0.18.20: - resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + /@esbuild/linux-x64@0.19.7: + resolution: {integrity: sha512-ZYZopyLhm4mcoZXjFt25itRlocKlcazDVkB4AhioiL9hOWhDldU9n38g62fhOI4Pth6vp+Mrd5rFKxD0/S+7aQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -515,8 +515,8 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64@0.18.20: - resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + /@esbuild/netbsd-x64@0.19.7: + resolution: {integrity: sha512-/yfjlsYmT1O3cum3J6cmGG16Fd5tqKMcg5D+sBYLaOQExheAJhqr8xOAEIuLo8JYkevmjM5zFD9rVs3VBcsjtQ==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -524,8 +524,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64@0.18.20: - resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + /@esbuild/openbsd-x64@0.19.7: + resolution: {integrity: sha512-MYDFyV0EW1cTP46IgUJ38OnEY5TaXxjoDmwiTXPjezahQgZd+j3T55Ht8/Q9YXBM0+T9HJygrSRGV5QNF/YVDQ==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -533,8 +533,8 @@ packages: dev: true optional: true - /@esbuild/sunos-x64@0.18.20: - resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + /@esbuild/sunos-x64@0.19.7: + resolution: {integrity: sha512-JcPvgzf2NN/y6X3UUSqP6jSS06V0DZAV/8q0PjsZyGSXsIGcG110XsdmuWiHM+pno7/mJF6fjH5/vhUz/vA9fw==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -542,8 +542,8 @@ packages: dev: true optional: true - /@esbuild/win32-arm64@0.18.20: - resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + /@esbuild/win32-arm64@0.19.7: + resolution: {integrity: sha512-ZA0KSYti5w5toax5FpmfcAgu3ZNJxYSRm0AW/Dao5up0YV1hDVof1NvwLomjEN+3/GMtaWDI+CIyJOMTRSTdMw==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -551,8 +551,8 @@ packages: dev: true optional: true - /@esbuild/win32-ia32@0.18.20: - resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + /@esbuild/win32-ia32@0.19.7: + resolution: {integrity: sha512-CTOnijBKc5Jpk6/W9hQMMvJnsSYRYgveN6O75DTACCY18RA2nqka8dTZR+x/JqXCRiKk84+5+bRKXUSbbwsS0A==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -560,8 +560,8 @@ packages: dev: true optional: true - /@esbuild/win32-x64@0.18.20: - resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + /@esbuild/win32-x64@0.19.7: + resolution: {integrity: sha512-gRaP2sk6hc98N734luX4VpF318l3w+ofrtTu9j5L8EQXF+FzQKV6alCOHMVoJJHvVK/mGbwBXfOL1HETQu9IGQ==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -599,12 +599,108 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true - /@solidjs/router@0.9.1(solid-js@1.8.5): + /@rollup/rollup-android-arm-eabi@4.5.2: + resolution: {integrity: sha512-ee7BudTwwrglFYSc3UnqInDDjCLWHKrFmGNi4aK7jlEyg4CyPa1DCMrZfsN1O13YT76UFEqXz2CoN7BCGpUlJw==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-android-arm64@4.5.2: + resolution: {integrity: sha512-xOuhj9HHtn8128ir8veoQsBbAUBasDbHIBniYTEx02pAmu9EXL+ZjJqngnNEy6ZgZ4h1JwL33GMNu3yJL5Mzow==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-arm64@4.5.2: + resolution: {integrity: sha512-NTGJWoL8bKyqyWFn9/RzSv4hQ4wTbaAv0lHHRwf4OnpiiP4P8W0jiXbm8Nc5BCXKmWAwuvJY82mcIU2TayC20g==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-x64@4.5.2: + resolution: {integrity: sha512-hlKqj7bpPvU15sZo4za14u185lpMzdwWLMc9raMqPK4wywt0wR23y1CaVQ4oAFXat3b5/gmRntyfpwWTKl+vvA==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.5.2: + resolution: {integrity: sha512-7ZIZx8c3u+pfI0ohQsft/GywrXez0uR6dUP0JhBuCK3sFO5TfdLn/YApnVkvPxuTv3+YKPIZend9Mt7Cz6sS3Q==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.5.2: + resolution: {integrity: sha512-7Pk/5mO11JW/cH+a8lL/i0ZxmRGrbpYqN0VwO2DHhU+SJWWOH2zE1RAcPaj8KqiwC8DCDIJOSxjV9+9lLb6aeA==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.5.2: + resolution: {integrity: sha512-KrRnuG5phJx756e62wxvWH2e+TK84MP2IVuPwfge+GBvWqIUfVzFRn09TKruuQBXzZp52Vyma7FjMDkwlA9xpg==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.5.2: + resolution: {integrity: sha512-My+53GasPa2D2tU5dXiyHYwrELAUouSfkNlZ3bUKpI7btaztO5vpALEs3mvFjM7aKTvEbc7GQckuXeXIDKQ0fg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.5.2: + resolution: {integrity: sha512-/f0Q6Sc+Vw54Ws6N8fxaEe4R7at3b8pFyv+O/F2VaQ4hODUJcRUcCBJh6zuqtgQQt7w845VTkGLFgWZkP3tUoQ==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.5.2: + resolution: {integrity: sha512-NCKuuZWLht6zj7s6EIFef4BxCRX1GMr83S2W4HPCA0RnJ4iHE4FS1695q6Ewoa6A9nFjJe1//yUu0kgBU07Edw==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.5.2: + resolution: {integrity: sha512-J5zL3riR4AOyU/J3M/i4k/zZ8eP1yT+nTmAKztCXJtnI36jYH0eepvob22mAQ/kLwfsK2TB6dbyVY1F8c/0H5A==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.5.2: + resolution: {integrity: sha512-pL0RXRHuuGLhvs7ayX/SAHph1hrDPXOM5anyYUQXWJEENxw3nfHkzv8FfVlEVcLyKPAEgDRkd6RKZq2SMqS/yg==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@solidjs/router@0.9.1(solid-js@1.8.6): resolution: {integrity: sha512-kRY75piOQsyoH75E/RP6lr7uVGFCjeeCCCJx7Z2D1Vc6+I1yFQjLCvE+6agXGwqDoWi6vbETP1g7gmp/L1mNLg==} peerDependencies: solid-js: ^1.8.4 dependencies: - solid-js: 1.8.5 + solid-js: 1.8.6 dev: false /@types/babel__core@7.20.3: @@ -730,34 +826,34 @@ packages: resolution: {integrity: sha512-dg5gj5qOgfZNkPNeyKBZQAQitIQ/xwfIDmEQJHCbXaD9ebTZxwJXUsDYcBlAvZGZLi+/354l35J1wkmP6CqYaw==} dev: true - /esbuild@0.18.20: - resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + /esbuild@0.19.7: + resolution: {integrity: sha512-6brbTZVqxhqgbpqBR5MzErImcpA0SQdoKOkcWK/U30HtQxnokIpG3TX2r0IJqbFUzqLjhU/zC1S5ndgakObVCQ==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/android-arm': 0.18.20 - '@esbuild/android-arm64': 0.18.20 - '@esbuild/android-x64': 0.18.20 - '@esbuild/darwin-arm64': 0.18.20 - '@esbuild/darwin-x64': 0.18.20 - '@esbuild/freebsd-arm64': 0.18.20 - '@esbuild/freebsd-x64': 0.18.20 - '@esbuild/linux-arm': 0.18.20 - '@esbuild/linux-arm64': 0.18.20 - '@esbuild/linux-ia32': 0.18.20 - '@esbuild/linux-loong64': 0.18.20 - '@esbuild/linux-mips64el': 0.18.20 - '@esbuild/linux-ppc64': 0.18.20 - '@esbuild/linux-riscv64': 0.18.20 - '@esbuild/linux-s390x': 0.18.20 - '@esbuild/linux-x64': 0.18.20 - '@esbuild/netbsd-x64': 0.18.20 - '@esbuild/openbsd-x64': 0.18.20 - '@esbuild/sunos-x64': 0.18.20 - '@esbuild/win32-arm64': 0.18.20 - '@esbuild/win32-ia32': 0.18.20 - '@esbuild/win32-x64': 0.18.20 + '@esbuild/android-arm': 0.19.7 + '@esbuild/android-arm64': 0.19.7 + '@esbuild/android-x64': 0.19.7 + '@esbuild/darwin-arm64': 0.19.7 + '@esbuild/darwin-x64': 0.19.7 + '@esbuild/freebsd-arm64': 0.19.7 + '@esbuild/freebsd-x64': 0.19.7 + '@esbuild/linux-arm': 0.19.7 + '@esbuild/linux-arm64': 0.19.7 + '@esbuild/linux-ia32': 0.19.7 + '@esbuild/linux-loong64': 0.19.7 + '@esbuild/linux-mips64el': 0.19.7 + '@esbuild/linux-ppc64': 0.19.7 + '@esbuild/linux-riscv64': 0.19.7 + '@esbuild/linux-s390x': 0.19.7 + '@esbuild/linux-x64': 0.19.7 + '@esbuild/netbsd-x64': 0.19.7 + '@esbuild/openbsd-x64': 0.19.7 + '@esbuild/sunos-x64': 0.19.7 + '@esbuild/win32-arm64': 0.19.7 + '@esbuild/win32-ia32': 0.19.7 + '@esbuild/win32-x64': 0.19.7 dev: true /escalade@3.1.1: @@ -818,16 +914,16 @@ packages: hasBin: true dev: true - /linkify-element@4.1.2(linkifyjs@4.1.2): - resolution: {integrity: sha512-Pg/A44SXkF7Hwwss5WMX8pbve/u4Co2a5uChXzSTFD1bOSlzdxJMIdh0VuHN6Cvk6oplA2ImBHOtrBUsBn2l2Q==} + /linkify-element@4.1.3(linkifyjs@4.1.3): + resolution: {integrity: sha512-oUoG7BWaR3Q6kAKdlLi8slsu5rkVRxbiDVVlkpoL7vtidY5THggLzRHIBtmcj+tvMpcAUQomJApDxg0ub0qpdA==} peerDependencies: linkifyjs: ^4.0.0 dependencies: - linkifyjs: 4.1.2 + linkifyjs: 4.1.3 dev: false - /linkifyjs@4.1.2: - resolution: {integrity: sha512-1elJrH8MwUgr77Rgmx4JgB/nBgISYVoGossH6pAfCeHG+07TblTn6RWKx0MKozEMJU6NCFYHRih9M8ZtV3YZ+Q==} + /linkifyjs@4.1.3: + resolution: {integrity: sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg==} dev: false /lru-cache@5.1.1: @@ -851,8 +947,8 @@ packages: resolution: {integrity: sha512-f7F8dUw3kCA80z4myG4z5ymsrNK0oft2JQ1Fz0brpc+vIimJhpLk5B/rI44IQLgjpBDNIRzaZfwZUdndKs6j3w==} dev: false - /nanoid@3.3.6: - resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true dev: true @@ -865,8 +961,8 @@ packages: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true - /pnpm@8.10.4: - resolution: {integrity: sha512-KH7bZKf5dYOIhK80/tKuRqbwbD9Khkd59TP0OES/Wr8yJqBKQnhQSrEYYRpxDW4XFLUnKhoxLy5vJTz1h7mlVA==} + /pnpm@8.10.5: + resolution: {integrity: sha512-nBYfQz2FVRxY8bOhCxjMPfcrWgLSyu5lZswFtvIK3e+UfnldkMOQM7+S3lUXfq1p2H9iqdqtyR56LjtY9JNToA==} engines: {node: '>=16.14'} hasBin: true dev: true @@ -875,16 +971,28 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} dependencies: - nanoid: 3.3.6 + nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.0.2 dev: true - /rollup@3.29.4: - resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} - engines: {node: '>=14.18.0', npm: '>=8.0.0'} + /rollup@4.5.2: + resolution: {integrity: sha512-CRK1uoROBfkcqrZKyaFcqCcZWNsvJ6yVYZkqTlRocZhO2s5yER6Z3f/QaYtO8RGyloPnmhwgzuPQpNGeK210xQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.5.2 + '@rollup/rollup-android-arm64': 4.5.2 + '@rollup/rollup-darwin-arm64': 4.5.2 + '@rollup/rollup-darwin-x64': 4.5.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.5.2 + '@rollup/rollup-linux-arm64-gnu': 4.5.2 + '@rollup/rollup-linux-arm64-musl': 4.5.2 + '@rollup/rollup-linux-x64-gnu': 4.5.2 + '@rollup/rollup-linux-x64-musl': 4.5.2 + '@rollup/rollup-win32-arm64-msvc': 4.5.2 + '@rollup/rollup-win32-ia32-msvc': 4.5.2 + '@rollup/rollup-win32-x64-msvc': 4.5.2 fsevents: 2.3.3 dev: true @@ -893,17 +1001,17 @@ packages: hasBin: true dev: true - /seroval@0.12.0: - resolution: {integrity: sha512-5qvk9WRORhvOdHVtDv6KEKrTDbG8O9nODDdETfQcJ0u9iaZJ6mAASg/nQgPzqtuV9cqln9msE7WNmD+sJomrIw==} + /seroval@0.14.1: + resolution: {integrity: sha512-ZlC9y1KVDhZFdEHLYZup1RjKDutyX1tt3ffOauqRbRURa2vRr2NU/bHuVEuNEqR3zE2uCU3WM6LqH6Oinc3tWg==} engines: {node: '>=10'} - /solid-js@1.8.5: - resolution: {integrity: sha512-xvtJvzJzWbsn35oKFhW9kNwaxG1Z/YLMsDp4tLVcYZTMPzvzQ8vEZuyDQ6nt7xDArVgZJ7TUFrJUwrui/oq53A==} + /solid-js@1.8.6: + resolution: {integrity: sha512-yiH6ZfBBZ3xj/aU/PBpVKB+8r8WWp100NGF7k/Z0IrK9Y8Lv0jwvFiJY1cHdc6Tj7GqXArKnMBabM0m1k+LzkA==} dependencies: csstype: 3.1.2 - seroval: 0.12.0 + seroval: 0.14.1 - /solid-refresh@0.5.3(solid-js@1.8.5): + /solid-refresh@0.5.3(solid-js@1.8.6): resolution: {integrity: sha512-Otg5it5sjOdZbQZJnvo99TEBAr6J7PQ5AubZLNU6szZzg3RQQ5MX04oteBIIGDs0y2Qv8aXKm9e44V8z+UnFdw==} peerDependencies: solid-js: ^1.3 @@ -911,7 +1019,7 @@ packages: '@babel/generator': 7.23.0 '@babel/helper-module-imports': 7.22.15 '@babel/types': 7.23.0 - solid-js: 1.8.5 + solid-js: 1.8.6 dev: true /source-map-js@1.0.2: @@ -931,8 +1039,8 @@ packages: engines: {node: '>=4'} dev: true - /typescript@5.2.2: - resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} + /typescript@5.3.2: + resolution: {integrity: sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==} engines: {node: '>=14.17'} hasBin: true dev: true @@ -952,7 +1060,7 @@ packages: resolution: {integrity: sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==} dev: true - /vite-plugin-solid@2.7.2(solid-js@1.8.5)(vite@4.5.0): + /vite-plugin-solid@2.7.2(solid-js@1.8.6)(vite@5.0.2): resolution: {integrity: sha512-GV2SMLAibOoXe76i02AsjAg7sbm/0lngBlERvJKVN67HOrJsHcWgkt0R6sfGLDJuFkv2aBe14Zm4vJcNME+7zw==} peerDependencies: solid-js: ^1.7.2 @@ -963,20 +1071,20 @@ packages: '@types/babel__core': 7.20.3 babel-preset-solid: 1.8.2(@babel/core@7.23.2) merge-anything: 5.1.7 - solid-js: 1.8.5 - solid-refresh: 0.5.3(solid-js@1.8.5) - vite: 4.5.0 - vitefu: 0.2.5(vite@4.5.0) + solid-js: 1.8.6 + solid-refresh: 0.5.3(solid-js@1.8.6) + vite: 5.0.2 + vitefu: 0.2.5(vite@5.0.2) transitivePeerDependencies: - supports-color dev: true - /vite@4.5.0: - resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} - engines: {node: ^14.18.0 || >=16.0.0} + /vite@5.0.2: + resolution: {integrity: sha512-6CCq1CAJCNM1ya2ZZA7+jS2KgnhbzvxakmlIjN24cF/PXhRMzpM/z8QgsVJA/Dm5fWUWnVEsmtBoMhmerPxT0g==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: - '@types/node': '>= 14' + '@types/node': ^18.0.0 || >=20.0.0 less: '*' lightningcss: ^1.21.0 sass: '*' @@ -999,14 +1107,14 @@ packages: terser: optional: true dependencies: - esbuild: 0.18.20 + esbuild: 0.19.7 postcss: 8.4.31 - rollup: 3.29.4 + rollup: 4.5.2 optionalDependencies: fsevents: 2.3.3 dev: true - /vitefu@0.2.5(vite@4.5.0): + /vitefu@0.2.5(vite@5.0.2): resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==} peerDependencies: vite: ^3.0.0 || ^4.0.0 || ^5.0.0 @@ -1014,7 +1122,7 @@ packages: vite: optional: true dependencies: - vite: 4.5.0 + vite: 5.0.2 dev: true /yallist@3.1.1: