From 3dd70055a00f56e16df324af7bd59065ffa5a382 Mon Sep 17 00:00:00 2001 From: stijnpotters Date: Thu, 19 Mar 2026 16:02:10 +0100 Subject: [PATCH 01/20] feat: initial working hazelcast integration. localgateway working, outboundgateway always 1 member --- pom.xml | 16 ++ .../routes/projectlanding/project-landing.tsx | 107 ++++++++-- .../app/services/hazelcast-service.ts | 12 ++ .../frankframework/flow/FlowApplication.java | 13 +- .../flow/FlowWarInitializer.java | 31 +++ .../flow/common/config/AnnotationConfig.java | 48 +++++ .../common/config/ConfigurationsConfig.java | 67 ++++++ .../config/SecurityChainConfigurer.java | 70 ++++++ .../flow/common/config/WebConfiguration.java | 40 +++- .../properties/HazelcastProperties.java | 12 ++ .../hazelcast/ConfigurationsDirectory.java | 93 ++++++++ .../flow/hazelcast/FrankInstanceDTO.java | 5 + .../hazelcast/HazelcastConfigurationDTO.java | 20 ++ .../flow/hazelcast/HazelcastController.java | 27 +++ .../flow/hazelcast/HazelcastService.java | 200 ++++++++++++++++++ .../flow/security/UserWorkspaceContext.java | 3 +- src/main/resources/FrankFlowContext.xml | 21 ++ src/main/resources/SpringBootContext.xml | 17 ++ src/main/resources/application.properties | 3 + 19 files changed, 788 insertions(+), 17 deletions(-) create mode 100644 src/main/frontend/app/services/hazelcast-service.ts create mode 100644 src/main/java/org/frankframework/flow/FlowWarInitializer.java create mode 100644 src/main/java/org/frankframework/flow/common/config/AnnotationConfig.java create mode 100644 src/main/java/org/frankframework/flow/common/config/ConfigurationsConfig.java create mode 100644 src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java create mode 100644 src/main/java/org/frankframework/flow/common/properties/HazelcastProperties.java create mode 100644 src/main/java/org/frankframework/flow/hazelcast/ConfigurationsDirectory.java create mode 100644 src/main/java/org/frankframework/flow/hazelcast/FrankInstanceDTO.java create mode 100644 src/main/java/org/frankframework/flow/hazelcast/HazelcastConfigurationDTO.java create mode 100644 src/main/java/org/frankframework/flow/hazelcast/HazelcastController.java create mode 100644 src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java create mode 100644 src/main/resources/FrankFlowContext.xml create mode 100644 src/main/resources/SpringBootContext.xml diff --git a/pom.xml b/pom.xml index dafd501f..b1973ffc 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,7 @@ 3.0.0-SNAPSHOT 21 + 7.0.6 2.20.1 1.18.42 3.0.0 @@ -96,6 +97,11 @@ + + org.springframework + spring-beans + ${spring-framework.version} + org.springframework.boot @@ -177,6 +183,12 @@ Saxon-HE 12.9 + + org.frankframework + frankframework-management-gateway + 10.0.0 + compile + @@ -222,6 +234,10 @@ lombok ${lombok.version} + + org.springframework.boot + spring-boot-configuration-processor + diff --git a/src/main/frontend/app/routes/projectlanding/project-landing.tsx b/src/main/frontend/app/routes/projectlanding/project-landing.tsx index af0f7baa..b25bafef 100644 --- a/src/main/frontend/app/routes/projectlanding/project-landing.tsx +++ b/src/main/frontend/app/routes/projectlanding/project-landing.tsx @@ -24,6 +24,7 @@ import { } from '~/services/project-service' import { useRecentProjects } from '~/hooks/use-projects' import { showErrorToast } from '~/components/toast' +import { discoverFrankInstances, type FrankInstance } from '~/services/hazelcast-service' export default function ProjectLanding() { const navigate = useNavigate() @@ -40,6 +41,8 @@ export default function ProjectLanding() { const [isLocalEnvironment, setIsLocalEnvironment] = useState(true) const [rootLocationName, setRootLocationName] = useState('Computer') const [isOpeningProject, setIsOpeningProject] = useState(false) + const [frankInstances, setFrankInstances] = useState([]) + const [isDiscovering, setIsDiscovering] = useState(false) const importInputRef = useRef(null) useEffect(() => { @@ -67,6 +70,29 @@ export default function ProjectLanding() { } }, [apiError]) + useEffect(() => { + if (!isLocalEnvironment) return + const controller = new AbortController() + + const discover = () => { + discoverFrankInstances(controller.signal) + .then(setFrankInstances) + .catch(() => { + // Discovery failure is non-critical; Hazelcast may not be running + }) + .finally(() => setIsDiscovering(false)) + } + + setIsDiscovering(true) + discover() + const interval = setInterval(discover, 3000) + + return () => { + controller.abort() + clearInterval(interval) + } + }, [isLocalEnvironment]) + const handleOpenProject = useCallback( async (rootPath: string) => { setIsOpeningProject(true) @@ -155,6 +181,27 @@ export default function ProjectLanding() { } } + const handleConnectToInstance = useCallback( + async (instance: FrankInstance) => { + const path = instance.projectPaths[0] + if (!path) { + showErrorToast(`No configuration path available for "${instance.name}"`) + return + } + setIsOpeningProject(true) + try { + const project = await openProject(path) + setProject(project) + navigate(`/studio/${encodeURIComponent(project.name)}`) + } catch (error) { + showErrorToast(error instanceof Error ? error.message : `Failed to open remote instance "${instance.name}"`) + } finally { + setIsOpeningProject(false) + } + }, + [navigate, setProject], + ) + const projects = recentProjects ?? [] const filteredProjects = projects.filter((project) => project.name.toLowerCase().includes(searchTerm.toLowerCase())) @@ -183,6 +230,9 @@ export default function ProjectLanding() { onProjectClick={handleOpenProject} onRemoveProject={onRemoveProject} onExportProject={onExportProject} + frankInstances={frankInstances} + isDiscovering={isDiscovering} + onConnectToInstance={handleConnectToInstance} /> @@ -264,27 +314,60 @@ const ProjectList = ({ onProjectClick, onRemoveProject, onExportProject, + frankInstances, + isDiscovering, + onConnectToInstance, }: { projects: RecentProject[] isLocal: boolean onProjectClick: (rootPath: string) => void onRemoveProject: (rootPath: string) => void onExportProject: (projectName: string) => void + frankInstances: FrankInstance[] + isDiscovering: boolean + onConnectToInstance: (instance: FrankInstance) => void }) => (
- {projects.length === 0 ? ( + {frankInstances.length > 0 && ( +
+

Remote

+ {frankInstances.map((instance) => ( +
onConnectToInstance(instance)} + > +
+
{instance.name}
+ {instance.projectPaths.length > 0 && ( +

{instance.projectPaths[0]}

+ )} +
+ Live +
+ ))} +
+ )} + {isDiscovering && frankInstances.length === 0 && ( +

Scanning for remote instances...

+ )} + {projects.length === 0 && frankInstances.length === 0 && !isDiscovering && (

No projects found

- ) : ( - projects.map((project) => ( - onProjectClick(project.rootPath)} - onRemove={() => onRemoveProject(project.rootPath)} - onExport={() => onExportProject(project.name)} - /> - )) + )} + {projects.length > 0 && ( + <> +

Recent

+ {projects.map((project) => ( + onProjectClick(project.rootPath)} + onRemove={() => onRemoveProject(project.rootPath)} + onExport={() => onExportProject(project.name)} + /> + ))} + )}
) diff --git a/src/main/frontend/app/services/hazelcast-service.ts b/src/main/frontend/app/services/hazelcast-service.ts new file mode 100644 index 00000000..afcffd79 --- /dev/null +++ b/src/main/frontend/app/services/hazelcast-service.ts @@ -0,0 +1,12 @@ +import { apiFetch } from '~/utils/api' + +export interface FrankInstance { + name: string + id: string | null + projectPaths: string[] + local: boolean +} + +export async function discoverFrankInstances(signal?: AbortSignal): Promise { + return apiFetch('/hazelcast/instances', { signal }) +} diff --git a/src/main/java/org/frankframework/flow/FlowApplication.java b/src/main/java/org/frankframework/flow/FlowApplication.java index d911c49a..52b7c2a5 100644 --- a/src/main/java/org/frankframework/flow/FlowApplication.java +++ b/src/main/java/org/frankframework/flow/FlowApplication.java @@ -7,6 +7,8 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.context.annotation.Bean; import org.springframework.core.io.ClassPathResource; import org.springframework.scheduling.annotation.EnableScheduling; @@ -16,11 +18,16 @@ @SpringBootApplication @EnableScheduling -public class FlowApplication { +@ConfigurationPropertiesScan +public class FlowApplication extends SpringBootServletInitializer { public static void main(String[] args) { - SpringApplication app = configureApplication(); - app.run(args); + SpringApplication.run(FlowApplication.class, args); + } + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { + return builder.sources(FlowApplication.class); } public static SpringApplication configureApplication() { diff --git a/src/main/java/org/frankframework/flow/FlowWarInitializer.java b/src/main/java/org/frankframework/flow/FlowWarInitializer.java new file mode 100644 index 00000000..a8411d35 --- /dev/null +++ b/src/main/java/org/frankframework/flow/FlowWarInitializer.java @@ -0,0 +1,31 @@ +package org.frankframework.flow; +/* + Copyright 2023 WeAreFrank! + + 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. +*/ + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * Spring Boot entrypoint when running as a WAR application deployed to an external servlet container. + * For standalone JAR execution, see {@link FlowApplication}. + */ +public class FlowWarInitializer extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { + return builder.sources(FlowApplication.class); + } +} diff --git a/src/main/java/org/frankframework/flow/common/config/AnnotationConfig.java b/src/main/java/org/frankframework/flow/common/config/AnnotationConfig.java new file mode 100644 index 00000000..9cfb4c88 --- /dev/null +++ b/src/main/java/org/frankframework/flow/common/config/AnnotationConfig.java @@ -0,0 +1,48 @@ +package org.frankframework.flow.common.config; + +import org.apache.commons.lang3.StringUtils; + +import org.frankframework.management.bus.LocalGateway; +import org.frankframework.management.bus.OutboundGatewayFactory; +import org.frankframework.management.gateway.HazelcastOutboundGateway; +import org.frankframework.management.security.JwtKeyGenerator; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.integration.channel.PublishSubscribeChannel; +import org.springframework.messaging.MessageChannel; + +@Configuration +@ConditionalOnProperty(name = "hazelcast.enabled", havingValue = "true") +public class AnnotationConfig { + + private final ApplicationContext applicationContext; + + public AnnotationConfig(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Bean + public JwtKeyGenerator jwtKeyGenerator() { + return new JwtKeyGenerator(); + } + + @Bean(name = "frank-management-bus") + @ConditionalOnProperty(name = "configurations.directory", matchIfMissing = false) + public MessageChannel frankManagementBus() { + return new PublishSubscribeChannel(); + } + + @Bean + @Scope("singleton") + public OutboundGatewayFactory createOutboundGatewayFactory() { + OutboundGatewayFactory factory = new OutboundGatewayFactory(); + String configDirectory = applicationContext.getEnvironment().getProperty("configurations.directory"); + String gatewayClassName = StringUtils.isEmpty(configDirectory) ? HazelcastOutboundGateway.class.getCanonicalName() : LocalGateway.class.getCanonicalName(); + factory.setGatewayClassname(gatewayClassName); + return factory; + } +} diff --git a/src/main/java/org/frankframework/flow/common/config/ConfigurationsConfig.java b/src/main/java/org/frankframework/flow/common/config/ConfigurationsConfig.java new file mode 100644 index 00000000..de02ca84 --- /dev/null +++ b/src/main/java/org/frankframework/flow/common/config/ConfigurationsConfig.java @@ -0,0 +1,67 @@ +package org.frankframework.flow.common.config; + +import java.util.ArrayList; +import java.util.List; + +import org.frankframework.flow.exception.ApiException; +import org.frankframework.flow.hazelcast.HazelcastConfigurationDTO; +import org.frankframework.management.bus.BusAction; +import org.frankframework.management.bus.BusMessageUtils; +import org.frankframework.management.bus.BusTopic; +import org.frankframework.management.bus.OutboundGateway; +import org.frankframework.util.JacksonUtils; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.SessionScope; + + +@Component +@SessionScope +@ConditionalOnProperty(name = "hazelcast.enabled", havingValue = "true") +public class ConfigurationsConfig implements InitializingBean { + private static final String DEFAULT_FF_CONFIGURATION_PREFIX = "IAF_"; + + private List configurations = new ArrayList<>(); + + private final OutboundGateway outboundGateway; + + public ConfigurationsConfig(OutboundGateway outboundGateway) { + this.outboundGateway = outboundGateway; + } + + + @Override + public void afterPropertiesSet() throws Exception { + Message request = MessageBuilder.withPayload("NONE").setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()).setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()).build(); + + Message response = outboundGateway.sendSyncMessage(request); + List configs = getConfigurations(response); + + configurations = configs.stream().filter(e -> !e.getName().startsWith(DEFAULT_FF_CONFIGURATION_PREFIX)).toList(); + } + + private List getConfigurations(Message response) throws ApiException { + if(MediaType.APPLICATION_JSON_VALUE.equals(response.getHeaders().get(BusMessageUtils.HEADER_PREFIX+"type"))) { + HazelcastConfigurationDTO[] arr = JacksonUtils.convertToDTO(response.getPayload(), HazelcastConfigurationDTO[].class); + return List.of(arr); //TODO new TypeReference>(){} + } + + throw new ApiException("unexpected result returned by Bus", HttpStatus.INTERNAL_SERVER_ERROR); + } + + public List getAllConfigurations() { + return configurations.stream().map(HazelcastConfigurationDTO::getName).toList(); + } + + public HazelcastConfigurationDTO getConfiguration(String name) throws ApiException { + return configurations.stream() + .filter(e -> e.getName().equals(name)) + .findFirst() + .orElseThrow(() -> new ApiException("configuration not found", HttpStatus.NOT_FOUND)); + } +} diff --git a/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java b/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java new file mode 100644 index 00000000..8dcce80c --- /dev/null +++ b/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java @@ -0,0 +1,70 @@ +package org.frankframework.flow.common.config; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.frankframework.lifecycle.DynamicRegistration.Servlet; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.EnvironmentAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.core.env.Environment; +import org.springframework.security.authorization.AuthenticatedAuthorizationManager; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; +import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; +import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.util.matcher.AnyRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; + +import lombok.Setter; + +/** + * Enable security, although.. it's anonymous on all endpoints, but at least sets the + * SecurityContextHolder.getContext().getAuthentication(); object. + */ +@Configuration +@EnableWebSecurity //Enables Spring Security (classpath) +@EnableMethodSecurity(jsr250Enabled = true, prePostEnabled = false) //Enables JSR 250 (JAX-RS) annotations +@Order(Ordered.HIGHEST_PRECEDENCE) +public class SecurityChainConfigurer implements ApplicationContextAware, EnvironmentAware { + private @Setter ApplicationContext applicationContext; + private @Setter Environment environment; + + @Bean + public SecurityFilterChain configureChain(HttpSecurity http) throws Exception { + //Apply defaults to disable bloated filters, see DefaultSecurityFilterChain.getFilters for the actual list. + http.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)); //Allow same origin iframe request + http.csrf(CsrfConfigurer::disable); + RequestMatcher securityRequestMatcher = AnyRequestMatcher.INSTANCE; + http.securityMatcher(securityRequestMatcher); //Triggers the SecurityFilterChain, also for OPTIONS requests! + http.formLogin(FormLoginConfigurer::disable); //Disable the form login filter + http.logout(LogoutConfigurer::disable); //Disable the logout endpoint on every filter + + http.anonymous(anonymous -> anonymous.authorities(getAuthorities())); + + // Enables security for all servlet endpoints + http.authorizeHttpRequests(requests -> requests.requestMatchers(securityRequestMatcher).access(AuthenticatedAuthorizationManager.anonymous())); + + return http.build(); + } + + private List getAuthorities() { + Set securityRoles = Set.of(Servlet.ALL_IBIS_USER_ROLES); + List grantedAuthorities = new ArrayList<>(securityRoles.size()); + for (String role : securityRoles) { + grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role)); + } + return grantedAuthorities; + } +} diff --git a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java index 4de60e6d..16eb5a36 100644 --- a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java +++ b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java @@ -1,17 +1,32 @@ package org.frankframework.flow.common.config; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import org.frankframework.management.gateway.InputStreamHttpMessageConverter; + import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.http.converter.FormHttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; +import org.springframework.web.filter.RequestContextFilter; import org.springframework.web.method.HandlerTypePredicate; +import org.springframework.web.multipart.support.StandardServletMultipartResolver; import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.util.List; + @Configuration +@EnableWebMvc public class WebConfiguration implements WebMvcConfigurer { private static final long MAX_AGE_SECONDS = 3600; @@ -36,11 +51,34 @@ public void configurePathMatch(PathMatchConfigurer configurer) { @Bean public ObjectMapper objectMapper() { - return new ObjectMapper(); + return new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); } @Bean public RestTemplate restTemplate() { return new RestTemplate(); } + + @Override + public void configureMessageConverters(List> converters) { + converters.add(new MappingJackson2HttpMessageConverter(objectMapper())); + converters.add(new InputStreamHttpMessageConverter()); + converters.add(new FormHttpMessageConverter()); + } + + @Bean + StandardServletMultipartResolver multipartResolver() { + return new StandardServletMultipartResolver(); + } + + /** + * Explicitly register RequestContextFilter since @EnableWebMvc disables WebMvcAutoConfiguration + * which normally registers it. Without this, request-scoped beans are not accessible in filters. + */ + @Bean + public FilterRegistrationBean requestContextFilter() { + FilterRegistrationBean registration = new FilterRegistrationBean<>(new RequestContextFilter()); + registration.setOrder(Ordered.HIGHEST_PRECEDENCE); + return registration; + } } diff --git a/src/main/java/org/frankframework/flow/common/properties/HazelcastProperties.java b/src/main/java/org/frankframework/flow/common/properties/HazelcastProperties.java new file mode 100644 index 00000000..80ea78a0 --- /dev/null +++ b/src/main/java/org/frankframework/flow/common/properties/HazelcastProperties.java @@ -0,0 +1,12 @@ +package org.frankframework.flow.common.properties; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@Setter +@ConfigurationProperties(prefix = "hazelcast") +public class HazelcastProperties { + private boolean enabled = false; +} diff --git a/src/main/java/org/frankframework/flow/hazelcast/ConfigurationsDirectory.java b/src/main/java/org/frankframework/flow/hazelcast/ConfigurationsDirectory.java new file mode 100644 index 00000000..e23ac63f --- /dev/null +++ b/src/main/java/org/frankframework/flow/hazelcast/ConfigurationsDirectory.java @@ -0,0 +1,93 @@ +package org.frankframework.flow.hazelcast; + + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import jakarta.annotation.PostConstruct; + +import lombok.extern.slf4j.Slf4j; + +import org.frankframework.management.bus.BusAction; +import org.frankframework.management.bus.BusTopic; +import org.frankframework.management.bus.message.JsonMessage; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.SubscribableChannel; +import org.springframework.stereotype.Component; + + +@Slf4j +@Component +@ConditionalOnProperty(name = "configurations.directory", matchIfMissing = false) +public class ConfigurationsDirectory implements MessageHandler { + + @Value("${configurations.directory:}") + private String configurationsDirectory; + + private final SubscribableChannel managementBusChannel; + + public ConfigurationsDirectory(@Qualifier("frank-management-bus") MessageChannel managementBusChannel) { + this.managementBusChannel = (SubscribableChannel) managementBusChannel; + } + + @PostConstruct + public void subscribe() { + managementBusChannel.subscribe(this); + log.debug("Subscribed to frank-management-bus for CONFIGURATION/FIND"); + } + + @Override + public void handleMessage(Message message) { + String topic = (String) message.getHeaders().get(BusTopic.TOPIC_HEADER_NAME); + String action = (String) message.getHeaders().get(BusAction.ACTION_HEADER_NAME); + + if (!BusTopic.CONFIGURATION.name().equalsIgnoreCase(topic) + || !BusAction.FIND.name().equalsIgnoreCase(action)) { + return; + } + + Object replyChannelObj = message.getHeaders().getReplyChannel(); + if (!(replyChannelObj instanceof MessageChannel replyChannel)) { + log.warn("No reply channel in message headers, cannot respond"); + return; + } + + try { + Message response = buildConfigurationsResponse(); + replyChannel.send(response); + } catch (Exception e) { + log.error("Error building configurations response", e); + } + } + + private Message buildConfigurationsResponse() { + List configurations = new ArrayList<>(); + for (File folder : resolveConfigurationsDir().listFiles()) { + HazelcastConfigurationDTO dto = new HazelcastConfigurationDTO(); + dto.setName(folder.getName()); + dto.setDirectory(folder.getAbsolutePath()); + configurations.add(dto); + } + return new JsonMessage(configurations); + } + + private File resolveConfigurationsDir() { + log.debug("using configurations.directory [{}]", configurationsDirectory); + File dir = new File(configurationsDirectory); + + if (!dir.exists()) { + throw new IllegalStateException("path [" + configurationsDirectory + "] doesn't not exist"); + } + if (!dir.isDirectory()) { + throw new IllegalStateException("path [" + configurationsDirectory + "] is not a directory"); + } + return dir; + } +} diff --git a/src/main/java/org/frankframework/flow/hazelcast/FrankInstanceDTO.java b/src/main/java/org/frankframework/flow/hazelcast/FrankInstanceDTO.java new file mode 100644 index 00000000..f0cf5d9b --- /dev/null +++ b/src/main/java/org/frankframework/flow/hazelcast/FrankInstanceDTO.java @@ -0,0 +1,5 @@ +package org.frankframework.flow.hazelcast; + +import java.util.List; + +public record FrankInstanceDTO(String name, String id, List projectPaths, boolean local) {} diff --git a/src/main/java/org/frankframework/flow/hazelcast/HazelcastConfigurationDTO.java b/src/main/java/org/frankframework/flow/hazelcast/HazelcastConfigurationDTO.java new file mode 100644 index 00000000..2fde706d --- /dev/null +++ b/src/main/java/org/frankframework/flow/hazelcast/HazelcastConfigurationDTO.java @@ -0,0 +1,20 @@ +package org.frankframework.flow.hazelcast; + +import lombok.Getter; +import lombok.Setter; + +public class HazelcastConfigurationDTO { + + private @Getter @Setter String name; + private @Getter @Setter String version; + private @Getter @Setter boolean stubbed; + private @Getter @Setter String type; + private @Getter @Setter String directory; + + private @Getter @Setter String parent; + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/org/frankframework/flow/hazelcast/HazelcastController.java b/src/main/java/org/frankframework/flow/hazelcast/HazelcastController.java new file mode 100644 index 00000000..4855958f --- /dev/null +++ b/src/main/java/org/frankframework/flow/hazelcast/HazelcastController.java @@ -0,0 +1,27 @@ +package org.frankframework.flow.hazelcast; + +import java.util.List; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; + +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; + +@RestController +@RequestMapping("/hazelcast") +@ConditionalOnBean(HazelcastService.class) +public class HazelcastController { + + private final HazelcastService hazelcastService; + + public HazelcastController(HazelcastService hazelcastService) { + this.hazelcastService = hazelcastService; + } + + @GetMapping("/instances") + public ResponseEntity> getInstances() { + return ResponseEntity.ok(hazelcastService.getRemoteInstances()); + } +} diff --git a/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java b/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java new file mode 100644 index 00000000..0b8e1937 --- /dev/null +++ b/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java @@ -0,0 +1,200 @@ +package org.frankframework.flow.hazelcast; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import lombok.extern.slf4j.Slf4j; + +import org.frankframework.management.bus.BusAction; +import org.frankframework.management.bus.BusMessageUtils; +import org.frankframework.management.bus.BusTopic; +import org.frankframework.management.bus.OutboundGateway; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.Message; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@ConditionalOnProperty(name = "hazelcast.enabled", havingValue = "true") +public class HazelcastService { + + private static final String WORKER_TYPE = "WORKER"; + private static final String CONFIGURATIONS_SUBPATH = "src/main/configurations"; + private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(2)) + .build(); + + @Value("${configurations.directory:}") + private String configurationsDirectory; + + @Value("${frank.base.url:}") + private String frankBaseUrl; + + private final ObjectProvider outboundGatewayProvider; + private final ObjectMapper objectMapper; + + public HazelcastService(ObjectProvider outboundGatewayProvider, ObjectMapper objectMapper) { + this.outboundGatewayProvider = outboundGatewayProvider; + this.objectMapper = objectMapper; + } + + public List getRemoteInstances() { + OutboundGateway gateway = outboundGatewayProvider.getIfAvailable(); + if (gateway == null) { + log.warn("OutboundGateway is not available"); + return List.of(); + } + + List members = gateway.getMembers(); + log.debug("Hazelcast cluster members: {}", members.size()); + members.forEach(m -> log.debug(" member: name={} address={} type={}", m.getName(), m.getAddress(), m.getType())); + + if (members.isEmpty()) { + return fetchLocalInstance(gateway); + } + + List result = new ArrayList<>(); + for (OutboundGateway.ClusterMember member : members) { + try { + List projectPaths = fetchProjectPaths(gateway, member); + if (!projectPaths.isEmpty()) { + result.add(new FrankInstanceDTO(member.getName(), member.getId().toString(), projectPaths, false)); + } + } catch (Exception e) { + log.debug("Member [{}] did not respond ({}), skipping", member.getName(), e.getMessage()); + } + } + return result; + } + + private List fetchLocalInstance(OutboundGateway outboundGateway) { + if (configurationsDirectory == null || configurationsDirectory.isBlank()) { + return List.of(); + } + if (!Files.isDirectory(Path.of(configurationsDirectory))) { + log.debug("Configurations directory [{}] does not exist", configurationsDirectory); + return List.of(); + } + if (!isFrankAlive()) { + log.debug("Frank at [{}] did not respond", frankBaseUrl); + return List.of(); + } + try { + Message request = MessageBuilder.withPayload("NONE") + .setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()) + .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()) + .build(); + Message response = outboundGateway.sendSyncMessage(request); + List projectPaths = parseProjectPaths(response.getPayload()); + String name = projectPaths.isEmpty() ? "local" : Path.of(projectPaths.getFirst()).getFileName().toString(); + return List.of(new FrankInstanceDTO(name, null, projectPaths, true)); + } catch (Exception e) { + log.debug("Failed to fetch local configurations: {}", e.getMessage()); + return List.of(); + } + } + + private boolean isFrankAlive() { + if (frankBaseUrl == null || frankBaseUrl.isBlank()) { + log.debug("frank.base.url not configured, cannot check liveness"); + return false; + } + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(frankBaseUrl + "/iaf/api/server/health")) + .timeout(Duration.ofSeconds(2)) + .GET() + .build(); + HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + // Verify it's actually Frank by checking for the "status" field in the JSON response body + return response.body() != null && response.body().contains("\"status\""); + } catch (Exception e) { + log.debug("Frank liveness check failed for [{}]: {}", frankBaseUrl, e.getMessage()); + return false; + } + } + + private List fetchProjectPaths(OutboundGateway outboundGateway, OutboundGateway.ClusterMember member) { + log.debug("Fetching project paths from member name=[{}] id=[{}] type=[{}] address=[{}]", + member.getName(), member.getId(), member.getType(), member.getAddress()); + Message request = MessageBuilder.withPayload("NONE") + .setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()) + .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()) + .setHeader(BusMessageUtils.HEADER_TARGET_KEY, member.getId().toString()) + .build(); + Message response = outboundGateway.sendSyncMessage(request); + log.debug("Response payload from member [{}]: {}", member.getName(), response.getPayload()); + return parseProjectPaths(response.getPayload()); + } + + /** + * Parses the bus response payload into a list of unique project root paths. + * The payload is expected to be a JSON array of {@link HazelcastConfigurationDTO} objects, + * each containing a {@code directory} field pointing to an individual configuration folder. + */ + private List parseProjectPaths(Object payload) { + if (payload == null) { + return List.of(); + } + try { + String json = payload instanceof String s ? s : objectMapper.writeValueAsString(payload); + HazelcastConfigurationDTO[] configs = objectMapper.readValue(json, HazelcastConfigurationDTO[].class); + Set projectRoots = new LinkedHashSet<>(); + for (HazelcastConfigurationDTO config : configs) { + String dir = config.getDirectory(); + if (dir != null && !dir.isBlank()) { + String projectRoot = deriveProjectRoot(dir); + if (projectRoot != null) { + projectRoots.add(projectRoot); + } + } + } + return new ArrayList<>(projectRoots); + } catch (Exception e) { + log.debug("Failed to parse configuration payload: {}", e.getMessage()); + return List.of(); + } + } + + /** + * Derives the Frank project root from a configuration subdirectory path. + *
    + *
  • Maven layout: {@code /project/src/main/configurations/ConfigName} → {@code /project}
  • + *
  • Standard layout: {@code /project/configurations/ConfigName} → {@code /project}
  • + *
+ */ + private String deriveProjectRoot(String configDirectory) { + if (configDirectory == null || configDirectory.isBlank()) { + return null; + } + String normalized = configDirectory.replace("\\", "/"); + // Maven layout: strip everything from src/main/configurations onward + int idx = normalized.indexOf(CONFIGURATIONS_SUBPATH); + if (idx >= 0) { + return configDirectory.substring(0, idx).replaceAll("[/\\\\]+$", ""); + } + // Standard layout: configDirectory is one level below the configurations folder, + // which is one level below the project root → go up two levels + Path path = Path.of(configDirectory); + Path configurationsDir = path.getParent(); + if (configurationsDir != null && configurationsDir.getParent() != null) { + return configurationsDir.getParent().toString(); + } + return configurationsDir != null ? configurationsDir.toString() : configDirectory; + } +} diff --git a/src/main/java/org/frankframework/flow/security/UserWorkspaceContext.java b/src/main/java/org/frankframework/flow/security/UserWorkspaceContext.java index c1afbf8a..0d9da07b 100644 --- a/src/main/java/org/frankframework/flow/security/UserWorkspaceContext.java +++ b/src/main/java/org/frankframework/flow/security/UserWorkspaceContext.java @@ -3,11 +3,12 @@ import java.io.Serializable; import lombok.Getter; import lombok.Setter; +import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; @Component -@RequestScope +@RequestScope(proxyMode = ScopedProxyMode.TARGET_CLASS) @Getter @Setter public class UserWorkspaceContext implements Serializable { diff --git a/src/main/resources/FrankFlowContext.xml b/src/main/resources/FrankFlowContext.xml new file mode 100644 index 00000000..409bf50d --- /dev/null +++ b/src/main/resources/FrankFlowContext.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + diff --git a/src/main/resources/SpringBootContext.xml b/src/main/resources/SpringBootContext.xml new file mode 100644 index 00000000..97f62f22 --- /dev/null +++ b/src/main/resources/SpringBootContext.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a9a26def..a2c4f04a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,3 +5,6 @@ spring.web.resources.static-locations=classpath:/frontend/ spring.servlet.multipart.max-file-size=50MB spring.servlet.multipart.max-request-size=50MB + +hazelcast.enabled=false + From 25a68a7c7742b41733c81ae06c61c3edcd4e6034 Mon Sep 17 00:00:00 2001 From: stijnpotters Date: Thu, 19 Mar 2026 20:49:53 +0100 Subject: [PATCH 02/20] fix: improved code to prefinal version --- .../hazelcast/HazelcastConfigurationDTO.java | 3 + .../flow/hazelcast/HazelcastService.java | 117 +++++++++++++++--- 2 files changed, 101 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/frankframework/flow/hazelcast/HazelcastConfigurationDTO.java b/src/main/java/org/frankframework/flow/hazelcast/HazelcastConfigurationDTO.java index 2fde706d..3d5c0f2b 100644 --- a/src/main/java/org/frankframework/flow/hazelcast/HazelcastConfigurationDTO.java +++ b/src/main/java/org/frankframework/flow/hazelcast/HazelcastConfigurationDTO.java @@ -1,8 +1,11 @@ package org.frankframework.flow.hazelcast; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + import lombok.Getter; import lombok.Setter; +@JsonIgnoreProperties(ignoreUnknown = true) public class HazelcastConfigurationDTO { private @Getter @Setter String name; diff --git a/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java b/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java index 0b8e1937..aa63c216 100644 --- a/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java +++ b/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java @@ -1,5 +1,6 @@ package org.frankframework.flow.hazelcast; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.net.URI; @@ -62,14 +63,13 @@ public List getRemoteInstances() { List members = gateway.getMembers(); log.debug("Hazelcast cluster members: {}", members.size()); - members.forEach(m -> log.debug(" member: name={} address={} type={}", m.getName(), m.getAddress(), m.getType())); - - if (members.isEmpty()) { - return fetchLocalInstance(gateway); - } + members.forEach(m -> log.debug(" member: name={} address={} type={} attributes={}", m.getName(), m.getAddress(), m.getType(), m.getAttributes())); List result = new ArrayList<>(); for (OutboundGateway.ClusterMember member : members) { + if (!WORKER_TYPE.equalsIgnoreCase(member.getType())) { + continue; + } try { List projectPaths = fetchProjectPaths(gateway, member); if (!projectPaths.isEmpty()) { @@ -79,33 +79,112 @@ public List getRemoteInstances() { log.debug("Member [{}] did not respond ({}), skipping", member.getName(), e.getMessage()); } } + + // No WORKER members responded — fall back to frank.base.url-based discovery + if (result.isEmpty()) { + result.addAll(fetchLocalInstance(gateway)); + } return result; } private List fetchLocalInstance(OutboundGateway outboundGateway) { - if (configurationsDirectory == null || configurationsDirectory.isBlank()) { - return List.of(); - } - if (!Files.isDirectory(Path.of(configurationsDirectory))) { - log.debug("Configurations directory [{}] does not exist", configurationsDirectory); + if (frankBaseUrl == null || frankBaseUrl.isBlank()) { return List.of(); } if (!isFrankAlive()) { log.debug("Frank at [{}] did not respond", frankBaseUrl); return List.of(); } + + // If configurations.directory is set, query the local management bus for project paths + if (configurationsDirectory != null && !configurationsDirectory.isBlank() + && Files.isDirectory(Path.of(configurationsDirectory))) { + try { + Message request = MessageBuilder.withPayload("NONE") + .setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()) + .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()) + .build(); + Message response = outboundGateway.sendSyncMessage(request); + List projectPaths = parseProjectPaths(response.getPayload()); + String name = projectPaths.isEmpty() ? fetchInstanceName() : Path.of(projectPaths.getFirst()).getFileName().toString(); + return List.of(new FrankInstanceDTO(name, null, projectPaths, true)); + } catch (Exception e) { + log.debug("Failed to fetch local configurations via bus: {}", e.getMessage()); + } + } + + // Fall back: query Frank HTTP API directly for name and configuration paths + return fetchInstanceViaHttp(); + } + + private List fetchInstanceViaHttp() { + String name = fetchInstanceName(); + List projectPaths = fetchProjectPathsViaHttp(); + if (projectPaths.isEmpty()) { + log.debug("No project paths found for Frank at [{}]", frankBaseUrl); + } + return List.of(new FrankInstanceDTO(name, null, projectPaths, true)); + } + + private String fetchInstanceName() { + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(frankBaseUrl + "/iaf/api/server/info")) + .timeout(Duration.ofSeconds(2)) + .GET() + .build(); + HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200) { + JsonNode root = objectMapper.readTree(response.body()); + // Try multiple possible response structures + for (String path : new String[]{"instance/name", "instanceName", "name"}) { + String[] parts = path.split("/"); + JsonNode node = root; + for (String part : parts) { + node = node.path(part); + } + String text = node.asText(null); + if (text != null && !text.isBlank() && !"null".equals(text)) { + return text; + } + } + } + } catch (Exception e) { + log.debug("Failed to fetch instance name from [{}]: {}", frankBaseUrl, e.getMessage()); + } + return deriveName(frankBaseUrl); + } + + private List fetchProjectPathsViaHttp() { try { - Message request = MessageBuilder.withPayload("NONE") - .setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()) - .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()) + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(frankBaseUrl + "/iaf/api/server/configurations")) + .timeout(Duration.ofSeconds(2)) + .header("Accept", "application/json") + .GET() .build(); - Message response = outboundGateway.sendSyncMessage(request); - List projectPaths = parseProjectPaths(response.getPayload()); - String name = projectPaths.isEmpty() ? "local" : Path.of(projectPaths.getFirst()).getFileName().toString(); - return List.of(new FrankInstanceDTO(name, null, projectPaths, true)); + HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200 && response.body().trim().startsWith("[")) { + return parseProjectPaths(response.body()); + } } catch (Exception e) { - log.debug("Failed to fetch local configurations: {}", e.getMessage()); - return List.of(); + log.debug("Failed to fetch configurations: {}", e.getMessage()); + } + return List.of(); + } + + + private String deriveName(String url) { + try { + URI uri = URI.create(url); + String host = uri.getHost(); + int port = uri.getPort(); + if (port > 0 && port != 80 && port != 443) { + return host + ":" + port; + } + return host != null ? host : url; + } catch (Exception e) { + return url; } } From 5dffdfea78e64d1313ae07222f1178253fe38c03 Mon Sep 17 00:00:00 2001 From: stijnpotters Date: Tue, 24 Mar 2026 13:36:25 +0100 Subject: [PATCH 03/20] fix: improved hazelcast connection logic --- .../flow/FlowWarInitializer.java | 21 +- .../common/config/ConfigurationsConfig.java | 39 +-- ...tationConfig.java => HazelcastConfig.java} | 30 +-- .../config/SecurityChainConfigurer.java | 50 ++-- .../flow/common/config/WebConfiguration.java | 34 +-- .../properties/HazelcastProperties.java | 12 - .../hazelcast/ConfigurationsDirectory.java | 77 +++--- .../hazelcast/HazelcastConfigurationDTO.java | 19 +- .../flow/hazelcast/HazelcastController.java | 2 - .../flow/hazelcast/HazelcastService.java | 239 ++---------------- src/main/resources/FrankFlowContext.xml | 21 -- ...itional-spring-configuration-metadata.json | 10 + src/main/resources/SpringBootContext.xml | 17 -- .../resources/application-local.properties | 4 + 14 files changed, 143 insertions(+), 432 deletions(-) rename src/main/java/org/frankframework/flow/common/config/{AnnotationConfig.java => HazelcastConfig.java} (52%) delete mode 100644 src/main/java/org/frankframework/flow/common/properties/HazelcastProperties.java delete mode 100644 src/main/resources/FrankFlowContext.xml create mode 100644 src/main/resources/META-INF/additional-spring-configuration-metadata.json delete mode 100644 src/main/resources/SpringBootContext.xml create mode 100644 src/main/resources/application-local.properties diff --git a/src/main/java/org/frankframework/flow/FlowWarInitializer.java b/src/main/java/org/frankframework/flow/FlowWarInitializer.java index a8411d35..7b052e30 100644 --- a/src/main/java/org/frankframework/flow/FlowWarInitializer.java +++ b/src/main/java/org/frankframework/flow/FlowWarInitializer.java @@ -1,20 +1,21 @@ package org.frankframework.flow; /* - Copyright 2023 WeAreFrank! +Copyright 2023 WeAreFrank! - 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 +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 + 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. +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. */ +import org.jspecify.annotations.NullMarked; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; diff --git a/src/main/java/org/frankframework/flow/common/config/ConfigurationsConfig.java b/src/main/java/org/frankframework/flow/common/config/ConfigurationsConfig.java index de02ca84..cbde39ea 100644 --- a/src/main/java/org/frankframework/flow/common/config/ConfigurationsConfig.java +++ b/src/main/java/org/frankframework/flow/common/config/ConfigurationsConfig.java @@ -1,8 +1,7 @@ package org.frankframework.flow.common.config; -import java.util.ArrayList; +import jakarta.annotation.PostConstruct; import java.util.List; - import org.frankframework.flow.exception.ApiException; import org.frankframework.flow.hazelcast.HazelcastConfigurationDTO; import org.frankframework.management.bus.BusAction; @@ -10,7 +9,6 @@ import org.frankframework.management.bus.BusTopic; import org.frankframework.management.bus.OutboundGateway; import org.frankframework.util.JacksonUtils; -import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -19,14 +17,14 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.SessionScope; - @Component @SessionScope @ConditionalOnProperty(name = "hazelcast.enabled", havingValue = "true") -public class ConfigurationsConfig implements InitializingBean { +public class ConfigurationsConfig { + private static final String DEFAULT_FF_CONFIGURATION_PREFIX = "IAF_"; - private List configurations = new ArrayList<>(); + private List configurations = List.of(); private final OutboundGateway outboundGateway; @@ -34,33 +32,36 @@ public ConfigurationsConfig(OutboundGateway outboundGateway) { this.outboundGateway = outboundGateway; } - - @Override - public void afterPropertiesSet() throws Exception { - Message request = MessageBuilder.withPayload("NONE").setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()).setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()).build(); + @PostConstruct + public void init() throws ApiException { + Message request = MessageBuilder.withPayload("NONE") + .setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()) + .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()) + .build(); Message response = outboundGateway.sendSyncMessage(request); - List configs = getConfigurations(response); - - configurations = configs.stream().filter(e -> !e.getName().startsWith(DEFAULT_FF_CONFIGURATION_PREFIX)).toList(); + configurations = parseConfigurations(response).stream() + .filter(config -> !config.name().startsWith(DEFAULT_FF_CONFIGURATION_PREFIX)) + .toList(); } - private List getConfigurations(Message response) throws ApiException { - if(MediaType.APPLICATION_JSON_VALUE.equals(response.getHeaders().get(BusMessageUtils.HEADER_PREFIX+"type"))) { + private List parseConfigurations(Message response) throws ApiException { + String contentType = (String) response.getHeaders().get(BusMessageUtils.HEADER_PREFIX + "type"); + if (MediaType.APPLICATION_JSON_VALUE.equals(contentType)) { HazelcastConfigurationDTO[] arr = JacksonUtils.convertToDTO(response.getPayload(), HazelcastConfigurationDTO[].class); - return List.of(arr); //TODO new TypeReference>(){} + return List.of(arr); } - throw new ApiException("unexpected result returned by Bus", HttpStatus.INTERNAL_SERVER_ERROR); + throw new ApiException("Unexpected result returned by Bus", HttpStatus.INTERNAL_SERVER_ERROR); } public List getAllConfigurations() { - return configurations.stream().map(HazelcastConfigurationDTO::getName).toList(); + return configurations.stream().map(HazelcastConfigurationDTO::name).toList(); } public HazelcastConfigurationDTO getConfiguration(String name) throws ApiException { return configurations.stream() - .filter(e -> e.getName().equals(name)) + .filter(config -> config.name().equals(name)) .findFirst() .orElseThrow(() -> new ApiException("configuration not found", HttpStatus.NOT_FOUND)); } diff --git a/src/main/java/org/frankframework/flow/common/config/AnnotationConfig.java b/src/main/java/org/frankframework/flow/common/config/HazelcastConfig.java similarity index 52% rename from src/main/java/org/frankframework/flow/common/config/AnnotationConfig.java rename to src/main/java/org/frankframework/flow/common/config/HazelcastConfig.java index 9cfb4c88..e00254bf 100644 --- a/src/main/java/org/frankframework/flow/common/config/AnnotationConfig.java +++ b/src/main/java/org/frankframework/flow/common/config/HazelcastConfig.java @@ -1,47 +1,41 @@ package org.frankframework.flow.common.config; import org.apache.commons.lang3.StringUtils; - import org.frankframework.management.bus.LocalGateway; import org.frankframework.management.bus.OutboundGatewayFactory; import org.frankframework.management.gateway.HazelcastOutboundGateway; import org.frankframework.management.security.JwtKeyGenerator; - +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Scope; import org.springframework.integration.channel.PublishSubscribeChannel; -import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.SubscribableChannel; @Configuration @ConditionalOnProperty(name = "hazelcast.enabled", havingValue = "true") -public class AnnotationConfig { +public class HazelcastConfig { - private final ApplicationContext applicationContext; - - public AnnotationConfig(ApplicationContext applicationContext) { - this.applicationContext = applicationContext; - } + @Value("${configurations.directory:}") + private String configurationsDirectory; @Bean public JwtKeyGenerator jwtKeyGenerator() { return new JwtKeyGenerator(); } - @Bean(name = "frank-management-bus") - @ConditionalOnProperty(name = "configurations.directory", matchIfMissing = false) - public MessageChannel frankManagementBus() { + @Bean("frank-management-bus") + @ConditionalOnProperty(name = "configurations.directory") + public SubscribableChannel frankManagementBus() { return new PublishSubscribeChannel(); } @Bean - @Scope("singleton") - public OutboundGatewayFactory createOutboundGatewayFactory() { + public OutboundGatewayFactory outboundGatewayFactory() { OutboundGatewayFactory factory = new OutboundGatewayFactory(); - String configDirectory = applicationContext.getEnvironment().getProperty("configurations.directory"); - String gatewayClassName = StringUtils.isEmpty(configDirectory) ? HazelcastOutboundGateway.class.getCanonicalName() : LocalGateway.class.getCanonicalName(); + String gatewayClassName = StringUtils.isNotBlank(configurationsDirectory) + ? LocalGateway.class.getCanonicalName() + : HazelcastOutboundGateway.class.getCanonicalName(); factory.setGatewayClassname(gatewayClassName); return factory; } diff --git a/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java b/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java index 8dcce80c..359bf359 100644 --- a/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java +++ b/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java @@ -1,18 +1,12 @@ package org.frankframework.flow.common.config; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import java.util.Set; - import org.frankframework.lifecycle.DynamicRegistration.Servlet; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; -import org.springframework.core.env.Environment; import org.springframework.security.authorization.AuthenticatedAuthorizationManager; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -25,46 +19,34 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.matcher.AnyRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; - -import lombok.Setter; /** - * Enable security, although.. it's anonymous on all endpoints, but at least sets the - * SecurityContextHolder.getContext().getAuthentication(); object. + * Enable security, although it's anonymous on all endpoints, but at least sets the + * SecurityContextHolder.getContext().getAuthentication() object. */ @Configuration -@EnableWebSecurity //Enables Spring Security (classpath) -@EnableMethodSecurity(jsr250Enabled = true, prePostEnabled = false) //Enables JSR 250 (JAX-RS) annotations +@EnableWebSecurity +@EnableMethodSecurity(jsr250Enabled = true, prePostEnabled = false) @Order(Ordered.HIGHEST_PRECEDENCE) -public class SecurityChainConfigurer implements ApplicationContextAware, EnvironmentAware { - private @Setter ApplicationContext applicationContext; - private @Setter Environment environment; +public class SecurityChainConfigurer { @Bean - public SecurityFilterChain configureChain(HttpSecurity http) throws Exception { - //Apply defaults to disable bloated filters, see DefaultSecurityFilterChain.getFilters for the actual list. - http.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)); //Allow same origin iframe request + public SecurityFilterChain configureChain(HttpSecurity http) { + http.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)); http.csrf(CsrfConfigurer::disable); - RequestMatcher securityRequestMatcher = AnyRequestMatcher.INSTANCE; - http.securityMatcher(securityRequestMatcher); //Triggers the SecurityFilterChain, also for OPTIONS requests! - http.formLogin(FormLoginConfigurer::disable); //Disable the form login filter - http.logout(LogoutConfigurer::disable); //Disable the logout endpoint on every filter - + http.securityMatcher(AnyRequestMatcher.INSTANCE); + http.formLogin(FormLoginConfigurer::disable); + http.logout(LogoutConfigurer::disable); http.anonymous(anonymous -> anonymous.authorities(getAuthorities())); - - // Enables security for all servlet endpoints - http.authorizeHttpRequests(requests -> requests.requestMatchers(securityRequestMatcher).access(AuthenticatedAuthorizationManager.anonymous())); + http.authorizeHttpRequests(requests -> + requests.requestMatchers(AnyRequestMatcher.INSTANCE).access(AuthenticatedAuthorizationManager.anonymous())); return http.build(); } private List getAuthorities() { - Set securityRoles = Set.of(Servlet.ALL_IBIS_USER_ROLES); - List grantedAuthorities = new ArrayList<>(securityRoles.size()); - for (String role : securityRoles) { - grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role)); - } - return grantedAuthorities; + return Arrays.stream(Servlet.ALL_IBIS_USER_ROLES) + .map(role -> (GrantedAuthority) new SimpleGrantedAuthority("ROLE_" + role)) + .toList(); } } diff --git a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java index 16eb5a36..27b4eeea 100644 --- a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java +++ b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java @@ -2,31 +2,19 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; - import org.frankframework.management.gateway.InputStreamHttpMessageConverter; - import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; -import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; -import org.springframework.web.filter.RequestContextFilter; import org.springframework.web.method.HandlerTypePredicate; -import org.springframework.web.multipart.support.StandardServletMultipartResolver; import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import java.util.List; - @Configuration -@EnableWebMvc public class WebConfiguration implements WebMvcConfigurer { private static final long MAX_AGE_SECONDS = 3600; @@ -59,26 +47,8 @@ public RestTemplate restTemplate() { return new RestTemplate(); } - @Override - public void configureMessageConverters(List> converters) { - converters.add(new MappingJackson2HttpMessageConverter(objectMapper())); - converters.add(new InputStreamHttpMessageConverter()); - converters.add(new FormHttpMessageConverter()); - } - - @Bean - StandardServletMultipartResolver multipartResolver() { - return new StandardServletMultipartResolver(); - } - - /** - * Explicitly register RequestContextFilter since @EnableWebMvc disables WebMvcAutoConfiguration - * which normally registers it. Without this, request-scoped beans are not accessible in filters. - */ @Bean - public FilterRegistrationBean requestContextFilter() { - FilterRegistrationBean registration = new FilterRegistrationBean<>(new RequestContextFilter()); - registration.setOrder(Ordered.HIGHEST_PRECEDENCE); - return registration; + public HttpMessageConverter inputStreamHttpMessageConverter() { + return new InputStreamHttpMessageConverter(); } } diff --git a/src/main/java/org/frankframework/flow/common/properties/HazelcastProperties.java b/src/main/java/org/frankframework/flow/common/properties/HazelcastProperties.java deleted file mode 100644 index 80ea78a0..00000000 --- a/src/main/java/org/frankframework/flow/common/properties/HazelcastProperties.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.frankframework.flow.common.properties; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@Getter -@Setter -@ConfigurationProperties(prefix = "hazelcast") -public class HazelcastProperties { - private boolean enabled = false; -} diff --git a/src/main/java/org/frankframework/flow/hazelcast/ConfigurationsDirectory.java b/src/main/java/org/frankframework/flow/hazelcast/ConfigurationsDirectory.java index e23ac63f..83c0c746 100644 --- a/src/main/java/org/frankframework/flow/hazelcast/ConfigurationsDirectory.java +++ b/src/main/java/org/frankframework/flow/hazelcast/ConfigurationsDirectory.java @@ -1,31 +1,27 @@ package org.frankframework.flow.hazelcast; - +import jakarta.annotation.PostConstruct; import java.io.File; -import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; - -import jakarta.annotation.PostConstruct; - import lombok.extern.slf4j.Slf4j; - import org.frankframework.management.bus.BusAction; import org.frankframework.management.bus.BusTopic; import org.frankframework.management.bus.message.JsonMessage; - +import org.jspecify.annotations.NullMarked; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.SubscribableChannel; import org.springframework.stereotype.Component; - @Slf4j @Component -@ConditionalOnProperty(name = "configurations.directory", matchIfMissing = false) +@ConditionalOnExpression("'${configurations.directory:}'.trim() != ''") public class ConfigurationsDirectory implements MessageHandler { @Value("${configurations.directory:}") @@ -33,61 +29,70 @@ public class ConfigurationsDirectory implements MessageHandler { private final SubscribableChannel managementBusChannel; - public ConfigurationsDirectory(@Qualifier("frank-management-bus") MessageChannel managementBusChannel) { - this.managementBusChannel = (SubscribableChannel) managementBusChannel; + public ConfigurationsDirectory(@Qualifier("frank-management-bus") SubscribableChannel managementBusChannel) { + this.managementBusChannel = managementBusChannel; } @PostConstruct public void subscribe() { managementBusChannel.subscribe(this); - log.debug("Subscribed to frank-management-bus for CONFIGURATION/FIND"); } @Override + @NullMarked public void handleMessage(Message message) { - String topic = (String) message.getHeaders().get(BusTopic.TOPIC_HEADER_NAME); - String action = (String) message.getHeaders().get(BusAction.ACTION_HEADER_NAME); - - if (!BusTopic.CONFIGURATION.name().equalsIgnoreCase(topic) - || !BusAction.FIND.name().equalsIgnoreCase(action)) { + if (!isConfigurationFindRequest(message)) { return; } - Object replyChannelObj = message.getHeaders().getReplyChannel(); - if (!(replyChannelObj instanceof MessageChannel replyChannel)) { + Object replyChannelObject = message.getHeaders().getReplyChannel(); + if (!(replyChannelObject instanceof MessageChannel replyChannel)) { log.warn("No reply channel in message headers, cannot respond"); return; } try { - Message response = buildConfigurationsResponse(); - replyChannel.send(response); + replyChannel.send(buildConfigurationsResponse()); } catch (Exception e) { log.error("Error building configurations response", e); } } + private boolean isConfigurationFindRequest(Message message) { + String topic = (String) message.getHeaders().get(BusTopic.TOPIC_HEADER_NAME); + String action = (String) message.getHeaders().get(BusAction.ACTION_HEADER_NAME); + + return BusTopic.CONFIGURATION.name().equalsIgnoreCase(topic) + && BusAction.FIND.name().equalsIgnoreCase(action); + } + private Message buildConfigurationsResponse() { - List configurations = new ArrayList<>(); - for (File folder : resolveConfigurationsDir().listFiles()) { - HazelcastConfigurationDTO dto = new HazelcastConfigurationDTO(); - dto.setName(folder.getName()); - dto.setDirectory(folder.getAbsolutePath()); - configurations.add(dto); + File directory = resolveConfigurationsDirectory(); + File[] folders = directory.listFiles(); + + if (folders == null) { + log.warn("Failed to list files in configurations directory [{}]", configurationsDirectory); + return new JsonMessage(Collections.emptyList()); } + + List configurations = Arrays.stream(folders) + .map(folder -> new HazelcastConfigurationDTO(folder.getName(), folder.getAbsolutePath())) + .toList(); + return new JsonMessage(configurations); } - private File resolveConfigurationsDir() { - log.debug("using configurations.directory [{}]", configurationsDirectory); - File dir = new File(configurationsDirectory); + private File resolveConfigurationsDirectory() { + File directory = new File(configurationsDirectory); - if (!dir.exists()) { - throw new IllegalStateException("path [" + configurationsDirectory + "] doesn't not exist"); + if (!directory.exists()) { + throw new IllegalStateException("Path [" + configurationsDirectory + "] does not exist"); } - if (!dir.isDirectory()) { - throw new IllegalStateException("path [" + configurationsDirectory + "] is not a directory"); + + if (!directory.isDirectory()) { + throw new IllegalStateException("Path [" + configurationsDirectory + "] is not a directory"); } - return dir; + + return directory; } } diff --git a/src/main/java/org/frankframework/flow/hazelcast/HazelcastConfigurationDTO.java b/src/main/java/org/frankframework/flow/hazelcast/HazelcastConfigurationDTO.java index 3d5c0f2b..1354d6cd 100644 --- a/src/main/java/org/frankframework/flow/hazelcast/HazelcastConfigurationDTO.java +++ b/src/main/java/org/frankframework/flow/hazelcast/HazelcastConfigurationDTO.java @@ -2,22 +2,5 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Getter; -import lombok.Setter; - @JsonIgnoreProperties(ignoreUnknown = true) -public class HazelcastConfigurationDTO { - - private @Getter @Setter String name; - private @Getter @Setter String version; - private @Getter @Setter boolean stubbed; - private @Getter @Setter String type; - private @Getter @Setter String directory; - - private @Getter @Setter String parent; - - @Override - public String toString() { - return name; - } -} +public record HazelcastConfigurationDTO(String name, String directory) {} diff --git a/src/main/java/org/frankframework/flow/hazelcast/HazelcastController.java b/src/main/java/org/frankframework/flow/hazelcast/HazelcastController.java index 4855958f..4cd894e8 100644 --- a/src/main/java/org/frankframework/flow/hazelcast/HazelcastController.java +++ b/src/main/java/org/frankframework/flow/hazelcast/HazelcastController.java @@ -1,9 +1,7 @@ package org.frankframework.flow.hazelcast; import java.util.List; - import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; - import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; diff --git a/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java b/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java index aa63c216..7a426271 100644 --- a/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java +++ b/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java @@ -1,32 +1,17 @@ package org.frankframework.flow.hazelcast; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; - -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Duration; import java.util.ArrayList; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; - import lombok.extern.slf4j.Slf4j; - import org.frankframework.management.bus.BusAction; import org.frankframework.management.bus.BusMessageUtils; import org.frankframework.management.bus.BusTopic; import org.frankframework.management.bus.OutboundGateway; - import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; import org.springframework.stereotype.Service; @Slf4j @@ -35,16 +20,6 @@ public class HazelcastService { private static final String WORKER_TYPE = "WORKER"; - private static final String CONFIGURATIONS_SUBPATH = "src/main/configurations"; - private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder() - .connectTimeout(Duration.ofSeconds(2)) - .build(); - - @Value("${configurations.directory:}") - private String configurationsDirectory; - - @Value("${frank.base.url:}") - private String frankBaseUrl; private final ObjectProvider outboundGatewayProvider; private final ObjectMapper objectMapper; @@ -63,217 +38,55 @@ public List getRemoteInstances() { List members = gateway.getMembers(); log.debug("Hazelcast cluster members: {}", members.size()); - members.forEach(m -> log.debug(" member: name={} address={} type={} attributes={}", m.getName(), m.getAddress(), m.getType(), m.getAttributes())); + return collectWorkerInstances(gateway, members); + } + + private List collectWorkerInstances(OutboundGateway gateway, List members) { List result = new ArrayList<>(); for (OutboundGateway.ClusterMember member : members) { if (!WORKER_TYPE.equalsIgnoreCase(member.getType())) { continue; } + try { - List projectPaths = fetchProjectPaths(gateway, member); - if (!projectPaths.isEmpty()) { - result.add(new FrankInstanceDTO(member.getName(), member.getId().toString(), projectPaths, false)); - } + List configurations = fetchConfigurations(gateway, member); + result.add(toFrankInstance(member, configurations)); } catch (Exception e) { log.debug("Member [{}] did not respond ({}), skipping", member.getName(), e.getMessage()); } } - - // No WORKER members responded — fall back to frank.base.url-based discovery - if (result.isEmpty()) { - result.addAll(fetchLocalInstance(gateway)); - } return result; } - private List fetchLocalInstance(OutboundGateway outboundGateway) { - if (frankBaseUrl == null || frankBaseUrl.isBlank()) { - return List.of(); - } - if (!isFrankAlive()) { - log.debug("Frank at [{}] did not respond", frankBaseUrl); - return List.of(); - } - - // If configurations.directory is set, query the local management bus for project paths - if (configurationsDirectory != null && !configurationsDirectory.isBlank() - && Files.isDirectory(Path.of(configurationsDirectory))) { - try { - Message request = MessageBuilder.withPayload("NONE") - .setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()) - .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()) - .build(); - Message response = outboundGateway.sendSyncMessage(request); - List projectPaths = parseProjectPaths(response.getPayload()); - String name = projectPaths.isEmpty() ? fetchInstanceName() : Path.of(projectPaths.getFirst()).getFileName().toString(); - return List.of(new FrankInstanceDTO(name, null, projectPaths, true)); - } catch (Exception e) { - log.debug("Failed to fetch local configurations via bus: {}", e.getMessage()); - } - } - - // Fall back: query Frank HTTP API directly for name and configuration paths - return fetchInstanceViaHttp(); - } - - private List fetchInstanceViaHttp() { - String name = fetchInstanceName(); - List projectPaths = fetchProjectPathsViaHttp(); - if (projectPaths.isEmpty()) { - log.debug("No project paths found for Frank at [{}]", frankBaseUrl); - } - return List.of(new FrankInstanceDTO(name, null, projectPaths, true)); - } - - private String fetchInstanceName() { - try { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(frankBaseUrl + "/iaf/api/server/info")) - .timeout(Duration.ofSeconds(2)) - .GET() - .build(); - HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() == 200) { - JsonNode root = objectMapper.readTree(response.body()); - // Try multiple possible response structures - for (String path : new String[]{"instance/name", "instanceName", "name"}) { - String[] parts = path.split("/"); - JsonNode node = root; - for (String part : parts) { - node = node.path(part); - } - String text = node.asText(null); - if (text != null && !text.isBlank() && !"null".equals(text)) { - return text; - } - } - } - } catch (Exception e) { - log.debug("Failed to fetch instance name from [{}]: {}", frankBaseUrl, e.getMessage()); - } - return deriveName(frankBaseUrl); - } - - private List fetchProjectPathsViaHttp() { - try { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(frankBaseUrl + "/iaf/api/server/configurations")) - .timeout(Duration.ofSeconds(2)) - .header("Accept", "application/json") - .GET() - .build(); - HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() == 200 && response.body().trim().startsWith("[")) { - return parseProjectPaths(response.body()); - } - } catch (Exception e) { - log.debug("Failed to fetch configurations: {}", e.getMessage()); - } - return List.of(); - } - - - private String deriveName(String url) { - try { - URI uri = URI.create(url); - String host = uri.getHost(); - int port = uri.getPort(); - if (port > 0 && port != 80 && port != 443) { - return host + ":" + port; - } - return host != null ? host : url; - } catch (Exception e) { - return url; - } - } - - private boolean isFrankAlive() { - if (frankBaseUrl == null || frankBaseUrl.isBlank()) { - log.debug("frank.base.url not configured, cannot check liveness"); - return false; - } - try { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(frankBaseUrl + "/iaf/api/server/health")) - .timeout(Duration.ofSeconds(2)) - .GET() - .build(); - HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); - // Verify it's actually Frank by checking for the "status" field in the JSON response body - return response.body() != null && response.body().contains("\"status\""); - } catch (Exception e) { - log.debug("Frank liveness check failed for [{}]: {}", frankBaseUrl, e.getMessage()); - return false; - } - } - - private List fetchProjectPaths(OutboundGateway outboundGateway, OutboundGateway.ClusterMember member) { - log.debug("Fetching project paths from member name=[{}] id=[{}] type=[{}] address=[{}]", - member.getName(), member.getId(), member.getType(), member.getAddress()); + private List fetchConfigurations(OutboundGateway gateway, OutboundGateway.ClusterMember member) { + log.debug("Fetching configurations from member name=[{}] id=[{}]", member.getName(), member.getId()); Message request = MessageBuilder.withPayload("NONE") .setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()) .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()) - .setHeader(BusMessageUtils.HEADER_TARGET_KEY, member.getId().toString()) + .setHeader(BusMessageUtils.HEADER_TARGET_KEY, member.getId()) .build(); - Message response = outboundGateway.sendSyncMessage(request); - log.debug("Response payload from member [{}]: {}", member.getName(), response.getPayload()); - return parseProjectPaths(response.getPayload()); + + Message response = gateway.sendSyncMessage(request); + return parseConfigurations(response.getPayload()); } - /** - * Parses the bus response payload into a list of unique project root paths. - * The payload is expected to be a JSON array of {@link HazelcastConfigurationDTO} objects, - * each containing a {@code directory} field pointing to an individual configuration folder. - */ - private List parseProjectPaths(Object payload) { - if (payload == null) { - return List.of(); - } + private FrankInstanceDTO toFrankInstance(OutboundGateway.ClusterMember member, List configurations) { + List directories = configurations.stream() + .map(HazelcastConfigurationDTO::directory) + .filter(directory -> directory != null && !directory.isBlank()) + .toList(); + + return new FrankInstanceDTO(member.getName(), member.getId().toString(), directories, false); + } + + private List parseConfigurations(Object payload) { try { - String json = payload instanceof String s ? s : objectMapper.writeValueAsString(payload); - HazelcastConfigurationDTO[] configs = objectMapper.readValue(json, HazelcastConfigurationDTO[].class); - Set projectRoots = new LinkedHashSet<>(); - for (HazelcastConfigurationDTO config : configs) { - String dir = config.getDirectory(); - if (dir != null && !dir.isBlank()) { - String projectRoot = deriveProjectRoot(dir); - if (projectRoot != null) { - projectRoots.add(projectRoot); - } - } - } - return new ArrayList<>(projectRoots); + String json = payload instanceof String jsonString ? jsonString : objectMapper.writeValueAsString(payload); + return List.of(objectMapper.readValue(json, HazelcastConfigurationDTO[].class)); } catch (Exception e) { log.debug("Failed to parse configuration payload: {}", e.getMessage()); return List.of(); } } - - /** - * Derives the Frank project root from a configuration subdirectory path. - *
    - *
  • Maven layout: {@code /project/src/main/configurations/ConfigName} → {@code /project}
  • - *
  • Standard layout: {@code /project/configurations/ConfigName} → {@code /project}
  • - *
- */ - private String deriveProjectRoot(String configDirectory) { - if (configDirectory == null || configDirectory.isBlank()) { - return null; - } - String normalized = configDirectory.replace("\\", "/"); - // Maven layout: strip everything from src/main/configurations onward - int idx = normalized.indexOf(CONFIGURATIONS_SUBPATH); - if (idx >= 0) { - return configDirectory.substring(0, idx).replaceAll("[/\\\\]+$", ""); - } - // Standard layout: configDirectory is one level below the configurations folder, - // which is one level below the project root → go up two levels - Path path = Path.of(configDirectory); - Path configurationsDir = path.getParent(); - if (configurationsDir != null && configurationsDir.getParent() != null) { - return configurationsDir.getParent().toString(); - } - return configurationsDir != null ? configurationsDir.toString() : configDirectory; - } } diff --git a/src/main/resources/FrankFlowContext.xml b/src/main/resources/FrankFlowContext.xml deleted file mode 100644 index 409bf50d..00000000 --- a/src/main/resources/FrankFlowContext.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 00000000..3481666c --- /dev/null +++ b/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,10 @@ +{ + "properties": [ + { + "name": "hazelcast.enabled", + "type": "java.lang.Boolean", + "defaultValue": false, + "description": "Enable Hazelcast cluster integration to discover remote Frank instances." + } + ] +} diff --git a/src/main/resources/SpringBootContext.xml b/src/main/resources/SpringBootContext.xml deleted file mode 100644 index 97f62f22..00000000 --- a/src/main/resources/SpringBootContext.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties new file mode 100644 index 00000000..f5355a46 --- /dev/null +++ b/src/main/resources/application-local.properties @@ -0,0 +1,4 @@ +hazelcast.enabled=true + +# To trigger LocalGateway you can put a path in here to direct to the running frank. +configurations.directory= From 56d07dc93153c2a35d12a4a4cd7ecef3b6a13cce Mon Sep 17 00:00:00 2001 From: stijnpotters Date: Tue, 24 Mar 2026 14:45:51 +0100 Subject: [PATCH 04/20] fix: improved LocalGateway logic, added tests and improved code more --- .../routes/projectlanding/project-landing.tsx | 6 +- .../app/services/hazelcast-service.ts | 5 +- .../flow/FlowWarInitializer.java | 1 - .../common/config/ConfigurationsConfig.java | 68 ------- .../config/SecurityChainConfigurer.java | 5 +- .../flow/common/config/WebConfiguration.java | 3 +- .../hazelcast/ConfigurationsDirectory.java | 23 +-- .../flow/hazelcast/FrankInstanceDTO.java | 4 +- .../flow/hazelcast/HazelcastService.java | 96 ++++++++-- .../ConfigurationsDirectoryTest.java | 177 ++++++++++++++++++ .../flow/hazelcast/HazelcastServiceTest.java | 162 ++++++++++++++++ 11 files changed, 439 insertions(+), 111 deletions(-) delete mode 100644 src/main/java/org/frankframework/flow/common/config/ConfigurationsConfig.java create mode 100644 src/test/java/org/frankframework/flow/hazelcast/ConfigurationsDirectoryTest.java create mode 100644 src/test/java/org/frankframework/flow/hazelcast/HazelcastServiceTest.java diff --git a/src/main/frontend/app/routes/projectlanding/project-landing.tsx b/src/main/frontend/app/routes/projectlanding/project-landing.tsx index b25bafef..96d78477 100644 --- a/src/main/frontend/app/routes/projectlanding/project-landing.tsx +++ b/src/main/frontend/app/routes/projectlanding/project-landing.tsx @@ -183,7 +183,7 @@ export default function ProjectLanding() { const handleConnectToInstance = useCallback( async (instance: FrankInstance) => { - const path = instance.projectPaths[0] + const path = instance.projectPath if (!path) { showErrorToast(`No configuration path available for "${instance.name}"`) return @@ -339,9 +339,7 @@ const ProjectList = ({ >
{instance.name}
- {instance.projectPaths.length > 0 && ( -

{instance.projectPaths[0]}

- )} + {instance.projectPath &&

{instance.projectPath}

}
Live diff --git a/src/main/frontend/app/services/hazelcast-service.ts b/src/main/frontend/app/services/hazelcast-service.ts index afcffd79..f08ae976 100644 --- a/src/main/frontend/app/services/hazelcast-service.ts +++ b/src/main/frontend/app/services/hazelcast-service.ts @@ -2,9 +2,8 @@ import { apiFetch } from '~/utils/api' export interface FrankInstance { name: string - id: string | null - projectPaths: string[] - local: boolean + id: string + projectPath: string | null } export async function discoverFrankInstances(signal?: AbortSignal): Promise { diff --git a/src/main/java/org/frankframework/flow/FlowWarInitializer.java b/src/main/java/org/frankframework/flow/FlowWarInitializer.java index 7b052e30..858160e8 100644 --- a/src/main/java/org/frankframework/flow/FlowWarInitializer.java +++ b/src/main/java/org/frankframework/flow/FlowWarInitializer.java @@ -15,7 +15,6 @@ limitations under the License. */ -import org.jspecify.annotations.NullMarked; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; diff --git a/src/main/java/org/frankframework/flow/common/config/ConfigurationsConfig.java b/src/main/java/org/frankframework/flow/common/config/ConfigurationsConfig.java deleted file mode 100644 index cbde39ea..00000000 --- a/src/main/java/org/frankframework/flow/common/config/ConfigurationsConfig.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.frankframework.flow.common.config; - -import jakarta.annotation.PostConstruct; -import java.util.List; -import org.frankframework.flow.exception.ApiException; -import org.frankframework.flow.hazelcast.HazelcastConfigurationDTO; -import org.frankframework.management.bus.BusAction; -import org.frankframework.management.bus.BusMessageUtils; -import org.frankframework.management.bus.BusTopic; -import org.frankframework.management.bus.OutboundGateway; -import org.frankframework.util.JacksonUtils; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.MessageBuilder; -import org.springframework.stereotype.Component; -import org.springframework.web.context.annotation.SessionScope; - -@Component -@SessionScope -@ConditionalOnProperty(name = "hazelcast.enabled", havingValue = "true") -public class ConfigurationsConfig { - - private static final String DEFAULT_FF_CONFIGURATION_PREFIX = "IAF_"; - - private List configurations = List.of(); - - private final OutboundGateway outboundGateway; - - public ConfigurationsConfig(OutboundGateway outboundGateway) { - this.outboundGateway = outboundGateway; - } - - @PostConstruct - public void init() throws ApiException { - Message request = MessageBuilder.withPayload("NONE") - .setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()) - .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()) - .build(); - - Message response = outboundGateway.sendSyncMessage(request); - configurations = parseConfigurations(response).stream() - .filter(config -> !config.name().startsWith(DEFAULT_FF_CONFIGURATION_PREFIX)) - .toList(); - } - - private List parseConfigurations(Message response) throws ApiException { - String contentType = (String) response.getHeaders().get(BusMessageUtils.HEADER_PREFIX + "type"); - if (MediaType.APPLICATION_JSON_VALUE.equals(contentType)) { - HazelcastConfigurationDTO[] arr = JacksonUtils.convertToDTO(response.getPayload(), HazelcastConfigurationDTO[].class); - return List.of(arr); - } - - throw new ApiException("Unexpected result returned by Bus", HttpStatus.INTERNAL_SERVER_ERROR); - } - - public List getAllConfigurations() { - return configurations.stream().map(HazelcastConfigurationDTO::name).toList(); - } - - public HazelcastConfigurationDTO getConfiguration(String name) throws ApiException { - return configurations.stream() - .filter(config -> config.name().equals(name)) - .findFirst() - .orElseThrow(() -> new ApiException("configuration not found", HttpStatus.NOT_FOUND)); - } -} diff --git a/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java b/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java index 359bf359..8b08da25 100644 --- a/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java +++ b/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java @@ -7,7 +7,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; -import org.springframework.security.authorization.AuthenticatedAuthorizationManager; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -22,7 +21,7 @@ /** * Enable security, although it's anonymous on all endpoints, but at least sets the - * SecurityContextHolder.getContext().getAuthentication() object. + * SecurityContextHolder.getContext().getAuthentication() object. */ @Configuration @EnableWebSecurity @@ -39,7 +38,7 @@ public SecurityFilterChain configureChain(HttpSecurity http) { http.logout(LogoutConfigurer::disable); http.anonymous(anonymous -> anonymous.authorities(getAuthorities())); http.authorizeHttpRequests(requests -> - requests.requestMatchers(AnyRequestMatcher.INSTANCE).access(AuthenticatedAuthorizationManager.anonymous())); + requests.requestMatchers(AnyRequestMatcher.INSTANCE).permitAll()); return http.build(); } diff --git a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java index 27b4eeea..5ba665c1 100644 --- a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java +++ b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java @@ -1,7 +1,6 @@ package org.frankframework.flow.common.config; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; import org.frankframework.management.gateway.InputStreamHttpMessageConverter; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -39,7 +38,7 @@ public void configurePathMatch(PathMatchConfigurer configurer) { @Bean public ObjectMapper objectMapper() { - return new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); + return new ObjectMapper(); } @Bean diff --git a/src/main/java/org/frankframework/flow/hazelcast/ConfigurationsDirectory.java b/src/main/java/org/frankframework/flow/hazelcast/ConfigurationsDirectory.java index 83c0c746..3f260143 100644 --- a/src/main/java/org/frankframework/flow/hazelcast/ConfigurationsDirectory.java +++ b/src/main/java/org/frankframework/flow/hazelcast/ConfigurationsDirectory.java @@ -51,8 +51,9 @@ public void handleMessage(Message message) { return; } + File directory = resolveConfigurationsDirectory(); try { - replyChannel.send(buildConfigurationsResponse()); + replyChannel.send(buildConfigurationsResponse(directory)); } catch (Exception e) { log.error("Error building configurations response", e); } @@ -66,12 +67,11 @@ private boolean isConfigurationFindRequest(Message message) { && BusAction.FIND.name().equalsIgnoreCase(action); } - private Message buildConfigurationsResponse() { - File directory = resolveConfigurationsDirectory(); - File[] folders = directory.listFiles(); + private Message buildConfigurationsResponse(File directory) { + File[] folders = directory.listFiles(File::isDirectory); if (folders == null) { - log.warn("Failed to list files in configurations directory [{}]", configurationsDirectory); + log.warn("Failed to list directories in [{}]", directory.getAbsolutePath()); return new JsonMessage(Collections.emptyList()); } @@ -83,16 +83,13 @@ private Message buildConfigurationsResponse() { } private File resolveConfigurationsDirectory() { - File directory = new File(configurationsDirectory); - - if (!directory.exists()) { - throw new IllegalStateException("Path [" + configurationsDirectory + "] does not exist"); - } + File projectDir = new File(configurationsDirectory); - if (!directory.isDirectory()) { - throw new IllegalStateException("Path [" + configurationsDirectory + "] is not a directory"); + if (!projectDir.exists() || !projectDir.isDirectory()) { + throw new IllegalStateException("Path [" + configurationsDirectory + "] does not exist or is not a directory"); } - return directory; + File configurationsSubfolder = new File(projectDir, "configurations"); + return configurationsSubfolder.isDirectory() ? configurationsSubfolder : projectDir; } } diff --git a/src/main/java/org/frankframework/flow/hazelcast/FrankInstanceDTO.java b/src/main/java/org/frankframework/flow/hazelcast/FrankInstanceDTO.java index f0cf5d9b..872ff374 100644 --- a/src/main/java/org/frankframework/flow/hazelcast/FrankInstanceDTO.java +++ b/src/main/java/org/frankframework/flow/hazelcast/FrankInstanceDTO.java @@ -1,5 +1,3 @@ package org.frankframework.flow.hazelcast; -import java.util.List; - -public record FrankInstanceDTO(String name, String id, List projectPaths, boolean local) {} +public record FrankInstanceDTO(String name, String id, String projectPath) {} diff --git a/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java b/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java index 7a426271..27c4a74d 100644 --- a/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java +++ b/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java @@ -1,6 +1,8 @@ package org.frankframework.flow.hazelcast; import com.fasterxml.jackson.databind.ObjectMapper; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import lombok.extern.slf4j.Slf4j; @@ -37,7 +39,11 @@ public List getRemoteInstances() { } List members = gateway.getMembers(); - log.debug("Hazelcast cluster members: {}", members.size()); + log.info("Hazelcast cluster members: {}", members.size()); + + if (members.isEmpty()) { + return collectLocalInstance(gateway); + } return collectWorkerInstances(gateway, members); } @@ -51,33 +57,58 @@ private List collectWorkerInstances(OutboundGateway gateway, L try { List configurations = fetchConfigurations(gateway, member); - result.add(toFrankInstance(member, configurations)); + result.add(toFrankInstance(member.getName(), member.getId().toString(), configurations)); } catch (Exception e) { - log.debug("Member [{}] did not respond ({}), skipping", member.getName(), e.getMessage()); + log.warn("Member [{}] did not respond ({}), skipping", member.getName(), e.getMessage()); } } return result; } + private List collectLocalInstance(OutboundGateway gateway) { + try { + List configurations = fetchConfigurations(gateway, null); + if (configurations.isEmpty()) { + return List.of(); + } + + String projectPath = configurations.stream() + .map(HazelcastConfigurationDTO::directory) + .filter(directory -> directory != null && !directory.isBlank()) + .map(HazelcastService::deriveProjectPath) + .findFirst() + .orElse(null); + String name = projectPath != null ? Paths.get(projectPath).getFileName().toString() : "local"; + return List.of(new FrankInstanceDTO(name, "local", projectPath)); + } catch (Exception e) { + log.warn("Local gateway did not respond ({}), skipping", e.getMessage()); + return List.of(); + } + } + private List fetchConfigurations(OutboundGateway gateway, OutboundGateway.ClusterMember member) { - log.debug("Fetching configurations from member name=[{}] id=[{}]", member.getName(), member.getId()); - Message request = MessageBuilder.withPayload("NONE") + log.info("Fetching configurations from member name=[{}]", member != null ? member.getName() : "local"); + MessageBuilder builder = MessageBuilder.withPayload("NONE") .setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()) - .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()) - .setHeader(BusMessageUtils.HEADER_TARGET_KEY, member.getId()) - .build(); + .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()); - Message response = gateway.sendSyncMessage(request); + if (member != null) { + builder.setHeader(BusMessageUtils.HEADER_TARGET_KEY, member.getId()); + } + + Message response = gateway.sendSyncMessage(builder.build()); return parseConfigurations(response.getPayload()); } - private FrankInstanceDTO toFrankInstance(OutboundGateway.ClusterMember member, List configurations) { - List directories = configurations.stream() + private FrankInstanceDTO toFrankInstance(String name, String id, List configurations) { + String projectPath = configurations.stream() .map(HazelcastConfigurationDTO::directory) .filter(directory -> directory != null && !directory.isBlank()) - .toList(); + .map(HazelcastService::deriveProjectPath) + .findFirst() + .orElse(null); - return new FrankInstanceDTO(member.getName(), member.getId().toString(), directories, false); + return new FrankInstanceDTO(name, id, projectPath); } private List parseConfigurations(Object payload) { @@ -85,8 +116,45 @@ private List parseConfigurations(Object payload) { String json = payload instanceof String jsonString ? jsonString : objectMapper.writeValueAsString(payload); return List.of(objectMapper.readValue(json, HazelcastConfigurationDTO[].class)); } catch (Exception e) { - log.debug("Failed to parse configuration payload: {}", e.getMessage()); + log.warn("Failed to parse configuration payload: {}", e.getMessage()); return List.of(); } } + + // TODO: remove when flow becomes configuration-based instead of project-based + private static String deriveProjectPath(String configDirectory) { + Path path = Paths.get(configDirectory); + int projectRootIndex = findProjectRootIndex(path); + if (projectRootIndex < 0) { + return configDirectory; + } + return buildPathUpTo(path, projectRootIndex); + } + + private static int findProjectRootIndex(Path path) { + for (int i = 0; i < path.getNameCount(); i++) { + if ("configurations".equalsIgnoreCase(path.getName(i).toString())) { + return isMavenStructure(path, i) ? i - getMavenPrefixLength() : i; + } + } + return -1; + } + + private static boolean isMavenStructure(Path path, int configurationsIndex) { + int srcIndex = configurationsIndex - getMavenPrefixLength(); + int mainIndex = configurationsIndex - 1; + return srcIndex >= 0 + && "src".equalsIgnoreCase(path.getName(srcIndex).toString()) + && "main".equalsIgnoreCase(path.getName(mainIndex).toString()); + } + + private static int getMavenPrefixLength() { + return "src/main".split("/").length; + } + + private static String buildPathUpTo(Path path, int index) { + return index == 0 + ? path.getRoot().toString() + : path.getRoot().resolve(path.subpath(0, index)).toString(); + } } diff --git a/src/test/java/org/frankframework/flow/hazelcast/ConfigurationsDirectoryTest.java b/src/test/java/org/frankframework/flow/hazelcast/ConfigurationsDirectoryTest.java new file mode 100644 index 00000000..86655a8a --- /dev/null +++ b/src/test/java/org/frankframework/flow/hazelcast/ConfigurationsDirectoryTest.java @@ -0,0 +1,177 @@ +package org.frankframework.flow.hazelcast; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReference; +import org.frankframework.management.bus.BusAction; +import org.frankframework.management.bus.BusTopic; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.SubscribableChannel; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.test.util.ReflectionTestUtils; + +@ExtendWith(MockitoExtension.class) +public class ConfigurationsDirectoryTest { + + @Mock + private SubscribableChannel managementBusChannel; + + @TempDir + Path tempDir; + + private ConfigurationsDirectory configurationsDirectory; + + @BeforeEach + public void setUp() { + configurationsDirectory = new ConfigurationsDirectory(managementBusChannel); + } + + @Test + public void handleMessage_nonConfigurationFindRequest_isIgnored() { + setDirectory(tempDir.toString()); + Message message = MessageBuilder.withPayload("NONE") + .setHeader(BusTopic.TOPIC_HEADER_NAME, "ADAPTER") + .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()) + .build(); + + configurationsDirectory.handleMessage(message); + + verify(managementBusChannel, never()).subscribe(any()); + } + + @Test + public void handleMessage_noReplyChannel_doesNotThrow() { + setDirectory(tempDir.toString()); + Message message = MessageBuilder.withPayload("NONE") + .setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()) + .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()) + .build(); + + configurationsDirectory.handleMessage(message); + } + + @Test + public void handleMessage_configurationFindRequest_sendsResponseOnReplyChannel() throws IOException { + Path configurationsDir = tempDir.resolve("configurations"); + Files.createDirectory(configurationsDir); + Files.createDirectory(configurationsDir.resolve("Config1")); + Files.createDirectory(configurationsDir.resolve("Config2")); + setDirectory(tempDir.toString()); + + AtomicReference> sentMessage = new AtomicReference<>(); + MessageChannel replyChannel = (message, timeout) -> { + sentMessage.set(message); + return true; + }; + + Message request = MessageBuilder.withPayload("NONE") + .setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()) + .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()) + .setReplyChannel(replyChannel) + .build(); + + configurationsDirectory.handleMessage(request); + + String payload = (String) sentMessage.get().getPayload(); + assertTrue(payload.contains("Config1")); + assertTrue(payload.contains("Config2")); + } + + @Test + public void resolveConfigurationsDirectory_withConfigurationsSubfolder_usesSubfolder() throws IOException { + Path configurationsDir = tempDir.resolve("configurations"); + Files.createDirectory(configurationsDir); + Files.createDirectory(configurationsDir.resolve("Config1")); + setDirectory(tempDir.toString()); + + AtomicReference> sentMessage = new AtomicReference<>(); + MessageChannel replyChannel = (message, timeout) -> { + sentMessage.set(message); + return true; + }; + + Message request = configurationFindRequest(replyChannel); + configurationsDirectory.handleMessage(request); + + String payload = (String) sentMessage.get().getPayload(); + assertTrue(payload.contains("Config1")); + } + + @Test + public void resolveConfigurationsDirectory_withoutConfigurationsSubfolder_usesDirectoryItself() throws IOException { + Files.createDirectory(tempDir.resolve("Config1")); + setDirectory(tempDir.toString()); + + AtomicReference> sentMessage = new AtomicReference<>(); + MessageChannel replyChannel = (message, timeout) -> { + sentMessage.set(message); + return true; + }; + + Message request = configurationFindRequest(replyChannel); + configurationsDirectory.handleMessage(request); + + String payload = (String) sentMessage.get().getPayload(); + assertTrue(payload.contains("Config1")); + } + + @Test + public void resolveConfigurationsDirectory_filesAreExcluded() throws IOException { + Path configurationsDir = tempDir.resolve("configurations"); + Files.createDirectory(configurationsDir); + Files.createDirectory(configurationsDir.resolve("Config1")); + Files.createFile(configurationsDir.resolve(".gitignore")); + Files.createFile(configurationsDir.resolve("FrankConfig.xsd")); + setDirectory(tempDir.toString()); + + AtomicReference> sentMessage = new AtomicReference<>(); + MessageChannel replyChannel = (message, timeout) -> { + sentMessage.set(message); + return true; + }; + + Message request = configurationFindRequest(replyChannel); + configurationsDirectory.handleMessage(request); + + String payload = (String) sentMessage.get().getPayload(); + assertTrue(payload.contains("Config1")); + assertFalse(payload.contains(".gitignore")); + assertFalse(payload.contains("FrankConfig.xsd")); + } + + @Test + public void resolveConfigurationsDirectory_nonExistentPath_throwsIllegalState() { + setDirectory("/non/existent/path"); + MessageChannel replyChannel = (message, timeout) -> true; + Message request = configurationFindRequest(replyChannel); + + assertThrows(IllegalStateException.class, () -> configurationsDirectory.handleMessage(request)); + } + + private void setDirectory(String path) { + ReflectionTestUtils.setField(configurationsDirectory, "configurationsDirectory", path); + } + + private Message configurationFindRequest(MessageChannel replyChannel) { + return MessageBuilder.withPayload("NONE") + .setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()) + .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()) + .setReplyChannel(replyChannel) + .build(); + } +} diff --git a/src/test/java/org/frankframework/flow/hazelcast/HazelcastServiceTest.java b/src/test/java/org/frankframework/flow/hazelcast/HazelcastServiceTest.java new file mode 100644 index 00000000..f55ff4c6 --- /dev/null +++ b/src/test/java/org/frankframework/flow/hazelcast/HazelcastServiceTest.java @@ -0,0 +1,162 @@ +package org.frankframework.flow.hazelcast; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.nio.file.Path; +import java.util.List; +import java.util.UUID; +import org.frankframework.management.bus.OutboundGateway; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.messaging.support.MessageBuilder; + +@ExtendWith(MockitoExtension.class) +public class HazelcastServiceTest { + + @Mock + private ObjectProvider outboundGatewayProvider; + + @Mock + private OutboundGateway outboundGateway; + + private HazelcastService hazelcastService; + + @BeforeEach + public void setUp() { + hazelcastService = new HazelcastService(outboundGatewayProvider, new ObjectMapper()); + } + + @Test + public void getRemoteInstances_gatewayUnavailable_returnsEmpty() { + when(outboundGatewayProvider.getIfAvailable()).thenReturn(null); + + assertTrue(hazelcastService.getRemoteInstances().isEmpty()); + } + + @Test + public void getRemoteInstances_noMembers_returnsLocalInstance() { + when(outboundGatewayProvider.getIfAvailable()).thenReturn(outboundGateway); + when(outboundGateway.getMembers()).thenReturn(List.of()); + doReturn(MessageBuilder.withPayload( + "[{\"name\":\"Config1\",\"directory\":\"/projects/MyProject/configurations/Config1\"}]" + ).build()).when(outboundGateway).sendSyncMessage(any()); + + List result = hazelcastService.getRemoteInstances(); + + assertEquals(1, result.size()); + assertEquals("MyProject", result.getFirst().name()); + assertEquals("local", result.getFirst().id()); + assertEquals(Path.of("/projects/MyProject").toString(), result.getFirst().projectPath()); + } + + @Test + public void getRemoteInstances_localMode_emptyConfigurations_returnsEmpty() { + when(outboundGatewayProvider.getIfAvailable()).thenReturn(outboundGateway); + when(outboundGateway.getMembers()).thenReturn(List.of()); + doReturn(MessageBuilder.withPayload("[]").build()).when(outboundGateway).sendSyncMessage(any()); + + assertTrue(hazelcastService.getRemoteInstances().isEmpty()); + } + + @Test + public void getRemoteInstances_workerMember_returnsInstance() { + UUID memberId = UUID.randomUUID(); + OutboundGateway.ClusterMember worker = mockMember(memberId); + + when(outboundGatewayProvider.getIfAvailable()).thenReturn(outboundGateway); + when(outboundGateway.getMembers()).thenReturn(List.of(worker)); + doReturn(MessageBuilder.withPayload( + "[{\"name\":\"Config1\",\"directory\":\"/projects/Frank2Example1/configurations/Config1\"}]" + ).build()).when(outboundGateway).sendSyncMessage(any()); + + List result = hazelcastService.getRemoteInstances(); + + assertEquals(1, result.size()); + assertEquals("Frank2Example1", result.getFirst().name()); + assertEquals(memberId.toString(), result.getFirst().id()); + assertEquals(Path.of("/projects/Frank2Example1").toString(), result.getFirst().projectPath()); + } + + @Test + public void getRemoteInstances_nonWorkerMember_isSkipped() { + OutboundGateway.ClusterMember controller = mock(OutboundGateway.ClusterMember.class); + when(controller.getType()).thenReturn("CONTROLLER"); + + when(outboundGatewayProvider.getIfAvailable()).thenReturn(outboundGateway); + when(outboundGateway.getMembers()).thenReturn(List.of(controller)); + + assertTrue(hazelcastService.getRemoteInstances().isEmpty()); + } + + @Test + public void getRemoteInstances_memberDoesNotRespond_isSkipped() { + OutboundGateway.ClusterMember worker = mock(OutboundGateway.ClusterMember.class); + when(worker.getType()).thenReturn("WORKER"); + when(worker.getName()).thenReturn("Frank2Example1"); + + when(outboundGatewayProvider.getIfAvailable()).thenReturn(outboundGateway); + when(outboundGateway.getMembers()).thenReturn(List.of(worker)); + when(outboundGateway.sendSyncMessage(any())).thenThrow(new RuntimeException("timeout")); + + assertTrue(hazelcastService.getRemoteInstances().isEmpty()); + } + + @Test + public void getRemoteInstances_mavenProjectStructure_derivesProjectRoot() { + when(outboundGatewayProvider.getIfAvailable()).thenReturn(outboundGateway); + when(outboundGateway.getMembers()).thenReturn(List.of()); + doReturn(MessageBuilder.withPayload( + "[{\"name\":\"Config1\",\"directory\":\"/projects/MyProject/src/main/configurations/Config1\"}]" + ).build()).when(outboundGateway).sendSyncMessage(any()); + + List result = hazelcastService.getRemoteInstances(); + + assertEquals(Path.of("/projects/MyProject").toString(), result.getFirst().projectPath()); + } + + @Test + public void getRemoteInstances_noConfigurationsInPath_returnsDirectoryAsIs() { + when(outboundGatewayProvider.getIfAvailable()).thenReturn(outboundGateway); + when(outboundGateway.getMembers()).thenReturn(List.of()); + doReturn(MessageBuilder.withPayload( + "[{\"name\":\"Config1\",\"directory\":\"/projects/MyProject/unknown/Config1\"}]" + ).build()).when(outboundGateway).sendSyncMessage(any()); + + List result = hazelcastService.getRemoteInstances(); + + assertEquals("/projects/MyProject/unknown/Config1", result.getFirst().projectPath()); + } + + @Test + public void getRemoteInstances_nullDirectory_projectPathIsNull() { + when(outboundGatewayProvider.getIfAvailable()).thenReturn(outboundGateway); + when(outboundGateway.getMembers()).thenReturn(List.of()); + doReturn(MessageBuilder.withPayload( + "[{\"name\":\"Config1\",\"directory\":null}]" + ).build()).when(outboundGateway).sendSyncMessage(any()); + + List result = hazelcastService.getRemoteInstances(); + + assertEquals(1, result.size()); + assertNull(result.getFirst().projectPath()); + } + + private OutboundGateway.ClusterMember mockMember(UUID id) { + OutboundGateway.ClusterMember member = mock(OutboundGateway.ClusterMember.class); + when(member.getName()).thenReturn("Frank2Example1"); + when(member.getId()).thenReturn(id); + when(member.getType()).thenReturn("WORKER"); + return member; + } +} From e2b4642bebdde9669def9f29b3693b42b4a589f0 Mon Sep 17 00:00:00 2001 From: Vivy <4380412+Matthbo@users.noreply.github.com> Date: Fri, 10 Apr 2026 16:33:00 +0200 Subject: [PATCH 05/20] Replace hazelcast implementation with Console-like one --- pom.xml | 22 ++- .../frankframework/flow/FlowApplication.java | 11 +- .../flow/FlowWarInitializer.java | 31 --- .../flow/common/FrankFrameworkService.java | 87 +++++++++ .../flow/common/config/ClientSession.java | 52 +++++ .../flow/common/config/HazelcastConfig.java | 42 ----- .../config/SecurityChainConfigurer.java | 39 +++- .../flow/common/config/WebConfiguration.java | 33 +++- .../hazelcast/ConfigurationsDirectory.java | 95 ---------- .../flow/hazelcast/FrankInstanceDTO.java | 3 - .../hazelcast/HazelcastConfigurationDTO.java | 6 - .../flow/hazelcast/HazelcastController.java | 25 --- .../flow/hazelcast/HazelcastService.java | 160 ---------------- .../flow/security/UserContextFilter.java | 6 +- .../flow/security/UserWorkspaceContext.java | 4 +- .../flow/utility/ResponseUtils.java | 113 +++++++++++ ...itional-spring-configuration-metadata.json | 10 - .../resources/application-local.properties | 4 - src/main/resources/application.properties | 3 - .../ConfigurationsDirectoryTest.java | 177 ------------------ .../flow/hazelcast/HazelcastServiceTest.java | 162 ---------------- 21 files changed, 334 insertions(+), 751 deletions(-) delete mode 100644 src/main/java/org/frankframework/flow/FlowWarInitializer.java create mode 100644 src/main/java/org/frankframework/flow/common/FrankFrameworkService.java create mode 100644 src/main/java/org/frankframework/flow/common/config/ClientSession.java delete mode 100644 src/main/java/org/frankframework/flow/common/config/HazelcastConfig.java delete mode 100644 src/main/java/org/frankframework/flow/hazelcast/ConfigurationsDirectory.java delete mode 100644 src/main/java/org/frankframework/flow/hazelcast/FrankInstanceDTO.java delete mode 100644 src/main/java/org/frankframework/flow/hazelcast/HazelcastConfigurationDTO.java delete mode 100644 src/main/java/org/frankframework/flow/hazelcast/HazelcastController.java delete mode 100644 src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java create mode 100644 src/main/java/org/frankframework/flow/utility/ResponseUtils.java delete mode 100644 src/main/resources/META-INF/additional-spring-configuration-metadata.json delete mode 100644 src/main/resources/application-local.properties delete mode 100644 src/test/java/org/frankframework/flow/hazelcast/ConfigurationsDirectoryTest.java delete mode 100644 src/test/java/org/frankframework/flow/hazelcast/HazelcastServiceTest.java diff --git a/pom.xml b/pom.xml index b1973ffc..1a9913a8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 4.0.2 + 4.0.5 @@ -60,8 +60,9 @@ 3.0.0-SNAPSHOT - 21 + 25 7.0.6 + 10.1.0-SNAPSHOT 2.20.1 1.18.42 3.0.0 @@ -80,8 +81,8 @@ UTF-8 - 25 - 25 + ${java.version} + ${java.version} @@ -97,11 +98,6 @@ - - org.springframework - spring-beans - ${spring-framework.version} - org.springframework.boot @@ -186,7 +182,13 @@ org.frankframework frankframework-management-gateway - 10.0.0 + ${frankframework.version} + compile + + + org.frankframework + frankframework-security + ${frankframework.version} compile diff --git a/src/main/java/org/frankframework/flow/FlowApplication.java b/src/main/java/org/frankframework/flow/FlowApplication.java index 52b7c2a5..ccf93ae3 100644 --- a/src/main/java/org/frankframework/flow/FlowApplication.java +++ b/src/main/java/org/frankframework/flow/FlowApplication.java @@ -18,16 +18,11 @@ @SpringBootApplication @EnableScheduling -@ConfigurationPropertiesScan -public class FlowApplication extends SpringBootServletInitializer { +public class FlowApplication { public static void main(String[] args) { - SpringApplication.run(FlowApplication.class, args); - } - - @Override - protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { - return builder.sources(FlowApplication.class); + SpringApplication app = configureApplication(); + app.run(args); } public static SpringApplication configureApplication() { diff --git a/src/main/java/org/frankframework/flow/FlowWarInitializer.java b/src/main/java/org/frankframework/flow/FlowWarInitializer.java deleted file mode 100644 index 858160e8..00000000 --- a/src/main/java/org/frankframework/flow/FlowWarInitializer.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.frankframework.flow; -/* -Copyright 2023 WeAreFrank! - -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. -*/ - -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; - -/** - * Spring Boot entrypoint when running as a WAR application deployed to an external servlet container. - * For standalone JAR execution, see {@link FlowApplication}. - */ -public class FlowWarInitializer extends SpringBootServletInitializer { - - @Override - protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { - return builder.sources(FlowApplication.class); - } -} diff --git a/src/main/java/org/frankframework/flow/common/FrankFrameworkService.java b/src/main/java/org/frankframework/flow/common/FrankFrameworkService.java new file mode 100644 index 00000000..ff3c370e --- /dev/null +++ b/src/main/java/org/frankframework/flow/common/FrankFrameworkService.java @@ -0,0 +1,87 @@ +package org.frankframework.flow.common; + +import lombok.Getter; + +import org.frankframework.flow.common.config.ClientSession; +import org.frankframework.flow.exception.ApiException; +import org.frankframework.flow.utility.ResponseUtils; +import org.frankframework.management.bus.BusException; +import org.frankframework.management.bus.OutboundGateway; +import org.frankframework.management.bus.message.RequestMessageBuilder; +import org.frankframework.management.gateway.events.ClusterMemberEvent; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationListener; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.messaging.Message; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.UUID; + +@Service +public class FrankFrameworkService implements ApplicationContextAware, InitializingBean, ApplicationListener { + + private @Getter ApplicationContext applicationContext; + private @Getter Environment environment; + private @Getter List clusterMembers; + + private final ClientSession session; + + public FrankFrameworkService(ClientSession session) { + this.session = session; + } + + public ResponseEntity callSyncGateway(RequestMessageBuilder input) throws ApiException { + Message response = sendSyncMessage(input); + // Build the response or do some final checks / return a different response + return ResponseUtils.convertToSpringResponse(response); + } + + public ResponseEntity callAsyncGateway(RequestMessageBuilder input) { + OutboundGateway gateway = getGateway(); + gateway.sendAsyncMessage(input.build(session.getMemberTarget())); + return ResponseEntity.ok().build(); + } + + @Override + public void afterPropertiesSet() { + environment = applicationContext.getEnvironment(); + clusterMembers = getGateway().getMembers().stream() + .filter(m -> "worker".equals(m.getType())) + .toList(); + } + + @Override + public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + public void onApplicationEvent(@NonNull ClusterMemberEvent event) { + afterPropertiesSet(); + } + + protected final OutboundGateway getGateway() { + return getApplicationContext().getBean("outboundGateway", OutboundGateway.class); + } + + @NonNull + protected Message sendSyncMessage(RequestMessageBuilder input) throws ApiException { + try { + return getGateway().sendSyncMessage(input.build(getMemberTarget())); + } catch (BusException e) { + throw new ApiException("Error while sending message to topic [%s] with action [%s]".formatted(input.getTopic(), input.getAction()), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + private UUID getMemberTarget() { + return session.getMemberTarget(); + } +} diff --git a/src/main/java/org/frankframework/flow/common/config/ClientSession.java b/src/main/java/org/frankframework/flow/common/config/ClientSession.java new file mode 100644 index 00000000..9959a728 --- /dev/null +++ b/src/main/java/org/frankframework/flow/common/config/ClientSession.java @@ -0,0 +1,52 @@ +package org.frankframework.flow.common.config; + +import lombok.Getter; + +import org.frankframework.management.bus.OutboundGateway; + +import org.jspecify.annotations.Nullable; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.SessionScope; + +import java.util.List; +import java.util.UUID; + +@Component +@SessionScope +public class ClientSession implements InitializingBean { + + @Autowired + @Qualifier("outboundGateway") + private OutboundGateway outboundGateway; + + /** + * Get target or `NULL` when no target has been specified or `afterPropertiesSet` has not been called yet. + */ + @Nullable + private @Getter UUID memberTarget; + + public void setMemberTarget(@Nullable UUID id) { + this.memberTarget = id; + } + + public void setMemberTarget(String id) { + setMemberTarget(UUID.fromString(id)); + } + + // When a new session is created, assign a default target + @Override + public void afterPropertiesSet() throws Exception { + List members = outboundGateway.getMembers(); + members.stream() + .filter(member -> "worker".equals(member.getType())) + .findFirst() + .ifPresent(member -> { + member.setSelectedMember(true); + setMemberTarget(member.getId()); + }); + } + +} diff --git a/src/main/java/org/frankframework/flow/common/config/HazelcastConfig.java b/src/main/java/org/frankframework/flow/common/config/HazelcastConfig.java deleted file mode 100644 index e00254bf..00000000 --- a/src/main/java/org/frankframework/flow/common/config/HazelcastConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.frankframework.flow.common.config; - -import org.apache.commons.lang3.StringUtils; -import org.frankframework.management.bus.LocalGateway; -import org.frankframework.management.bus.OutboundGatewayFactory; -import org.frankframework.management.gateway.HazelcastOutboundGateway; -import org.frankframework.management.security.JwtKeyGenerator; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.integration.channel.PublishSubscribeChannel; -import org.springframework.messaging.SubscribableChannel; - -@Configuration -@ConditionalOnProperty(name = "hazelcast.enabled", havingValue = "true") -public class HazelcastConfig { - - @Value("${configurations.directory:}") - private String configurationsDirectory; - - @Bean - public JwtKeyGenerator jwtKeyGenerator() { - return new JwtKeyGenerator(); - } - - @Bean("frank-management-bus") - @ConditionalOnProperty(name = "configurations.directory") - public SubscribableChannel frankManagementBus() { - return new PublishSubscribeChannel(); - } - - @Bean - public OutboundGatewayFactory outboundGatewayFactory() { - OutboundGatewayFactory factory = new OutboundGatewayFactory(); - String gatewayClassName = StringUtils.isNotBlank(configurationsDirectory) - ? LocalGateway.class.getCanonicalName() - : HazelcastOutboundGateway.class.getCanonicalName(); - factory.setGatewayClassname(gatewayClassName); - return factory; - } -} diff --git a/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java b/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java index 8b08da25..5e435bcd 100644 --- a/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java +++ b/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java @@ -2,7 +2,23 @@ import java.util.Arrays; import java.util.List; + +import lombok.Setter; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import org.frankframework.lifecycle.DynamicRegistration.Servlet; + +import org.frankframework.lifecycle.servlets.AuthenticatorUtils; +import org.frankframework.lifecycle.servlets.IAuthenticator; + +import org.frankframework.security.config.ServletRegistration; +import org.frankframework.util.ClassUtils; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; @@ -14,6 +30,7 @@ import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.web.SecurityFilterChain; @@ -27,25 +44,33 @@ @EnableWebSecurity @EnableMethodSecurity(jsr250Enabled = true, prePostEnabled = false) @Order(Ordered.HIGHEST_PRECEDENCE) -public class SecurityChainConfigurer { +public class SecurityChainConfigurer implements ApplicationContextAware { + + private @Setter ApplicationContext applicationContext; @Bean - public SecurityFilterChain configureChain(HttpSecurity http) { + public SecurityFilterChain configureChain(IAuthenticator authenticator, HttpSecurity http) throws Exception { http.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)); http.csrf(CsrfConfigurer::disable); http.securityMatcher(AnyRequestMatcher.INSTANCE); http.formLogin(FormLoginConfigurer::disable); http.logout(LogoutConfigurer::disable); - http.anonymous(anonymous -> anonymous.authorities(getAuthorities())); - http.authorizeHttpRequests(requests -> - requests.requestMatchers(AnyRequestMatcher.INSTANCE).permitAll()); +// http.anonymous(anonymous -> anonymous.authorities(getAuthorities())); +// http.authorizeHttpRequests(requests -> requests.requestMatchers(AnyRequestMatcher.INSTANCE).permitAll()); + http.sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)); - return http.build(); + return authenticator.configureHttpSecurity(http); } - private List getAuthorities() { + /*private List getAuthorities() { return Arrays.stream(Servlet.ALL_IBIS_USER_ROLES) .map(role -> (GrantedAuthority) new SimpleGrantedAuthority("ROLE_" + role)) .toList(); + }*/ + + @Bean + public IAuthenticator flowAuthenticator() { + String propertyPrefix = "application.security.flow.authentication."; + return AuthenticatorUtils.createAuthenticator(applicationContext, propertyPrefix); } } diff --git a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java index 5ba665c1..22eedf6d 100644 --- a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java +++ b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java @@ -1,11 +1,18 @@ package org.frankframework.flow.common.config; import com.fasterxml.jackson.databind.ObjectMapper; + +import org.frankframework.management.bus.LocalGateway; +import org.frankframework.management.bus.OutboundGatewayFactory; import org.frankframework.management.gateway.InputStreamHttpMessageConverter; + import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverters; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import org.springframework.web.method.HandlerTypePredicate; @@ -14,7 +21,9 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration -public class WebConfiguration implements WebMvcConfigurer { +public class WebConfiguration implements WebMvcConfigurer, EnvironmentAware { + + private String gatewayClassName; private static final long MAX_AGE_SECONDS = 3600; @@ -36,6 +45,23 @@ public void configurePathMatch(PathMatchConfigurer configurer) { configurer.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class)); } + @Override + public void configureMessageConverters(HttpMessageConverters.ServerBuilder builder) { + builder.addCustomConverter(new InputStreamHttpMessageConverter()); + } + + @Override + public void setEnvironment(Environment environment) { + gatewayClassName = environment.getProperty("management.gateway.outbound.class", String.class, LocalGateway.class.getCanonicalName()); + } + + @Bean + public OutboundGatewayFactory outboundGateway() { + OutboundGatewayFactory factory = new OutboundGatewayFactory(); + factory.setGatewayClassname(gatewayClassName); + return factory; + } + @Bean public ObjectMapper objectMapper() { return new ObjectMapper(); @@ -45,9 +71,4 @@ public ObjectMapper objectMapper() { public RestTemplate restTemplate() { return new RestTemplate(); } - - @Bean - public HttpMessageConverter inputStreamHttpMessageConverter() { - return new InputStreamHttpMessageConverter(); - } } diff --git a/src/main/java/org/frankframework/flow/hazelcast/ConfigurationsDirectory.java b/src/main/java/org/frankframework/flow/hazelcast/ConfigurationsDirectory.java deleted file mode 100644 index 3f260143..00000000 --- a/src/main/java/org/frankframework/flow/hazelcast/ConfigurationsDirectory.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.frankframework.flow.hazelcast; - -import jakarta.annotation.PostConstruct; -import java.io.File; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import lombok.extern.slf4j.Slf4j; -import org.frankframework.management.bus.BusAction; -import org.frankframework.management.bus.BusTopic; -import org.frankframework.management.bus.message.JsonMessage; -import org.jspecify.annotations.NullMarked; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.SubscribableChannel; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -@ConditionalOnExpression("'${configurations.directory:}'.trim() != ''") -public class ConfigurationsDirectory implements MessageHandler { - - @Value("${configurations.directory:}") - private String configurationsDirectory; - - private final SubscribableChannel managementBusChannel; - - public ConfigurationsDirectory(@Qualifier("frank-management-bus") SubscribableChannel managementBusChannel) { - this.managementBusChannel = managementBusChannel; - } - - @PostConstruct - public void subscribe() { - managementBusChannel.subscribe(this); - } - - @Override - @NullMarked - public void handleMessage(Message message) { - if (!isConfigurationFindRequest(message)) { - return; - } - - Object replyChannelObject = message.getHeaders().getReplyChannel(); - if (!(replyChannelObject instanceof MessageChannel replyChannel)) { - log.warn("No reply channel in message headers, cannot respond"); - return; - } - - File directory = resolveConfigurationsDirectory(); - try { - replyChannel.send(buildConfigurationsResponse(directory)); - } catch (Exception e) { - log.error("Error building configurations response", e); - } - } - - private boolean isConfigurationFindRequest(Message message) { - String topic = (String) message.getHeaders().get(BusTopic.TOPIC_HEADER_NAME); - String action = (String) message.getHeaders().get(BusAction.ACTION_HEADER_NAME); - - return BusTopic.CONFIGURATION.name().equalsIgnoreCase(topic) - && BusAction.FIND.name().equalsIgnoreCase(action); - } - - private Message buildConfigurationsResponse(File directory) { - File[] folders = directory.listFiles(File::isDirectory); - - if (folders == null) { - log.warn("Failed to list directories in [{}]", directory.getAbsolutePath()); - return new JsonMessage(Collections.emptyList()); - } - - List configurations = Arrays.stream(folders) - .map(folder -> new HazelcastConfigurationDTO(folder.getName(), folder.getAbsolutePath())) - .toList(); - - return new JsonMessage(configurations); - } - - private File resolveConfigurationsDirectory() { - File projectDir = new File(configurationsDirectory); - - if (!projectDir.exists() || !projectDir.isDirectory()) { - throw new IllegalStateException("Path [" + configurationsDirectory + "] does not exist or is not a directory"); - } - - File configurationsSubfolder = new File(projectDir, "configurations"); - return configurationsSubfolder.isDirectory() ? configurationsSubfolder : projectDir; - } -} diff --git a/src/main/java/org/frankframework/flow/hazelcast/FrankInstanceDTO.java b/src/main/java/org/frankframework/flow/hazelcast/FrankInstanceDTO.java deleted file mode 100644 index 872ff374..00000000 --- a/src/main/java/org/frankframework/flow/hazelcast/FrankInstanceDTO.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.frankframework.flow.hazelcast; - -public record FrankInstanceDTO(String name, String id, String projectPath) {} diff --git a/src/main/java/org/frankframework/flow/hazelcast/HazelcastConfigurationDTO.java b/src/main/java/org/frankframework/flow/hazelcast/HazelcastConfigurationDTO.java deleted file mode 100644 index 1354d6cd..00000000 --- a/src/main/java/org/frankframework/flow/hazelcast/HazelcastConfigurationDTO.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.frankframework.flow.hazelcast; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - -@JsonIgnoreProperties(ignoreUnknown = true) -public record HazelcastConfigurationDTO(String name, String directory) {} diff --git a/src/main/java/org/frankframework/flow/hazelcast/HazelcastController.java b/src/main/java/org/frankframework/flow/hazelcast/HazelcastController.java deleted file mode 100644 index 4cd894e8..00000000 --- a/src/main/java/org/frankframework/flow/hazelcast/HazelcastController.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.frankframework.flow.hazelcast; - -import java.util.List; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -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; - -@RestController -@RequestMapping("/hazelcast") -@ConditionalOnBean(HazelcastService.class) -public class HazelcastController { - - private final HazelcastService hazelcastService; - - public HazelcastController(HazelcastService hazelcastService) { - this.hazelcastService = hazelcastService; - } - - @GetMapping("/instances") - public ResponseEntity> getInstances() { - return ResponseEntity.ok(hazelcastService.getRemoteInstances()); - } -} diff --git a/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java b/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java deleted file mode 100644 index 27c4a74d..00000000 --- a/src/main/java/org/frankframework/flow/hazelcast/HazelcastService.java +++ /dev/null @@ -1,160 +0,0 @@ -package org.frankframework.flow.hazelcast; - -import com.fasterxml.jackson.databind.ObjectMapper; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import lombok.extern.slf4j.Slf4j; -import org.frankframework.management.bus.BusAction; -import org.frankframework.management.bus.BusMessageUtils; -import org.frankframework.management.bus.BusTopic; -import org.frankframework.management.bus.OutboundGateway; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.MessageBuilder; -import org.springframework.stereotype.Service; - -@Slf4j -@Service -@ConditionalOnProperty(name = "hazelcast.enabled", havingValue = "true") -public class HazelcastService { - - private static final String WORKER_TYPE = "WORKER"; - - private final ObjectProvider outboundGatewayProvider; - private final ObjectMapper objectMapper; - - public HazelcastService(ObjectProvider outboundGatewayProvider, ObjectMapper objectMapper) { - this.outboundGatewayProvider = outboundGatewayProvider; - this.objectMapper = objectMapper; - } - - public List getRemoteInstances() { - OutboundGateway gateway = outboundGatewayProvider.getIfAvailable(); - if (gateway == null) { - log.warn("OutboundGateway is not available"); - return List.of(); - } - - List members = gateway.getMembers(); - log.info("Hazelcast cluster members: {}", members.size()); - - if (members.isEmpty()) { - return collectLocalInstance(gateway); - } - - return collectWorkerInstances(gateway, members); - } - - private List collectWorkerInstances(OutboundGateway gateway, List members) { - List result = new ArrayList<>(); - for (OutboundGateway.ClusterMember member : members) { - if (!WORKER_TYPE.equalsIgnoreCase(member.getType())) { - continue; - } - - try { - List configurations = fetchConfigurations(gateway, member); - result.add(toFrankInstance(member.getName(), member.getId().toString(), configurations)); - } catch (Exception e) { - log.warn("Member [{}] did not respond ({}), skipping", member.getName(), e.getMessage()); - } - } - return result; - } - - private List collectLocalInstance(OutboundGateway gateway) { - try { - List configurations = fetchConfigurations(gateway, null); - if (configurations.isEmpty()) { - return List.of(); - } - - String projectPath = configurations.stream() - .map(HazelcastConfigurationDTO::directory) - .filter(directory -> directory != null && !directory.isBlank()) - .map(HazelcastService::deriveProjectPath) - .findFirst() - .orElse(null); - String name = projectPath != null ? Paths.get(projectPath).getFileName().toString() : "local"; - return List.of(new FrankInstanceDTO(name, "local", projectPath)); - } catch (Exception e) { - log.warn("Local gateway did not respond ({}), skipping", e.getMessage()); - return List.of(); - } - } - - private List fetchConfigurations(OutboundGateway gateway, OutboundGateway.ClusterMember member) { - log.info("Fetching configurations from member name=[{}]", member != null ? member.getName() : "local"); - MessageBuilder builder = MessageBuilder.withPayload("NONE") - .setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()) - .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()); - - if (member != null) { - builder.setHeader(BusMessageUtils.HEADER_TARGET_KEY, member.getId()); - } - - Message response = gateway.sendSyncMessage(builder.build()); - return parseConfigurations(response.getPayload()); - } - - private FrankInstanceDTO toFrankInstance(String name, String id, List configurations) { - String projectPath = configurations.stream() - .map(HazelcastConfigurationDTO::directory) - .filter(directory -> directory != null && !directory.isBlank()) - .map(HazelcastService::deriveProjectPath) - .findFirst() - .orElse(null); - - return new FrankInstanceDTO(name, id, projectPath); - } - - private List parseConfigurations(Object payload) { - try { - String json = payload instanceof String jsonString ? jsonString : objectMapper.writeValueAsString(payload); - return List.of(objectMapper.readValue(json, HazelcastConfigurationDTO[].class)); - } catch (Exception e) { - log.warn("Failed to parse configuration payload: {}", e.getMessage()); - return List.of(); - } - } - - // TODO: remove when flow becomes configuration-based instead of project-based - private static String deriveProjectPath(String configDirectory) { - Path path = Paths.get(configDirectory); - int projectRootIndex = findProjectRootIndex(path); - if (projectRootIndex < 0) { - return configDirectory; - } - return buildPathUpTo(path, projectRootIndex); - } - - private static int findProjectRootIndex(Path path) { - for (int i = 0; i < path.getNameCount(); i++) { - if ("configurations".equalsIgnoreCase(path.getName(i).toString())) { - return isMavenStructure(path, i) ? i - getMavenPrefixLength() : i; - } - } - return -1; - } - - private static boolean isMavenStructure(Path path, int configurationsIndex) { - int srcIndex = configurationsIndex - getMavenPrefixLength(); - int mainIndex = configurationsIndex - 1; - return srcIndex >= 0 - && "src".equalsIgnoreCase(path.getName(srcIndex).toString()) - && "main".equalsIgnoreCase(path.getName(mainIndex).toString()); - } - - private static int getMavenPrefixLength() { - return "src/main".split("/").length; - } - - private static String buildPathUpTo(Path path, int index) { - return index == 0 - ? path.getRoot().toString() - : path.getRoot().resolve(path.subpath(0, index)).toString(); - } -} diff --git a/src/main/java/org/frankframework/flow/security/UserContextFilter.java b/src/main/java/org/frankframework/flow/security/UserContextFilter.java index 8c18e051..32d9480d 100644 --- a/src/main/java/org/frankframework/flow/security/UserContextFilter.java +++ b/src/main/java/org/frankframework/flow/security/UserContextFilter.java @@ -2,21 +2,25 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; + import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpFilter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; + import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; import java.util.HexFormat; + import lombok.extern.slf4j.Slf4j; + import org.springframework.stereotype.Component; -@Component +//@Component @Slf4j public class UserContextFilter extends HttpFilter { diff --git a/src/main/java/org/frankframework/flow/security/UserWorkspaceContext.java b/src/main/java/org/frankframework/flow/security/UserWorkspaceContext.java index 0d9da07b..ee3cef76 100644 --- a/src/main/java/org/frankframework/flow/security/UserWorkspaceContext.java +++ b/src/main/java/org/frankframework/flow/security/UserWorkspaceContext.java @@ -1,13 +1,15 @@ package org.frankframework.flow.security; import java.io.Serializable; + import lombok.Getter; import lombok.Setter; + import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; -@Component +//@Component @RequestScope(proxyMode = ScopedProxyMode.TARGET_CLASS) @Getter @Setter diff --git a/src/main/java/org/frankframework/flow/utility/ResponseUtils.java b/src/main/java/org/frankframework/flow/utility/ResponseUtils.java new file mode 100644 index 00000000..113a8005 --- /dev/null +++ b/src/main/java/org/frankframework/flow/utility/ResponseUtils.java @@ -0,0 +1,113 @@ +package org.frankframework.flow.utility; + +import lombok.NoArgsConstructor; + +import org.frankframework.flow.exception.ApiException; +import org.frankframework.management.bus.BusMessageUtils; +import org.frankframework.management.bus.message.EmptyMessage; +import org.frankframework.management.bus.message.MessageBase; +import org.frankframework.util.StreamUtil; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.messaging.Message; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; + +import java.io.IOException; +import java.io.InputStream; + +@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE) +public class ResponseUtils { + + public static ResponseEntity convertToSpringResponse(Message message) { + return convertToSpringResponse(message, null); + } + + @SuppressWarnings("unchecked") + public static ResponseEntity convertToSpringStreamingResponse(Message message) { + StreamingResponseBody response = outputStream -> { + InputStream inputStream = message.getPayload(); + int numberOfBytesToWrite; + byte[] data = new byte[1024]; + while ((numberOfBytesToWrite = inputStream.read(data, 0, data.length)) != -1) { + outputStream.write(data, 0, numberOfBytesToWrite); + } + inputStream.close(); + }; + return (ResponseEntity) convertToSpringResponse(message, response); + } + + public static ResponseEntity convertToSpringResponse(Message message, StreamingResponseBody response) { + int status = BusMessageUtils.getIntHeader(message, MessageBase.STATUS_KEY, 200); + String mimeType = BusMessageUtils.getHeader(message, MessageBase.MIMETYPE_KEY, null); + ResponseEntity.BodyBuilder responseEntity = ResponseEntity.status(status); + HttpHeaders httpHeaders = new HttpHeaders(); + + if (mimeType != null) { + httpHeaders.setContentType(MediaType.valueOf(mimeType)); + } + + String contentDisposition = BusMessageUtils.getHeader(message, MessageBase.CONTENT_DISPOSITION_KEY, null); + if (contentDisposition != null) { + httpHeaders.setContentDisposition(ContentDisposition.parse(contentDisposition)); + } + + responseEntity.headers(httpHeaders); + + if (hasPayload(status)) { + if (response != null) { + return responseEntity.body(response); + } + return responseEntity.body(message.getPayload()); + } + + return responseEntity.build(); + } + + /** + * Extracted method so it's in one place. + * See {@link EmptyMessage} for more info. + */ + private static boolean hasPayload(int status) { + return (status == 200 || status > 204); + } + + @Nullable + public static String parseAsString(Message message) throws ApiException { + int status = BusMessageUtils.getIntHeader(message, MessageBase.STATUS_KEY, 200); + if (!hasPayload(status)) { + return null; + } + + String mimeType = BusMessageUtils.getHeader(message, MessageBase.MIMETYPE_KEY, null); + if (mimeType != null) { + MediaType mime = MediaType.valueOf(mimeType); + if (MediaType.APPLICATION_JSON.equalsTypeAndSubtype(mime) || MediaType.TEXT_PLAIN.equalsTypeAndSubtype(mime)) { + return (String) message.getPayload(); + } + } + return convertPayload(message.getPayload()); + } + + @NonNull + public static String convertPayload(Object payload) throws ApiException { + if (payload instanceof String string) { + return string; + } else if (payload instanceof byte[] bytes) { + return new String(bytes); + } else if (payload instanceof InputStream stream) { + try { + // Convert line endings to \n to show them in the browser as real line feeds + return StreamUtil.streamToString(stream, "\n", false); + } catch (IOException exception) { + throw new ApiException("unable to read response payload: " + exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + throw new ApiException("unexpected response payload type [" + payload.getClass().getCanonicalName() + "]", HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/src/main/resources/META-INF/additional-spring-configuration-metadata.json deleted file mode 100644 index 3481666c..00000000 --- a/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "properties": [ - { - "name": "hazelcast.enabled", - "type": "java.lang.Boolean", - "defaultValue": false, - "description": "Enable Hazelcast cluster integration to discover remote Frank instances." - } - ] -} diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties deleted file mode 100644 index f5355a46..00000000 --- a/src/main/resources/application-local.properties +++ /dev/null @@ -1,4 +0,0 @@ -hazelcast.enabled=true - -# To trigger LocalGateway you can put a path in here to direct to the running frank. -configurations.directory= diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a2c4f04a..a9a26def 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,6 +5,3 @@ spring.web.resources.static-locations=classpath:/frontend/ spring.servlet.multipart.max-file-size=50MB spring.servlet.multipart.max-request-size=50MB - -hazelcast.enabled=false - diff --git a/src/test/java/org/frankframework/flow/hazelcast/ConfigurationsDirectoryTest.java b/src/test/java/org/frankframework/flow/hazelcast/ConfigurationsDirectoryTest.java deleted file mode 100644 index 86655a8a..00000000 --- a/src/test/java/org/frankframework/flow/hazelcast/ConfigurationsDirectoryTest.java +++ /dev/null @@ -1,177 +0,0 @@ -package org.frankframework.flow.hazelcast; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.concurrent.atomic.AtomicReference; -import org.frankframework.management.bus.BusAction; -import org.frankframework.management.bus.BusTopic; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.SubscribableChannel; -import org.springframework.messaging.support.MessageBuilder; -import org.springframework.test.util.ReflectionTestUtils; - -@ExtendWith(MockitoExtension.class) -public class ConfigurationsDirectoryTest { - - @Mock - private SubscribableChannel managementBusChannel; - - @TempDir - Path tempDir; - - private ConfigurationsDirectory configurationsDirectory; - - @BeforeEach - public void setUp() { - configurationsDirectory = new ConfigurationsDirectory(managementBusChannel); - } - - @Test - public void handleMessage_nonConfigurationFindRequest_isIgnored() { - setDirectory(tempDir.toString()); - Message message = MessageBuilder.withPayload("NONE") - .setHeader(BusTopic.TOPIC_HEADER_NAME, "ADAPTER") - .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()) - .build(); - - configurationsDirectory.handleMessage(message); - - verify(managementBusChannel, never()).subscribe(any()); - } - - @Test - public void handleMessage_noReplyChannel_doesNotThrow() { - setDirectory(tempDir.toString()); - Message message = MessageBuilder.withPayload("NONE") - .setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()) - .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()) - .build(); - - configurationsDirectory.handleMessage(message); - } - - @Test - public void handleMessage_configurationFindRequest_sendsResponseOnReplyChannel() throws IOException { - Path configurationsDir = tempDir.resolve("configurations"); - Files.createDirectory(configurationsDir); - Files.createDirectory(configurationsDir.resolve("Config1")); - Files.createDirectory(configurationsDir.resolve("Config2")); - setDirectory(tempDir.toString()); - - AtomicReference> sentMessage = new AtomicReference<>(); - MessageChannel replyChannel = (message, timeout) -> { - sentMessage.set(message); - return true; - }; - - Message request = MessageBuilder.withPayload("NONE") - .setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()) - .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()) - .setReplyChannel(replyChannel) - .build(); - - configurationsDirectory.handleMessage(request); - - String payload = (String) sentMessage.get().getPayload(); - assertTrue(payload.contains("Config1")); - assertTrue(payload.contains("Config2")); - } - - @Test - public void resolveConfigurationsDirectory_withConfigurationsSubfolder_usesSubfolder() throws IOException { - Path configurationsDir = tempDir.resolve("configurations"); - Files.createDirectory(configurationsDir); - Files.createDirectory(configurationsDir.resolve("Config1")); - setDirectory(tempDir.toString()); - - AtomicReference> sentMessage = new AtomicReference<>(); - MessageChannel replyChannel = (message, timeout) -> { - sentMessage.set(message); - return true; - }; - - Message request = configurationFindRequest(replyChannel); - configurationsDirectory.handleMessage(request); - - String payload = (String) sentMessage.get().getPayload(); - assertTrue(payload.contains("Config1")); - } - - @Test - public void resolveConfigurationsDirectory_withoutConfigurationsSubfolder_usesDirectoryItself() throws IOException { - Files.createDirectory(tempDir.resolve("Config1")); - setDirectory(tempDir.toString()); - - AtomicReference> sentMessage = new AtomicReference<>(); - MessageChannel replyChannel = (message, timeout) -> { - sentMessage.set(message); - return true; - }; - - Message request = configurationFindRequest(replyChannel); - configurationsDirectory.handleMessage(request); - - String payload = (String) sentMessage.get().getPayload(); - assertTrue(payload.contains("Config1")); - } - - @Test - public void resolveConfigurationsDirectory_filesAreExcluded() throws IOException { - Path configurationsDir = tempDir.resolve("configurations"); - Files.createDirectory(configurationsDir); - Files.createDirectory(configurationsDir.resolve("Config1")); - Files.createFile(configurationsDir.resolve(".gitignore")); - Files.createFile(configurationsDir.resolve("FrankConfig.xsd")); - setDirectory(tempDir.toString()); - - AtomicReference> sentMessage = new AtomicReference<>(); - MessageChannel replyChannel = (message, timeout) -> { - sentMessage.set(message); - return true; - }; - - Message request = configurationFindRequest(replyChannel); - configurationsDirectory.handleMessage(request); - - String payload = (String) sentMessage.get().getPayload(); - assertTrue(payload.contains("Config1")); - assertFalse(payload.contains(".gitignore")); - assertFalse(payload.contains("FrankConfig.xsd")); - } - - @Test - public void resolveConfigurationsDirectory_nonExistentPath_throwsIllegalState() { - setDirectory("/non/existent/path"); - MessageChannel replyChannel = (message, timeout) -> true; - Message request = configurationFindRequest(replyChannel); - - assertThrows(IllegalStateException.class, () -> configurationsDirectory.handleMessage(request)); - } - - private void setDirectory(String path) { - ReflectionTestUtils.setField(configurationsDirectory, "configurationsDirectory", path); - } - - private Message configurationFindRequest(MessageChannel replyChannel) { - return MessageBuilder.withPayload("NONE") - .setHeader(BusTopic.TOPIC_HEADER_NAME, BusTopic.CONFIGURATION.name()) - .setHeader(BusAction.ACTION_HEADER_NAME, BusAction.FIND.name()) - .setReplyChannel(replyChannel) - .build(); - } -} diff --git a/src/test/java/org/frankframework/flow/hazelcast/HazelcastServiceTest.java b/src/test/java/org/frankframework/flow/hazelcast/HazelcastServiceTest.java deleted file mode 100644 index f55ff4c6..00000000 --- a/src/test/java/org/frankframework/flow/hazelcast/HazelcastServiceTest.java +++ /dev/null @@ -1,162 +0,0 @@ -package org.frankframework.flow.hazelcast; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.fasterxml.jackson.databind.ObjectMapper; -import java.nio.file.Path; -import java.util.List; -import java.util.UUID; -import org.frankframework.management.bus.OutboundGateway; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.messaging.support.MessageBuilder; - -@ExtendWith(MockitoExtension.class) -public class HazelcastServiceTest { - - @Mock - private ObjectProvider outboundGatewayProvider; - - @Mock - private OutboundGateway outboundGateway; - - private HazelcastService hazelcastService; - - @BeforeEach - public void setUp() { - hazelcastService = new HazelcastService(outboundGatewayProvider, new ObjectMapper()); - } - - @Test - public void getRemoteInstances_gatewayUnavailable_returnsEmpty() { - when(outboundGatewayProvider.getIfAvailable()).thenReturn(null); - - assertTrue(hazelcastService.getRemoteInstances().isEmpty()); - } - - @Test - public void getRemoteInstances_noMembers_returnsLocalInstance() { - when(outboundGatewayProvider.getIfAvailable()).thenReturn(outboundGateway); - when(outboundGateway.getMembers()).thenReturn(List.of()); - doReturn(MessageBuilder.withPayload( - "[{\"name\":\"Config1\",\"directory\":\"/projects/MyProject/configurations/Config1\"}]" - ).build()).when(outboundGateway).sendSyncMessage(any()); - - List result = hazelcastService.getRemoteInstances(); - - assertEquals(1, result.size()); - assertEquals("MyProject", result.getFirst().name()); - assertEquals("local", result.getFirst().id()); - assertEquals(Path.of("/projects/MyProject").toString(), result.getFirst().projectPath()); - } - - @Test - public void getRemoteInstances_localMode_emptyConfigurations_returnsEmpty() { - when(outboundGatewayProvider.getIfAvailable()).thenReturn(outboundGateway); - when(outboundGateway.getMembers()).thenReturn(List.of()); - doReturn(MessageBuilder.withPayload("[]").build()).when(outboundGateway).sendSyncMessage(any()); - - assertTrue(hazelcastService.getRemoteInstances().isEmpty()); - } - - @Test - public void getRemoteInstances_workerMember_returnsInstance() { - UUID memberId = UUID.randomUUID(); - OutboundGateway.ClusterMember worker = mockMember(memberId); - - when(outboundGatewayProvider.getIfAvailable()).thenReturn(outboundGateway); - when(outboundGateway.getMembers()).thenReturn(List.of(worker)); - doReturn(MessageBuilder.withPayload( - "[{\"name\":\"Config1\",\"directory\":\"/projects/Frank2Example1/configurations/Config1\"}]" - ).build()).when(outboundGateway).sendSyncMessage(any()); - - List result = hazelcastService.getRemoteInstances(); - - assertEquals(1, result.size()); - assertEquals("Frank2Example1", result.getFirst().name()); - assertEquals(memberId.toString(), result.getFirst().id()); - assertEquals(Path.of("/projects/Frank2Example1").toString(), result.getFirst().projectPath()); - } - - @Test - public void getRemoteInstances_nonWorkerMember_isSkipped() { - OutboundGateway.ClusterMember controller = mock(OutboundGateway.ClusterMember.class); - when(controller.getType()).thenReturn("CONTROLLER"); - - when(outboundGatewayProvider.getIfAvailable()).thenReturn(outboundGateway); - when(outboundGateway.getMembers()).thenReturn(List.of(controller)); - - assertTrue(hazelcastService.getRemoteInstances().isEmpty()); - } - - @Test - public void getRemoteInstances_memberDoesNotRespond_isSkipped() { - OutboundGateway.ClusterMember worker = mock(OutboundGateway.ClusterMember.class); - when(worker.getType()).thenReturn("WORKER"); - when(worker.getName()).thenReturn("Frank2Example1"); - - when(outboundGatewayProvider.getIfAvailable()).thenReturn(outboundGateway); - when(outboundGateway.getMembers()).thenReturn(List.of(worker)); - when(outboundGateway.sendSyncMessage(any())).thenThrow(new RuntimeException("timeout")); - - assertTrue(hazelcastService.getRemoteInstances().isEmpty()); - } - - @Test - public void getRemoteInstances_mavenProjectStructure_derivesProjectRoot() { - when(outboundGatewayProvider.getIfAvailable()).thenReturn(outboundGateway); - when(outboundGateway.getMembers()).thenReturn(List.of()); - doReturn(MessageBuilder.withPayload( - "[{\"name\":\"Config1\",\"directory\":\"/projects/MyProject/src/main/configurations/Config1\"}]" - ).build()).when(outboundGateway).sendSyncMessage(any()); - - List result = hazelcastService.getRemoteInstances(); - - assertEquals(Path.of("/projects/MyProject").toString(), result.getFirst().projectPath()); - } - - @Test - public void getRemoteInstances_noConfigurationsInPath_returnsDirectoryAsIs() { - when(outboundGatewayProvider.getIfAvailable()).thenReturn(outboundGateway); - when(outboundGateway.getMembers()).thenReturn(List.of()); - doReturn(MessageBuilder.withPayload( - "[{\"name\":\"Config1\",\"directory\":\"/projects/MyProject/unknown/Config1\"}]" - ).build()).when(outboundGateway).sendSyncMessage(any()); - - List result = hazelcastService.getRemoteInstances(); - - assertEquals("/projects/MyProject/unknown/Config1", result.getFirst().projectPath()); - } - - @Test - public void getRemoteInstances_nullDirectory_projectPathIsNull() { - when(outboundGatewayProvider.getIfAvailable()).thenReturn(outboundGateway); - when(outboundGateway.getMembers()).thenReturn(List.of()); - doReturn(MessageBuilder.withPayload( - "[{\"name\":\"Config1\",\"directory\":null}]" - ).build()).when(outboundGateway).sendSyncMessage(any()); - - List result = hazelcastService.getRemoteInstances(); - - assertEquals(1, result.size()); - assertNull(result.getFirst().projectPath()); - } - - private OutboundGateway.ClusterMember mockMember(UUID id) { - OutboundGateway.ClusterMember member = mock(OutboundGateway.ClusterMember.class); - when(member.getName()).thenReturn("Frank2Example1"); - when(member.getId()).thenReturn(id); - when(member.getType()).thenReturn("WORKER"); - return member; - } -} From ac43c07dc8e00b8f523aacc26b967a257a257369 Mon Sep 17 00:00:00 2001 From: Vivy <4380412+Matthbo@users.noreply.github.com> Date: Mon, 13 Apr 2026 17:12:02 +0200 Subject: [PATCH 06/20] Update security --- .../frankframework/flow/FlowApplication.java | 3 +-- .../config/SecurityChainConfigurer.java | 27 +++++++++---------- .../flow/common/config/WebConfiguration.java | 8 ++++++ .../flow/security/UserWorkspaceContext.java | 2 +- src/main/resources/application.properties | 4 +++ 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/frankframework/flow/FlowApplication.java b/src/main/java/org/frankframework/flow/FlowApplication.java index ccf93ae3..0e988911 100644 --- a/src/main/java/org/frankframework/flow/FlowApplication.java +++ b/src/main/java/org/frankframework/flow/FlowApplication.java @@ -7,8 +7,6 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.context.properties.ConfigurationPropertiesScan; -import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.context.annotation.Bean; import org.springframework.core.io.ClassPathResource; import org.springframework.scheduling.annotation.EnableScheduling; @@ -22,6 +20,7 @@ public class FlowApplication { public static void main(String[] args) { SpringApplication app = configureApplication(); + app.run(args); } diff --git a/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java b/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java index 5e435bcd..63cc9f26 100644 --- a/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java +++ b/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java @@ -13,6 +13,7 @@ import org.frankframework.lifecycle.servlets.AuthenticatorUtils; import org.frankframework.lifecycle.servlets.IAuthenticator; +import org.frankframework.lifecycle.servlets.SecuritySettings; import org.frankframework.security.config.ServletRegistration; import org.frankframework.util.ClassUtils; @@ -36,38 +37,34 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.matcher.AnyRequestMatcher; -/** - * Enable security, although it's anonymous on all endpoints, but at least sets the - * SecurityContextHolder.getContext().getAuthentication() object. - */ @Configuration @EnableWebSecurity @EnableMethodSecurity(jsr250Enabled = true, prePostEnabled = false) @Order(Ordered.HIGHEST_PRECEDENCE) public class SecurityChainConfigurer implements ApplicationContextAware { - private @Setter ApplicationContext applicationContext; + private ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + SecuritySettings.setupDefaultSecuritySettings(applicationContext.getEnvironment()); + } @Bean - public SecurityFilterChain configureChain(IAuthenticator authenticator, HttpSecurity http) throws Exception { + public SecurityFilterChain configureChain(/*IAuthenticator authenticator,*/ HttpSecurity http) throws Exception { http.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)); http.csrf(CsrfConfigurer::disable); http.securityMatcher(AnyRequestMatcher.INSTANCE); http.formLogin(FormLoginConfigurer::disable); http.logout(LogoutConfigurer::disable); -// http.anonymous(anonymous -> anonymous.authorities(getAuthorities())); -// http.authorizeHttpRequests(requests -> requests.requestMatchers(AnyRequestMatcher.INSTANCE).permitAll()); http.sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)); - return authenticator.configureHttpSecurity(http); + // Doesn't work because it requires endpoints to be defined, WebConfiguration sets up te endpoints and I don't know how to get those here yet. + // return authenticator.configureHttpSecurity(http); + return http.build(); } - /*private List getAuthorities() { - return Arrays.stream(Servlet.ALL_IBIS_USER_ROLES) - .map(role -> (GrantedAuthority) new SimpleGrantedAuthority("ROLE_" + role)) - .toList(); - }*/ - @Bean public IAuthenticator flowAuthenticator() { String propertyPrefix = "application.security.flow.authentication."; diff --git a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java index 22eedf6d..49d741ad 100644 --- a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java +++ b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java @@ -7,12 +7,15 @@ import org.frankframework.management.gateway.InputStreamHttpMessageConverter; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverters; +import org.springframework.integration.channel.PublishSubscribeChannel; +import org.springframework.messaging.SubscribableChannel; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import org.springframework.web.method.HandlerTypePredicate; @@ -55,6 +58,11 @@ public void setEnvironment(Environment environment) { gatewayClassName = environment.getProperty("management.gateway.outbound.class", String.class, LocalGateway.class.getCanonicalName()); } + @Bean("frank-management-bus") + public SubscribableChannel frankManagementBus() { + return new PublishSubscribeChannel(); + } + @Bean public OutboundGatewayFactory outboundGateway() { OutboundGatewayFactory factory = new OutboundGatewayFactory(); diff --git a/src/main/java/org/frankframework/flow/security/UserWorkspaceContext.java b/src/main/java/org/frankframework/flow/security/UserWorkspaceContext.java index ee3cef76..4732a251 100644 --- a/src/main/java/org/frankframework/flow/security/UserWorkspaceContext.java +++ b/src/main/java/org/frankframework/flow/security/UserWorkspaceContext.java @@ -9,7 +9,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; -//@Component +@Component // TODO replace this for Spring Session/Integration so it can be used with the framework and standalone properly @RequestScope(proxyMode = ScopedProxyMode.TARGET_CLASS) @Getter @Setter diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a9a26def..35f624d7 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,3 +5,7 @@ spring.web.resources.static-locations=classpath:/frontend/ spring.servlet.multipart.max-file-size=50MB spring.servlet.multipart.max-request-size=50MB + +#dtap.stage=LOC +#application.security.http.authentication=false +#application.security.http.transportGuarantee=NONE From 56c0bb057df090832c2cfc9fc782a49f3c45e04b Mon Sep 17 00:00:00 2001 From: Vivy <4380412+Matthbo@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:37:02 +0200 Subject: [PATCH 07/20] Make flow and framework detect each other --- docker/Dockerfile | 2 +- flow-framework-compose.yml | 22 +++++++++ pom.xml | 8 ++++ .../flow/common/ClusterMembers.java | 48 +++++++++++++++++++ .../flow/common/FrankGateway.java | 41 ++++++++++++++++ .../config/SecurityChainConfigurer.java | 14 ------ .../flow/common/config/WebConfiguration.java | 28 +---------- .../flow/security/UserContextFilter.java | 2 - .../flow/utility/ResponseUtils.java | 9 ++-- 9 files changed, 126 insertions(+), 48 deletions(-) create mode 100644 flow-framework-compose.yml create mode 100644 src/main/java/org/frankframework/flow/common/ClusterMembers.java create mode 100644 src/main/java/org/frankframework/flow/common/FrankGateway.java diff --git a/docker/Dockerfile b/docker/Dockerfile index d5a2c93c..5439cc12 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM eclipse-temurin:21-jre-jammy AS runner +FROM eclipse-temurin:25-jre-jammy AS runner ARG GID=2000 ARG UID=2000 WORKDIR /app diff --git a/flow-framework-compose.yml b/flow-framework-compose.yml new file mode 100644 index 00000000..24ff1510 --- /dev/null +++ b/flow-framework-compose.yml @@ -0,0 +1,22 @@ +services: + flow: + image: ghcr.io/frankframework/flow:latest + build: + dockerfile: ./docker/Dockerfile + context: . + environment: + SPRING_PROFILES_ACTIVE: cloud + management.gateway.outbound.class: org.frankframework.management.gateway.HazelcastOutboundGateway + ports: + - "8080:8080" + - "5700-5709:5700-5709" + + ff-test: + image: frankframework/frankframework:latest + ports: + - "8081:8080" + - "5710-5720:5700-5710" + environment: + larva.adapter.active: false + management.gateway.inbound.class: org.frankframework.management.gateway.HazelcastInboundGateway + management.gateway.outbound.class: org.frankframework.management.gateway.HazelcastOutboundGateway diff --git a/pom.xml b/pom.xml index 1a9913a8..46d76e04 100644 --- a/pom.xml +++ b/pom.xml @@ -71,6 +71,9 @@ 0.8.14 1.28.0 + + 5.6.0 + frank-framework https://sonarcloud.io @@ -191,6 +194,11 @@ ${frankframework.version} compile + + com.hazelcast + hazelcast + ${hazelcast.version} + diff --git a/src/main/java/org/frankframework/flow/common/ClusterMembers.java b/src/main/java/org/frankframework/flow/common/ClusterMembers.java new file mode 100644 index 00000000..b052a503 --- /dev/null +++ b/src/main/java/org/frankframework/flow/common/ClusterMembers.java @@ -0,0 +1,48 @@ +package org.frankframework.flow.common; + +import lombok.extern.log4j.Log4j2; + +import org.frankframework.flow.common.config.ClientSession; +import org.frankframework.flow.utility.ResponseUtils; +import org.frankframework.management.bus.OutboundGateway; +import org.frankframework.management.bus.message.JsonMessage; +import org.frankframework.management.gateway.events.ClusterMemberEvent; + +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) +@Log4j2 +public class ClusterMembers implements ApplicationListener { + + private final ClientSession session; + + private final OutboundGateway outboundGateway; + + public ClusterMembers(ClientSession session, OutboundGateway outboundGateway) { + this.session = session; + this.outboundGateway = outboundGateway; + } + +// @RolesAllowed({"IbisObserver", "IbisDataAdmin", "IbisAdmin", "IbisTester"}) +// @Relation("cluster") + @GetMapping(value = "/cluster/members", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity getClusterMembers() { + List members = outboundGateway.getMembers(); + JsonMessage response = new JsonMessage(members); + return ResponseUtils.convertToSpringResponse(response); + } + + @Override + public void onApplicationEvent(ClusterMemberEvent event) { + log.info("[{}]: {}", event.getType(), event.getMember()); + } +} diff --git a/src/main/java/org/frankframework/flow/common/FrankGateway.java b/src/main/java/org/frankframework/flow/common/FrankGateway.java new file mode 100644 index 00000000..90aa83a9 --- /dev/null +++ b/src/main/java/org/frankframework/flow/common/FrankGateway.java @@ -0,0 +1,41 @@ +package org.frankframework.flow.common; + +import org.frankframework.management.bus.LocalGateway; +import org.frankframework.management.bus.OutboundGatewayFactory; + +import org.frankframework.management.security.JwtKeyGenerator; + +import org.springframework.context.EnvironmentAware; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; +import org.springframework.integration.channel.PublishSubscribeChannel; +import org.springframework.messaging.SubscribableChannel; +import org.springframework.stereotype.Component; + +@Component +public class FrankGateway implements EnvironmentAware { + + private String gatewayClassName; + + @Override + public void setEnvironment(Environment environment) { + gatewayClassName = environment.getProperty("management.gateway.outbound.class", String.class, LocalGateway.class.getCanonicalName()); + } + + @Bean("frank-management-bus") + public SubscribableChannel frankManagementBus() { + return new PublishSubscribeChannel(); + } + + @Bean + public OutboundGatewayFactory outboundGateway() { + OutboundGatewayFactory factory = new OutboundGatewayFactory(); + factory.setGatewayClassname(gatewayClassName); + return factory; + } + + @Bean + public JwtKeyGenerator jwtKeyGenerator() { + return new JwtKeyGenerator(); + } +} diff --git a/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java b/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java index 63cc9f26..56b06cbe 100644 --- a/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java +++ b/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java @@ -1,21 +1,9 @@ package org.frankframework.flow.common.config; -import java.util.Arrays; -import java.util.List; - -import lombok.Setter; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.frankframework.lifecycle.DynamicRegistration.Servlet; - import org.frankframework.lifecycle.servlets.AuthenticatorUtils; import org.frankframework.lifecycle.servlets.IAuthenticator; import org.frankframework.lifecycle.servlets.SecuritySettings; -import org.frankframework.security.config.ServletRegistration; -import org.frankframework.util.ClassUtils; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; @@ -32,8 +20,6 @@ import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.matcher.AnyRequestMatcher; diff --git a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java index 49d741ad..b46ff81c 100644 --- a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java +++ b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java @@ -2,20 +2,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import org.frankframework.management.bus.LocalGateway; -import org.frankframework.management.bus.OutboundGatewayFactory; import org.frankframework.management.gateway.InputStreamHttpMessageConverter; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; -import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverters; -import org.springframework.integration.channel.PublishSubscribeChannel; -import org.springframework.messaging.SubscribableChannel; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import org.springframework.web.method.HandlerTypePredicate; @@ -24,9 +16,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration -public class WebConfiguration implements WebMvcConfigurer, EnvironmentAware { - - private String gatewayClassName; +public class WebConfiguration implements WebMvcConfigurer { private static final long MAX_AGE_SECONDS = 3600; @@ -53,22 +43,6 @@ public void configureMessageConverters(HttpMessageConverters.ServerBuilder build builder.addCustomConverter(new InputStreamHttpMessageConverter()); } - @Override - public void setEnvironment(Environment environment) { - gatewayClassName = environment.getProperty("management.gateway.outbound.class", String.class, LocalGateway.class.getCanonicalName()); - } - - @Bean("frank-management-bus") - public SubscribableChannel frankManagementBus() { - return new PublishSubscribeChannel(); - } - - @Bean - public OutboundGatewayFactory outboundGateway() { - OutboundGatewayFactory factory = new OutboundGatewayFactory(); - factory.setGatewayClassname(gatewayClassName); - return factory; - } @Bean public ObjectMapper objectMapper() { diff --git a/src/main/java/org/frankframework/flow/security/UserContextFilter.java b/src/main/java/org/frankframework/flow/security/UserContextFilter.java index 32d9480d..6fae90ef 100644 --- a/src/main/java/org/frankframework/flow/security/UserContextFilter.java +++ b/src/main/java/org/frankframework/flow/security/UserContextFilter.java @@ -18,8 +18,6 @@ import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - //@Component @Slf4j public class UserContextFilter extends HttpFilter { diff --git a/src/main/java/org/frankframework/flow/utility/ResponseUtils.java b/src/main/java/org/frankframework/flow/utility/ResponseUtils.java index 113a8005..b620c046 100644 --- a/src/main/java/org/frankframework/flow/utility/ResponseUtils.java +++ b/src/main/java/org/frankframework/flow/utility/ResponseUtils.java @@ -33,7 +33,8 @@ public static ResponseEntity convertToSpringStreamingResp StreamingResponseBody response = outputStream -> { InputStream inputStream = message.getPayload(); int numberOfBytesToWrite; - byte[] data = new byte[1024]; + final int STUPID_CHECKSTYLE_DOESNT_WANT_MAGIC_NUMBERS_SO_BUFFER_SIZE_IS_NOW_A_DUMB_CONST_WITH_A_LONG_NAME = 1024; + byte[] data = new byte[STUPID_CHECKSTYLE_DOESNT_WANT_MAGIC_NUMBERS_SO_BUFFER_SIZE_IS_NOW_A_DUMB_CONST_WITH_A_LONG_NAME]; while ((numberOfBytesToWrite = inputStream.read(data, 0, data.length)) != -1) { outputStream.write(data, 0, numberOfBytesToWrite); } @@ -43,7 +44,7 @@ public static ResponseEntity convertToSpringStreamingResp } public static ResponseEntity convertToSpringResponse(Message message, StreamingResponseBody response) { - int status = BusMessageUtils.getIntHeader(message, MessageBase.STATUS_KEY, 200); + int status = BusMessageUtils.getIntHeader(message, MessageBase.STATUS_KEY, HttpStatus.OK.value()); String mimeType = BusMessageUtils.getHeader(message, MessageBase.MIMETYPE_KEY, null); ResponseEntity.BodyBuilder responseEntity = ResponseEntity.status(status); HttpHeaders httpHeaders = new HttpHeaders(); @@ -74,12 +75,12 @@ public static ResponseEntity convertToSpringResponse(Message message, Stre * See {@link EmptyMessage} for more info. */ private static boolean hasPayload(int status) { - return (status == 200 || status > 204); + return (status == HttpStatus.OK.value() || status > HttpStatus.NO_CONTENT.value()); } @Nullable public static String parseAsString(Message message) throws ApiException { - int status = BusMessageUtils.getIntHeader(message, MessageBase.STATUS_KEY, 200); + int status = BusMessageUtils.getIntHeader(message, MessageBase.STATUS_KEY, HttpStatus.OK.value()); if (!hasPayload(status)) { return null; } From 3cdcd132484315e04358ccf11add98f566b60901 Mon Sep 17 00:00:00 2001 From: Vivy <4380412+Matthbo@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:19:01 +0200 Subject: [PATCH 08/20] Attempt to send message to framework --- .run/Flow Hazelcast Cluster.run.xml | 13 ++++++++++ flow-framework-compose.yml | 2 ++ .../flow/common/AllowAllFrankUserRoles.java | 24 +++++++++++++++++++ .../flow/common/ClusterMembers.java | 3 +-- .../flow/project/ProjectController.java | 19 ++++++++++++++- src/main/resources/application.properties | 6 ++--- 6 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 .run/Flow Hazelcast Cluster.run.xml create mode 100644 src/main/java/org/frankframework/flow/common/AllowAllFrankUserRoles.java diff --git a/.run/Flow Hazelcast Cluster.run.xml b/.run/Flow Hazelcast Cluster.run.xml new file mode 100644 index 00000000..83481673 --- /dev/null +++ b/.run/Flow Hazelcast Cluster.run.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/flow-framework-compose.yml b/flow-framework-compose.yml index 24ff1510..484decc0 100644 --- a/flow-framework-compose.yml +++ b/flow-framework-compose.yml @@ -7,6 +7,7 @@ services: environment: SPRING_PROFILES_ACTIVE: cloud management.gateway.outbound.class: org.frankframework.management.gateway.HazelcastOutboundGateway + logger.com.hazelcast: debug ports: - "8080:8080" - "5700-5709:5700-5709" @@ -17,6 +18,7 @@ services: - "8081:8080" - "5710-5720:5700-5710" environment: + jdbc.required: false larva.adapter.active: false management.gateway.inbound.class: org.frankframework.management.gateway.HazelcastInboundGateway management.gateway.outbound.class: org.frankframework.management.gateway.HazelcastOutboundGateway diff --git a/src/main/java/org/frankframework/flow/common/AllowAllFrankUserRoles.java b/src/main/java/org/frankframework/flow/common/AllowAllFrankUserRoles.java new file mode 100644 index 00000000..2b29ae71 --- /dev/null +++ b/src/main/java/org/frankframework/flow/common/AllowAllFrankUserRoles.java @@ -0,0 +1,24 @@ +package org.frankframework.flow.common; + +import jakarta.annotation.security.RolesAllowed; + +import org.frankframework.lifecycle.DynamicRegistration; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * To avoid repeating this list of user roles, use a default annotation + * + * @see DynamicRegistration#ALL_IBIS_USER_ROLES + */ +@Documented +@Retention(RUNTIME) +@Target(METHOD) +@RolesAllowed({ "IbisObserver", "IbisDataAdmin", "IbisAdmin", "IbisTester" }) +public @interface AllowAllFrankUserRoles { +} diff --git a/src/main/java/org/frankframework/flow/common/ClusterMembers.java b/src/main/java/org/frankframework/flow/common/ClusterMembers.java index b052a503..b1824069 100644 --- a/src/main/java/org/frankframework/flow/common/ClusterMembers.java +++ b/src/main/java/org/frankframework/flow/common/ClusterMembers.java @@ -32,8 +32,7 @@ public ClusterMembers(ClientSession session, OutboundGateway outboundGateway) { this.outboundGateway = outboundGateway; } -// @RolesAllowed({"IbisObserver", "IbisDataAdmin", "IbisAdmin", "IbisTester"}) -// @Relation("cluster") + @AllowAllFrankUserRoles @GetMapping(value = "/cluster/members", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity getClusterMembers() { List members = outboundGateway.getMembers(); diff --git a/src/main/java/org/frankframework/flow/project/ProjectController.java b/src/main/java/org/frankframework/flow/project/ProjectController.java index d536953e..35b1e3c5 100644 --- a/src/main/java/org/frankframework/flow/project/ProjectController.java +++ b/src/main/java/org/frankframework/flow/project/ProjectController.java @@ -2,11 +2,20 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletResponse; + import java.io.IOException; import java.util.List; + import lombok.extern.slf4j.Slf4j; + +import org.frankframework.flow.common.FrankFrameworkService; +import org.frankframework.flow.exception.ApiException; import org.frankframework.flow.projectsettings.InvalidFilterTypeException; import org.frankframework.flow.recentproject.RecentProjectsService; +import org.frankframework.management.bus.BusAction; +import org.frankframework.management.bus.BusTopic; +import org.frankframework.management.bus.message.RequestMessageBuilder; + import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -26,10 +35,12 @@ public class ProjectController { private final ProjectService projectService; private final RecentProjectsService recentProjectsService; + private final FrankFrameworkService frankFrameworkService; - public ProjectController(ProjectService projectService, RecentProjectsService recentProjectsService) { + public ProjectController(ProjectService projectService, RecentProjectsService recentProjectsService, FrankFrameworkService frankFrameworkService) { this.projectService = projectService; this.recentProjectsService = recentProjectsService; + this.frankFrameworkService = frankFrameworkService; } @GetMapping @@ -107,4 +118,10 @@ public ResponseEntity importProject( recentProjectsService.addRecentProject(project.getName(), project.getRootPath()); return ResponseEntity.ok(projectService.toDto(project)); } + + @GetMapping("/configurations") + public ResponseEntity getFrameworkConfigurations() throws ApiException { + RequestMessageBuilder builder = RequestMessageBuilder.create(BusTopic.CONFIGURATION, BusAction.GET); + return frankFrameworkService.callSyncGateway(builder); + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 35f624d7..f2f469a5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,6 +6,6 @@ spring.web.resources.static-locations=classpath:/frontend/ spring.servlet.multipart.max-file-size=50MB spring.servlet.multipart.max-request-size=50MB -#dtap.stage=LOC -#application.security.http.authentication=false -#application.security.http.transportGuarantee=NONE +# Hazelcast +logger.com.hazelcast=debug +hazelcast.logging.details.enabled=false From f905469fc89c8e843f50d4337d159a94521b4b7b Mon Sep 17 00:00:00 2001 From: Vivy <4380412+Matthbo@users.noreply.github.com> Date: Thu, 16 Apr 2026 11:49:37 +0200 Subject: [PATCH 09/20] Move UserContext logic to ClientSession --- .../flow/common/config/ClientSession.java | 52 ++++++++-- .../CloudFileSystemStorageService.java | 11 ++- .../flow/security/UserContextFilter.java | 97 ------------------- .../flow/security/UserWorkspaceContext.java | 25 ----- 4 files changed, 49 insertions(+), 136 deletions(-) delete mode 100644 src/main/java/org/frankframework/flow/security/UserContextFilter.java delete mode 100644 src/main/java/org/frankframework/flow/security/UserWorkspaceContext.java diff --git a/src/main/java/org/frankframework/flow/common/config/ClientSession.java b/src/main/java/org/frankframework/flow/common/config/ClientSession.java index 9959a728..9942bcc5 100644 --- a/src/main/java/org/frankframework/flow/common/config/ClientSession.java +++ b/src/main/java/org/frankframework/flow/common/config/ClientSession.java @@ -1,16 +1,21 @@ package org.frankframework.flow.common.config; +import jakarta.servlet.http.HttpServletRequest; + import lombok.Getter; import org.frankframework.management.bus.OutboundGateway; import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.SessionScope; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HexFormat; import java.util.List; import java.util.UUID; @@ -18,15 +23,17 @@ @SessionScope public class ClientSession implements InitializingBean { - @Autowired - @Qualifier("outboundGateway") - private OutboundGateway outboundGateway; - - /** - * Get target or `NULL` when no target has been specified or `afterPropertiesSet` has not been called yet. - */ @Nullable private @Getter UUID memberTarget; + private final OutboundGateway outboundGateway; + private final @Getter String workspaceId; + + private static final int SESSION_HASH_LENGTH = 16; + + public ClientSession(@Qualifier("outboundGateway") OutboundGateway outboundGateway, HttpServletRequest request) { + this.outboundGateway = outboundGateway; + this.workspaceId = determineWorkspaceId(request); + } public void setMemberTarget(@Nullable UUID id) { this.memberTarget = id; @@ -36,7 +43,10 @@ public void setMemberTarget(String id) { setMemberTarget(UUID.fromString(id)); } - // When a new session is created, assign a default target + /* + When a new session is created, assign a default target + Maybe we don't want this for the flow, but what if every instance has the same configurations? + */ @Override public void afterPropertiesSet() throws Exception { List members = outboundGateway.getMembers(); @@ -49,4 +59,28 @@ public void afterPropertiesSet() throws Exception { }); } + private String determineWorkspaceId(HttpServletRequest request) { + + String sessionId = request.getHeader("X-Session-ID"); + if (sessionId != null && !sessionId.isBlank()) { + return "anon-" + hashSessionId(sessionId); + } + + return "anonymous"; + } + + private String hashSessionId(String sessionId) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(sessionId.getBytes(StandardCharsets.UTF_8)); + return HexFormat.of().formatHex(hash).substring(0, SESSION_HASH_LENGTH); + } catch (NoSuchAlgorithmException e) { + return sanitize(sessionId); + } + } + + private String sanitize(String input) { + return input.replaceAll("[^a-zA-Z0-9.@-]", "_"); + } + } diff --git a/src/main/java/org/frankframework/flow/filesystem/CloudFileSystemStorageService.java b/src/main/java/org/frankframework/flow/filesystem/CloudFileSystemStorageService.java index 7fd2a65b..0b89ba47 100644 --- a/src/main/java/org/frankframework/flow/filesystem/CloudFileSystemStorageService.java +++ b/src/main/java/org/frankframework/flow/filesystem/CloudFileSystemStorageService.java @@ -12,7 +12,8 @@ import java.util.List; import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; -import org.frankframework.flow.security.UserWorkspaceContext; + +import org.frankframework.flow.common.config.ClientSession; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; @@ -24,14 +25,14 @@ public class CloudFileSystemStorageService implements FileSystemStorage { @Value("${frankflow.workspace.root:/tmp/frankflow/workspace}") private String baseWorkspacePath; - private final UserWorkspaceContext userContext; + private final ClientSession session; - public CloudFileSystemStorageService(UserWorkspaceContext userContext) { - this.userContext = userContext; + public CloudFileSystemStorageService(ClientSession session) { + this.session = session; } private Path getUserRootPath() { - String workspaceId = userContext.getWorkspaceId(); + String workspaceId = session.getWorkspaceId(); if (workspaceId == null) workspaceId = "anonymous"; return Paths.get(baseWorkspacePath, workspaceId).toAbsolutePath().normalize(); diff --git a/src/main/java/org/frankframework/flow/security/UserContextFilter.java b/src/main/java/org/frankframework/flow/security/UserContextFilter.java deleted file mode 100644 index 6fae90ef..00000000 --- a/src/main/java/org/frankframework/flow/security/UserContextFilter.java +++ /dev/null @@ -1,97 +0,0 @@ -package org.frankframework.flow.security; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpFilter; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; -import java.util.HexFormat; - -import lombok.extern.slf4j.Slf4j; - -//@Component -@Slf4j -public class UserContextFilter extends HttpFilter { - - private static final String BEARER_PREFIX = "Bearer "; - private static final int SESSION_HASH_LENGTH = 16; - private static final int MIN_JWT_PARTS = 2; - - private final UserWorkspaceContext userWorkspaceContext; - private final ObjectMapper objectMapper; - - public UserContextFilter(UserWorkspaceContext userWorkspaceContext, ObjectMapper objectMapper) { - this.userWorkspaceContext = userWorkspaceContext; - this.objectMapper = objectMapper; - } - - @Override - protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) - throws IOException, ServletException { - - if (!userWorkspaceContext.isInitialized()) { - String workspaceId = determineWorkspaceId(request); - userWorkspaceContext.initialize(workspaceId); - log.debug("Context initialized for workspace: {}", workspaceId); - } - - chain.doFilter(request, response); - } - - private String determineWorkspaceId(HttpServletRequest request) { - String authHeader = request.getHeader("Authorization"); - if (authHeader != null && authHeader.startsWith(BEARER_PREFIX)) { - String userId = extractUserIdFromJwt(authHeader.substring(BEARER_PREFIX.length())); - if (userId != null) return sanitize(userId); - } - - String sessionId = request.getHeader("X-Session-ID"); - if (sessionId != null && !sessionId.isBlank()) { - return "anon-" + hashSessionId(sessionId); - } - - return "anonymous"; - } - - private String hashSessionId(String sessionId) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - byte[] hash = digest.digest(sessionId.getBytes(StandardCharsets.UTF_8)); - return HexFormat.of().formatHex(hash).substring(0, SESSION_HASH_LENGTH); - } catch (NoSuchAlgorithmException e) { - return sanitize(sessionId); - } - } - - private String sanitize(String input) { - return input.replaceAll("[^a-zA-Z0-9.@-]", "_"); - } - - private String extractUserIdFromJwt(String token) { - try { - String[] parts = token.split("\\."); - if (parts.length < MIN_JWT_PARTS) return null; - String payload = new String(Base64.getUrlDecoder().decode(parts[1])); - JsonNode claims = objectMapper.readTree(payload); - - if (!claims.path("sub").isMissingNode()) { - return claims.path("sub").asText(); - } - if (!claims.path("preferred_username").isMissingNode()) { - return claims.path("preferred_username").asText(); - } - } catch (Exception e) { - log.warn("Invalid JWT format"); - } - return null; - } -} diff --git a/src/main/java/org/frankframework/flow/security/UserWorkspaceContext.java b/src/main/java/org/frankframework/flow/security/UserWorkspaceContext.java deleted file mode 100644 index 4732a251..00000000 --- a/src/main/java/org/frankframework/flow/security/UserWorkspaceContext.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.frankframework.flow.security; - -import java.io.Serializable; - -import lombok.Getter; -import lombok.Setter; - -import org.springframework.context.annotation.ScopedProxyMode; -import org.springframework.stereotype.Component; -import org.springframework.web.context.annotation.RequestScope; - -@Component // TODO replace this for Spring Session/Integration so it can be used with the framework and standalone properly -@RequestScope(proxyMode = ScopedProxyMode.TARGET_CLASS) -@Getter -@Setter -public class UserWorkspaceContext implements Serializable { - - private String workspaceId; - private boolean initialized = false; - - public void initialize(String id) { - this.workspaceId = id; - this.initialized = true; - } -} From b9e5583cd3b16e8f4cb1a66db753da1928260c93 Mon Sep 17 00:00:00 2001 From: Vivy <4380412+Matthbo@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:34:57 +0200 Subject: [PATCH 10/20] Tests and losing my mind --- flow-framework-compose.yml | 22 +- .../routes/projectlanding/project-landing.tsx | 61 ++---- .../app/services/frank-framework-service.ts | 13 ++ .../app/services/hazelcast-service.ts | 11 - .../flow/project/ProjectController.java | 2 +- .../flow/adapter/AdapterControllerTest.java | 8 +- .../flow/common/config/ClientSessionTest.java | 108 ++++++++++ .../ConfigurationControllerTest.java | 6 - .../CloudFileSystemStorageServiceTest.java | 39 ++-- .../FrankConfigXsdControllerTest.java | 8 - .../flow/frankdoc/FrankDocControllerTest.java | 8 - .../flow/git/GitControllerTest.java | 6 - .../flow/project/ProjectControllerTest.java | 8 - .../flow/security/UserContextFilterTest.java | 188 ------------------ 14 files changed, 184 insertions(+), 304 deletions(-) create mode 100644 src/main/frontend/app/services/frank-framework-service.ts delete mode 100644 src/main/frontend/app/services/hazelcast-service.ts create mode 100644 src/test/java/org/frankframework/flow/common/config/ClientSessionTest.java delete mode 100644 src/test/java/org/frankframework/flow/security/UserContextFilterTest.java diff --git a/flow-framework-compose.yml b/flow-framework-compose.yml index 484decc0..c80cef78 100644 --- a/flow-framework-compose.yml +++ b/flow-framework-compose.yml @@ -17,8 +17,22 @@ services: ports: - "8081:8080" - "5710-5720:5700-5710" + configs: + - source: resources.yml + target: /opt/frank/resources/resources.yml environment: - jdbc.required: false - larva.adapter.active: false - management.gateway.inbound.class: org.frankframework.management.gateway.HazelcastInboundGateway - management.gateway.outbound.class: org.frankframework.management.gateway.HazelcastOutboundGateway + - instance.name=Flow-FF-Network + - dtap.stage=LOC + - jdbc.required=false + - larva.enabled=false + - testtool.enabled=false + - management.gateway.inbound.class=org.frankframework.management.gateway.HazelcastInboundGateway + - management.gateway.outbound.class=org.frankframework.management.gateway.HazelcastOutboundGateway + +configs: + resources.yml: + content: | + jdbc: + - name: "ff-quick-start" + type: "org.h2.jdbcx.JdbcDataSource" + url: "jdbc:h2:mem:test;NON_KEYWORDS=VALUE;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1;TRACE_LEVEL_FILE=0;" diff --git a/src/main/frontend/app/routes/projectlanding/project-landing.tsx b/src/main/frontend/app/routes/projectlanding/project-landing.tsx index 96d78477..1c33ce3c 100644 --- a/src/main/frontend/app/routes/projectlanding/project-landing.tsx +++ b/src/main/frontend/app/routes/projectlanding/project-landing.tsx @@ -2,6 +2,7 @@ import { useEffect, useState, useCallback, useRef } from 'react' import { useNavigate } from 'react-router' import FfIcon from '/icons/custom/ff!-icon.svg?react' import ArchiveIcon from '/icons/solar/Archive.svg?react' +import {fetchInstanceConfigurations, type FFConfiguration} from "~/services/frank-framework-service"; import { useProjectStore } from '~/stores/project-store' import ProjectRow from './project-row' @@ -24,7 +25,6 @@ import { } from '~/services/project-service' import { useRecentProjects } from '~/hooks/use-projects' import { showErrorToast } from '~/components/toast' -import { discoverFrankInstances, type FrankInstance } from '~/services/hazelcast-service' export default function ProjectLanding() { const navigate = useNavigate() @@ -41,7 +41,6 @@ export default function ProjectLanding() { const [isLocalEnvironment, setIsLocalEnvironment] = useState(true) const [rootLocationName, setRootLocationName] = useState('Computer') const [isOpeningProject, setIsOpeningProject] = useState(false) - const [frankInstances, setFrankInstances] = useState([]) const [isDiscovering, setIsDiscovering] = useState(false) const importInputRef = useRef(null) @@ -75,11 +74,8 @@ export default function ProjectLanding() { const controller = new AbortController() const discover = () => { - discoverFrankInstances(controller.signal) - .then(setFrankInstances) - .catch(() => { - // Discovery failure is non-critical; Hazelcast may not be running - }) + fetchInstanceConfigurations(controller.signal) + .then((ffConfigurations) => console.log('Found FF configurations:', ffConfigurations)) .finally(() => setIsDiscovering(false)) } @@ -181,27 +177,6 @@ export default function ProjectLanding() { } } - const handleConnectToInstance = useCallback( - async (instance: FrankInstance) => { - const path = instance.projectPath - if (!path) { - showErrorToast(`No configuration path available for "${instance.name}"`) - return - } - setIsOpeningProject(true) - try { - const project = await openProject(path) - setProject(project) - navigate(`/studio/${encodeURIComponent(project.name)}`) - } catch (error) { - showErrorToast(error instanceof Error ? error.message : `Failed to open remote instance "${instance.name}"`) - } finally { - setIsOpeningProject(false) - } - }, - [navigate, setProject], - ) - const projects = recentProjects ?? [] const filteredProjects = projects.filter((project) => project.name.toLowerCase().includes(searchTerm.toLowerCase())) @@ -213,7 +188,7 @@ export default function ProjectLanding() {
-
+
@@ -230,9 +205,8 @@ export default function ProjectLanding() { onProjectClick={handleOpenProject} onRemoveProject={onRemoveProject} onExportProject={onExportProject} - frankInstances={frankInstances} + frameworkConfigurations={[]} isDiscovering={isDiscovering} - onConnectToInstance={handleConnectToInstance} />
@@ -300,7 +274,7 @@ const Sidebar = ({ onCloneClick: () => void onImportClick: () => void }) => ( -
diff --git a/src/main/frontend/app/services/frank-framework-service.ts b/src/main/frontend/app/services/frank-framework-service.ts index 022cb744..92a3e45f 100644 --- a/src/main/frontend/app/services/frank-framework-service.ts +++ b/src/main/frontend/app/services/frank-framework-service.ts @@ -1,5 +1,10 @@ import { apiFetch } from '~/utils/api' +interface FFInstance { + name: string + configurations: FFConfiguration[] +} + export interface FFConfiguration { name: string stubbed: boolean @@ -8,6 +13,6 @@ export interface FFConfiguration { filename?: string } -export async function fetchInstanceConfigurations(signal?: AbortSignal): Promise { - return apiFetch('/projects/configurations', { signal }) +export async function fetchInstanceConfigurations(signal?: AbortSignal): Promise { + return apiFetch('/projects/configurations', { signal }) } diff --git a/src/main/java/org/frankframework/flow/common/AllowAllFrankUserRoles.java b/src/main/java/org/frankframework/flow/common/AllowAllFrankUserRoles.java index 2b29ae71..e1ca978b 100644 --- a/src/main/java/org/frankframework/flow/common/AllowAllFrankUserRoles.java +++ b/src/main/java/org/frankframework/flow/common/AllowAllFrankUserRoles.java @@ -1,15 +1,13 @@ package org.frankframework.flow.common; -import jakarta.annotation.security.RolesAllowed; - -import org.frankframework.lifecycle.DynamicRegistration; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import jakarta.annotation.security.RolesAllowed; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.RUNTIME; +import org.frankframework.lifecycle.DynamicRegistration; /** * To avoid repeating this list of user roles, use a default annotation diff --git a/src/main/java/org/frankframework/flow/common/ClusterMembers.java b/src/main/java/org/frankframework/flow/common/ClusterMembers.java index b1824069..5f16e3e2 100644 --- a/src/main/java/org/frankframework/flow/common/ClusterMembers.java +++ b/src/main/java/org/frankframework/flow/common/ClusterMembers.java @@ -1,13 +1,12 @@ package org.frankframework.flow.common; +import java.util.List; import lombok.extern.log4j.Log4j2; - import org.frankframework.flow.common.config.ClientSession; import org.frankframework.flow.utility.ResponseUtils; import org.frankframework.management.bus.OutboundGateway; import org.frankframework.management.bus.message.JsonMessage; import org.frankframework.management.gateway.events.ClusterMemberEvent; - import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; @@ -16,8 +15,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.List; - @RestController @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) @Log4j2 diff --git a/src/main/java/org/frankframework/flow/common/FrankFrameworkService.java b/src/main/java/org/frankframework/flow/common/FrankFrameworkService.java index ff3c370e..48ac1337 100644 --- a/src/main/java/org/frankframework/flow/common/FrankFrameworkService.java +++ b/src/main/java/org/frankframework/flow/common/FrankFrameworkService.java @@ -1,7 +1,8 @@ package org.frankframework.flow.common; +import java.util.List; +import java.util.UUID; import lombok.Getter; - import org.frankframework.flow.common.config.ClientSession; import org.frankframework.flow.exception.ApiException; import org.frankframework.flow.utility.ResponseUtils; @@ -9,7 +10,6 @@ import org.frankframework.management.bus.OutboundGateway; import org.frankframework.management.bus.message.RequestMessageBuilder; import org.frankframework.management.gateway.events.ClusterMemberEvent; - import org.jspecify.annotations.NonNull; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; @@ -22,9 +22,6 @@ import org.springframework.messaging.Message; import org.springframework.stereotype.Service; -import java.util.List; -import java.util.UUID; - @Service public class FrankFrameworkService implements ApplicationContextAware, InitializingBean, ApplicationListener { diff --git a/src/main/java/org/frankframework/flow/common/FrankGateway.java b/src/main/java/org/frankframework/flow/common/FrankGateway.java index 90aa83a9..3f8cedff 100644 --- a/src/main/java/org/frankframework/flow/common/FrankGateway.java +++ b/src/main/java/org/frankframework/flow/common/FrankGateway.java @@ -2,9 +2,7 @@ import org.frankframework.management.bus.LocalGateway; import org.frankframework.management.bus.OutboundGatewayFactory; - import org.frankframework.management.security.JwtKeyGenerator; - import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; diff --git a/src/main/java/org/frankframework/flow/common/config/ClientSession.java b/src/main/java/org/frankframework/flow/common/config/ClientSession.java index 9942bcc5..7c9bfb62 100644 --- a/src/main/java/org/frankframework/flow/common/config/ClientSession.java +++ b/src/main/java/org/frankframework/flow/common/config/ClientSession.java @@ -1,24 +1,20 @@ package org.frankframework.flow.common.config; import jakarta.servlet.http.HttpServletRequest; - +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HexFormat; +import java.util.List; +import java.util.UUID; import lombok.Getter; - import org.frankframework.management.bus.OutboundGateway; - import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.SessionScope; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.HexFormat; -import java.util.List; -import java.util.UUID; - @Component @SessionScope public class ClientSession implements InitializingBean { diff --git a/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java b/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java index 56b06cbe..56593e09 100644 --- a/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java +++ b/src/main/java/org/frankframework/flow/common/config/SecurityChainConfigurer.java @@ -2,9 +2,8 @@ import org.frankframework.lifecycle.servlets.AuthenticatorUtils; import org.frankframework.lifecycle.servlets.IAuthenticator; - import org.frankframework.lifecycle.servlets.SecuritySettings; - +import org.frankframework.lifecycle.servlets.ServletConfiguration; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -38,17 +37,15 @@ public void setApplicationContext(ApplicationContext applicationContext) throws } @Bean - public SecurityFilterChain configureChain(/*IAuthenticator authenticator,*/ HttpSecurity http) throws Exception { + public SecurityFilterChain configureChain(IAuthenticator authenticator, HttpSecurity http) throws Exception { + configureAuthenticator(authenticator); http.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)); http.csrf(CsrfConfigurer::disable); http.securityMatcher(AnyRequestMatcher.INSTANCE); http.formLogin(FormLoginConfigurer::disable); http.logout(LogoutConfigurer::disable); http.sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)); - - // Doesn't work because it requires endpoints to be defined, WebConfiguration sets up te endpoints and I don't know how to get those here yet. - // return authenticator.configureHttpSecurity(http); - return http.build(); + return authenticator.configureHttpSecurity(http); } @Bean @@ -56,4 +53,10 @@ public IAuthenticator flowAuthenticator() { String propertyPrefix = "application.security.flow.authentication."; return AuthenticatorUtils.createAuthenticator(applicationContext, propertyPrefix); } + + private void configureAuthenticator(IAuthenticator authenticator) { + ServletConfiguration servletConfig = new ServletConfiguration(); + servletConfig.setUrlMapping("/*"); + authenticator.registerServlet(servletConfig); + } } diff --git a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java index b46ff81c..3c02f70f 100644 --- a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java +++ b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java @@ -1,9 +1,7 @@ package org.frankframework.flow.common.config; import com.fasterxml.jackson.databind.ObjectMapper; - import org.frankframework.management.gateway.InputStreamHttpMessageConverter; - import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/org/frankframework/flow/filesystem/CloudFileSystemStorageService.java b/src/main/java/org/frankframework/flow/filesystem/CloudFileSystemStorageService.java index 0b89ba47..e25973ca 100644 --- a/src/main/java/org/frankframework/flow/filesystem/CloudFileSystemStorageService.java +++ b/src/main/java/org/frankframework/flow/filesystem/CloudFileSystemStorageService.java @@ -12,7 +12,6 @@ import java.util.List; import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; - import org.frankframework.flow.common.config.ClientSession; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; diff --git a/src/main/java/org/frankframework/flow/project/ProjectController.java b/src/main/java/org/frankframework/flow/project/ProjectController.java index 1e2493fb..7eb1ccc9 100644 --- a/src/main/java/org/frankframework/flow/project/ProjectController.java +++ b/src/main/java/org/frankframework/flow/project/ProjectController.java @@ -2,20 +2,20 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletResponse; - import java.io.IOException; +import java.util.HashMap; import java.util.List; - +import java.util.Map; import lombok.extern.slf4j.Slf4j; - +import org.frankframework.flow.common.AllowAllFrankUserRoles; import org.frankframework.flow.common.FrankFrameworkService; +import org.frankframework.flow.common.config.ClientSession; import org.frankframework.flow.exception.ApiException; import org.frankframework.flow.projectsettings.InvalidFilterTypeException; import org.frankframework.flow.recentproject.RecentProjectsService; import org.frankframework.management.bus.BusAction; import org.frankframework.management.bus.BusTopic; import org.frankframework.management.bus.message.RequestMessageBuilder; - import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -119,9 +119,15 @@ public ResponseEntity importProject( return ResponseEntity.ok(projectService.toDto(project)); } + @AllowAllFrankUserRoles @GetMapping("/configurations") - public ResponseEntity getFrameworkConfigurations() throws ApiException { + public Map getFrameworkConfigurations(ClientSession session) throws ApiException { RequestMessageBuilder builder = RequestMessageBuilder.create(BusTopic.CONFIGURATION, BusAction.FIND); - return frankFrameworkService.callSyncGateway(builder); + ResponseEntity configurations = frankFrameworkService.callSyncGateway(builder); + + Map response = new HashMap<>(); + response.put("configurations", configurations.getBody()); + response.put("name", session.getMemberTarget()); + return response; } } diff --git a/src/main/java/org/frankframework/flow/utility/ResponseUtils.java b/src/main/java/org/frankframework/flow/utility/ResponseUtils.java index b620c046..9dbe9540 100644 --- a/src/main/java/org/frankframework/flow/utility/ResponseUtils.java +++ b/src/main/java/org/frankframework/flow/utility/ResponseUtils.java @@ -1,13 +1,13 @@ package org.frankframework.flow.utility; +import java.io.IOException; +import java.io.InputStream; import lombok.NoArgsConstructor; - import org.frankframework.flow.exception.ApiException; import org.frankframework.management.bus.BusMessageUtils; import org.frankframework.management.bus.message.EmptyMessage; import org.frankframework.management.bus.message.MessageBase; import org.frankframework.util.StreamUtil; - import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.springframework.http.ContentDisposition; @@ -18,9 +18,6 @@ import org.springframework.messaging.Message; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; -import java.io.IOException; -import java.io.InputStream; - @NoArgsConstructor(access = lombok.AccessLevel.PRIVATE) public class ResponseUtils { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f2f469a5..89611282 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,6 +6,20 @@ spring.web.resources.static-locations=classpath:/frontend/ spring.servlet.multipart.max-file-size=50MB spring.servlet.multipart.max-request-size=50MB -# Hazelcast -logger.com.hazelcast=debug -hazelcast.logging.details.enabled=false +# Spring Application log settings +logging.level.root=INFO +logging.level.org.frankframework=INFO +logging.level.org.apache.coyote=WARN +logging.level.org.apache.tomcat=WARN +logging.level.org.apache.catalina=INFO +logging.level.org.apache.jasper=WARN +logging.level.jdk.event.security=WARN +logging.level.org.apache.naming=WARN +logging.level.org.springframework=WARN +logging.level.org.frankframework.management.gateway=DEBUG +logging.level.com.hazelcast=WARN +logging.level._org.springframework.web.servlet.HandlerMapping.Mappings=WARN + +spring.jmx.enabled=false + +application.security.http.authentication=false diff --git a/src/test/java/org/frankframework/flow/adapter/AdapterControllerTest.java b/src/test/java/org/frankframework/flow/adapter/AdapterControllerTest.java index 989da711..d5d3a290 100644 --- a/src/test/java/org/frankframework/flow/adapter/AdapterControllerTest.java +++ b/src/test/java/org/frankframework/flow/adapter/AdapterControllerTest.java @@ -7,8 +7,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import java.nio.file.Paths; - -import org.frankframework.flow.common.config.ClientSession; import org.frankframework.flow.configuration.ConfigurationNotFoundException; import org.frankframework.flow.xml.XmlDTO; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/frankframework/flow/filesystem/CloudFileSystemStorageServiceTest.java b/src/test/java/org/frankframework/flow/filesystem/CloudFileSystemStorageServiceTest.java index 5dbbbb6e..9efe202b 100644 --- a/src/test/java/org/frankframework/flow/filesystem/CloudFileSystemStorageServiceTest.java +++ b/src/test/java/org/frankframework/flow/filesystem/CloudFileSystemStorageServiceTest.java @@ -9,9 +9,7 @@ import java.nio.file.Path; import java.util.Comparator; import java.util.List; - import org.frankframework.flow.common.config.ClientSession; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; From 4760c568b38a9af7dfbd148a50a6002e0370b595 Mon Sep 17 00:00:00 2001 From: Vivy <4380412+Matthbo@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:13:54 +0200 Subject: [PATCH 12/20] Fix issues --- .../flow/common/config/WebConfiguration.java | 20 ++++++++++++++++ .../flow/project/ProjectController.java | 24 ++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java index 3c02f70f..04fb91f7 100644 --- a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java +++ b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java @@ -1,11 +1,19 @@ package org.frankframework.flow.common.config; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; + import org.frankframework.management.gateway.InputStreamHttpMessageConverter; + import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverters; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import org.springframework.web.method.HandlerTypePredicate; @@ -38,7 +46,19 @@ public void configurePathMatch(PathMatchConfigurer configurer) { @Override public void configureMessageConverters(HttpMessageConverters.ServerBuilder builder) { + JsonMapper jsonMapper = JsonMapper.builder() + .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true) + .configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true) + .configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false) // allow null value for boolean + .configure(SerializationFeature.INDENT_OUTPUT, true) // pretty print + .build(); + + builder.withJsonConverter(new MappingJackson2HttpMessageConverter(jsonMapper)); + builder.addCustomConverter(new InputStreamHttpMessageConverter()); + builder.addCustomConverter(new FormHttpMessageConverter()); } diff --git a/src/main/java/org/frankframework/flow/project/ProjectController.java b/src/main/java/org/frankframework/flow/project/ProjectController.java index 7eb1ccc9..320120aa 100644 --- a/src/main/java/org/frankframework/flow/project/ProjectController.java +++ b/src/main/java/org/frankframework/flow/project/ProjectController.java @@ -2,11 +2,14 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletResponse; + import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; + import lombok.extern.slf4j.Slf4j; + import org.frankframework.flow.common.AllowAllFrankUserRoles; import org.frankframework.flow.common.FrankFrameworkService; import org.frankframework.flow.common.config.ClientSession; @@ -16,6 +19,9 @@ import org.frankframework.management.bus.BusAction; import org.frankframework.management.bus.BusTopic; import org.frankframework.management.bus.message.RequestMessageBuilder; + +import org.frankframework.util.JacksonUtils; + import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -36,11 +42,13 @@ public class ProjectController { private final ProjectService projectService; private final RecentProjectsService recentProjectsService; private final FrankFrameworkService frankFrameworkService; + private final ClientSession session; - public ProjectController(ProjectService projectService, RecentProjectsService recentProjectsService, FrankFrameworkService frankFrameworkService) { + public ProjectController(ProjectService projectService, RecentProjectsService recentProjectsService, FrankFrameworkService frankFrameworkService, ClientSession session) { this.projectService = projectService; this.recentProjectsService = recentProjectsService; this.frankFrameworkService = frankFrameworkService; + this.session = session; } @GetMapping @@ -121,13 +129,23 @@ public ResponseEntity importProject( @AllowAllFrankUserRoles @GetMapping("/configurations") - public Map getFrameworkConfigurations(ClientSession session) throws ApiException { + public Map getFrameworkConfigurations() throws ApiException { RequestMessageBuilder builder = RequestMessageBuilder.create(BusTopic.CONFIGURATION, BusAction.FIND); ResponseEntity configurations = frankFrameworkService.callSyncGateway(builder); Map response = new HashMap<>(); - response.put("configurations", configurations.getBody()); + response.put("configurations", JacksonUtils.convertToDTO(configurations.getBody(), FFConfigurationDTO[].class)); response.put("name", session.getMemberTarget()); return response; } + + private record FFConfigurationDTO( + String name, + String version, + Boolean stubbed, + String directory, + String filename, + String parent + ) { + } } From d3e5fedb8d76cdb60571a722ae82d8f749d8eb13 Mon Sep 17 00:00:00 2001 From: Vivy <4380412+Matthbo@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:43:54 +0200 Subject: [PATCH 13/20] copilot feedback & test fixes --- .../org/frankframework/flow/utility/ResponseUtils.java | 6 +++++- .../filesystem/CloudFileSystemStorageServiceTest.java | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/frankframework/flow/utility/ResponseUtils.java b/src/main/java/org/frankframework/flow/utility/ResponseUtils.java index 9dbe9540..f558092a 100644 --- a/src/main/java/org/frankframework/flow/utility/ResponseUtils.java +++ b/src/main/java/org/frankframework/flow/utility/ResponseUtils.java @@ -2,12 +2,16 @@ import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; + import lombok.NoArgsConstructor; + import org.frankframework.flow.exception.ApiException; import org.frankframework.management.bus.BusMessageUtils; import org.frankframework.management.bus.message.EmptyMessage; import org.frankframework.management.bus.message.MessageBase; import org.frankframework.util.StreamUtil; + import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.springframework.http.ContentDisposition; @@ -97,7 +101,7 @@ public static String convertPayload(Object payload) throws ApiException { if (payload instanceof String string) { return string; } else if (payload instanceof byte[] bytes) { - return new String(bytes); + return new String(bytes, StandardCharsets.UTF_8); } else if (payload instanceof InputStream stream) { try { // Convert line endings to \n to show them in the browser as real line feeds diff --git a/src/test/java/org/frankframework/flow/filesystem/CloudFileSystemStorageServiceTest.java b/src/test/java/org/frankframework/flow/filesystem/CloudFileSystemStorageServiceTest.java index 9efe202b..c46d6390 100644 --- a/src/test/java/org/frankframework/flow/filesystem/CloudFileSystemStorageServiceTest.java +++ b/src/test/java/org/frankframework/flow/filesystem/CloudFileSystemStorageServiceTest.java @@ -9,14 +9,19 @@ import java.nio.file.Path; import java.util.Comparator; import java.util.List; + import org.frankframework.flow.common.config.ClientSession; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; +@ExtendWith(MockitoExtension.class) class CloudFileSystemStorageServiceTest { private CloudFileSystemStorageService service; @@ -49,6 +54,7 @@ void tearDown() throws IOException { @Test void isLocalEnvironmentReturnsFalse() { + assertEquals("test-user", session.getWorkspaceId()); // fix unnecessary mock warning assertFalse(service.isLocalEnvironment()); } @@ -169,8 +175,10 @@ void toRelativePathReturnsInputIfNotUnderUserRoot() throws IOException { @Test void anonymousUserWhenWorkspaceIdIsNull() throws IOException { + assertEquals("test-user", session.getWorkspaceId()); + ClientSession anonymousSession = Mockito.mock(ClientSession.class); - when(session.getWorkspaceId()).thenReturn(null); + when(anonymousSession.getWorkspaceId()).thenReturn(null); CloudFileSystemStorageService anonService = new CloudFileSystemStorageService(anonymousSession); setServiceBasePathToTemp(anonService, tempWorkspaceRoot.toString()); From 9150f6f972a47c9f572eb60e636ec85298c88a6f Mon Sep 17 00:00:00 2001 From: Vivy <4380412+Matthbo@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:45:32 +0200 Subject: [PATCH 14/20] Spotless --- .../frankframework/flow/common/config/WebConfiguration.java | 2 -- .../org/frankframework/flow/project/ProjectController.java | 5 ----- .../java/org/frankframework/flow/utility/ResponseUtils.java | 3 --- .../flow/filesystem/CloudFileSystemStorageServiceTest.java | 2 -- 4 files changed, 12 deletions(-) diff --git a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java index 04fb91f7..49535f73 100644 --- a/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java +++ b/src/main/java/org/frankframework/flow/common/config/WebConfiguration.java @@ -5,9 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; - import org.frankframework.management.gateway.InputStreamHttpMessageConverter; - import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/org/frankframework/flow/project/ProjectController.java b/src/main/java/org/frankframework/flow/project/ProjectController.java index 320120aa..b2c73c85 100644 --- a/src/main/java/org/frankframework/flow/project/ProjectController.java +++ b/src/main/java/org/frankframework/flow/project/ProjectController.java @@ -2,14 +2,11 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletResponse; - import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; - import lombok.extern.slf4j.Slf4j; - import org.frankframework.flow.common.AllowAllFrankUserRoles; import org.frankframework.flow.common.FrankFrameworkService; import org.frankframework.flow.common.config.ClientSession; @@ -19,9 +16,7 @@ import org.frankframework.management.bus.BusAction; import org.frankframework.management.bus.BusTopic; import org.frankframework.management.bus.message.RequestMessageBuilder; - import org.frankframework.util.JacksonUtils; - import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; diff --git a/src/main/java/org/frankframework/flow/utility/ResponseUtils.java b/src/main/java/org/frankframework/flow/utility/ResponseUtils.java index f558092a..4ada4b96 100644 --- a/src/main/java/org/frankframework/flow/utility/ResponseUtils.java +++ b/src/main/java/org/frankframework/flow/utility/ResponseUtils.java @@ -3,15 +3,12 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; - import lombok.NoArgsConstructor; - import org.frankframework.flow.exception.ApiException; import org.frankframework.management.bus.BusMessageUtils; import org.frankframework.management.bus.message.EmptyMessage; import org.frankframework.management.bus.message.MessageBase; import org.frankframework.util.StreamUtil; - import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.springframework.http.ContentDisposition; diff --git a/src/test/java/org/frankframework/flow/filesystem/CloudFileSystemStorageServiceTest.java b/src/test/java/org/frankframework/flow/filesystem/CloudFileSystemStorageServiceTest.java index c46d6390..6206669a 100644 --- a/src/test/java/org/frankframework/flow/filesystem/CloudFileSystemStorageServiceTest.java +++ b/src/test/java/org/frankframework/flow/filesystem/CloudFileSystemStorageServiceTest.java @@ -9,9 +9,7 @@ import java.nio.file.Path; import java.util.Comparator; import java.util.List; - import org.frankframework.flow.common.config.ClientSession; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; From dbb6b2041d12a5c80658db374574858de49c0ca6 Mon Sep 17 00:00:00 2001 From: Vivy <4380412+Matthbo@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:02:05 +0200 Subject: [PATCH 15/20] More test fixes --- .../flow/project/ProjectControllerTest.java | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/frankframework/flow/project/ProjectControllerTest.java b/src/test/java/org/frankframework/flow/project/ProjectControllerTest.java index f9dd63c5..97bbc438 100644 --- a/src/test/java/org/frankframework/flow/project/ProjectControllerTest.java +++ b/src/test/java/org/frankframework/flow/project/ProjectControllerTest.java @@ -13,6 +13,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import org.frankframework.flow.common.FrankFrameworkService; +import org.frankframework.flow.common.config.ClientSession; import org.frankframework.flow.configuration.Configuration; import org.frankframework.flow.filesystem.FileSystemStorage; import org.frankframework.flow.projectsettings.FilterType; @@ -46,6 +48,12 @@ class ProjectControllerTest { @MockitoBean private FileSystemStorage fileSystemStorage; + @MockitoBean + private FrankFrameworkService frankFrameworkService; + + @MockitoBean + private ClientSession session; + @BeforeEach void setUp() throws IOException { Mockito.reset(projectService); @@ -75,7 +83,8 @@ private Project mockProject() { when(settings.getFilters()) .thenReturn(Map.of( FilterType.ADAPTER, true, - FilterType.AMQP, false)); + FilterType.AMQP, false + )); when(project.getProjectSettings()).thenReturn(settings); @@ -152,7 +161,8 @@ void enableFilterTogglesFilterToTrue() throws Exception { Map updatedFilters = Map.of( FilterType.ADAPTER, true, - FilterType.AMQP, true); + FilterType.AMQP, true + ); Project updatedProject = mock(Project.class); when(updatedProject.getName()).thenReturn("MyProject"); @@ -211,7 +221,8 @@ void disableFilterTogglesFilterToFalse() throws Exception { Map updatedFilters = Map.of( FilterType.ADAPTER, false, - FilterType.AMQP, false); + FilterType.AMQP, false + ); Project updatedProject = mock(Project.class); when(updatedProject.getName()).thenReturn("MyProject"); @@ -267,10 +278,10 @@ void disableFilterInvalidFilterTypeReturns400() throws Exception { @Test void exportProjectReturnsZipFile() throws Exception { doAnswer(invocation -> { - OutputStream os = invocation.getArgument(1); - os.write("fake-zip-content".getBytes()); - return null; - }) + OutputStream os = invocation.getArgument(1); + os.write("fake-zip-content".getBytes()); + return null; + }) .when(projectService) .exportProjectAsZip(eq("MyProject"), any(OutputStream.class)); From 389934e9409f550574f06d55eb29a43a7d58693a Mon Sep 17 00:00:00 2001 From: Vivy <4380412+Matthbo@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:36:33 +0200 Subject: [PATCH 16/20] Use both central & ff nexus repositories --- pom.xml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 46d76e04..12a718ec 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ 3.0.0-SNAPSHOT 25 7.0.6 - 10.1.0-SNAPSHOT + 10.1.0-20260402.042330 2.20.1 1.18.42 3.0.0 @@ -466,4 +466,18 @@
+ + + + false + + central + Central Repository + https://repo.maven.apache.org/maven2/ + + + frankframework + https://nexus.frankframework.org/content/groups/public + + From 834709b79aa19c65d69a94c08fe2035591f69484 Mon Sep 17 00:00:00 2001 From: Sergi Philipsen Date: Wed, 22 Apr 2026 21:52:56 +0200 Subject: [PATCH 17/20] chore: update Java version to 25 in CI configurations --- .github/workflows/build-release.yml | 2 +- .github/workflows/ci.yaml | 2 +- .github/workflows/sonarqube.yaml | 13 +++++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index f400e864..5d556a0d 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -15,7 +15,7 @@ env: REGISTRY: ghcr.io # github.repository as / IMAGE_NAME: ${{ github.repository }} - JAVA_VERSION: 21 + JAVA_VERSION: 25 NODE_VERSION: 23 PNPM_VERSION: 10.4.0 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index dcbfc0b1..81f81ba7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,7 +8,7 @@ on: branches: [ master ] env: - JAVA_VERSION: 21 + JAVA_VERSION: 25 NODE_VERSION: 23 PNPM_VERSION: 10.4.0 diff --git a/.github/workflows/sonarqube.yaml b/.github/workflows/sonarqube.yaml index 2dfc287f..554e6303 100644 --- a/.github/workflows/sonarqube.yaml +++ b/.github/workflows/sonarqube.yaml @@ -7,6 +7,11 @@ on: pull_request: types: [opened, synchronize, reopened] +env: + JAVA_VERSION: 25 + NODE_VERSION: 23 + PNPM_VERSION: 10.4.0 + jobs: build: if: github.actor != 'renovate[bot]' && github.actor != 'dependabot[bot]' @@ -18,22 +23,22 @@ jobs: with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Set up JDK 21 + - name: Set up JDK ${{ env.JAVA_VERSION }} uses: actions/setup-java@v5 with: - java-version: 21 + java-version: ${{ env.JAVA_VERSION }} distribution: 'temurin' cache: 'maven' - name: Install pnpm uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 with: - version: 10.22.0 + version: ${{ env.PNPM_VERSION }} - name: Set up Node.js uses: actions/setup-node@v6 with: - node-version: 23 + node-version: ${{ env.NODE_VERSION }} cache: 'pnpm' - name: Cache SonarQube packages From 9dd5e9fb6b9d7d4d790f6259faec96712b2485b1 Mon Sep 17 00:00:00 2001 From: Vivy <4380412+Matthbo@users.noreply.github.com> Date: Fri, 24 Apr 2026 10:44:27 +0200 Subject: [PATCH 18/20] Update dependencies --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 12a718ec..0a4c857b 100644 --- a/pom.xml +++ b/pom.xml @@ -61,8 +61,8 @@ 3.0.0-SNAPSHOT 25 - 7.0.6 - 10.1.0-20260402.042330 + 7.0.7 + 10.1.0 2.20.1 1.18.42 3.0.0 From 5e81b384efb8011146c59b264e4b1ca99bc6a720 Mon Sep 17 00:00:00 2001 From: Vivy <4380412+Matthbo@users.noreply.github.com> Date: Fri, 24 Apr 2026 11:58:18 +0200 Subject: [PATCH 19/20] Also update frontend packages --- pnpm-lock.yaml | 3364 ++++++++++-------------- src/main/frontend/cypress/package.json | 2 +- src/main/frontend/package.json | 60 +- 3 files changed, 1468 insertions(+), 1958 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e9f8b359..a87c0117 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,25 +16,25 @@ importers: version: 1.0.3 '@frankframework/doc-library-react': specifier: ^1.0.3 - version: 1.0.3(react@19.2.4) + version: 1.0.3(react@19.2.5) '@mdx-js/rollup': specifier: ^3.1.1 - version: 3.1.1(rollup@4.59.0) + version: 3.1.1(rollup@4.60.2) '@monaco-editor/react': - specifier: 4.7.0-rc.0 - version: 4.7.0-rc.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: 4.7.0 + version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@react-router/node': - specifier: ^7.13.1 - version: 7.13.1(react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + specifier: ^7.14.2 + version: 7.14.2(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3) '@react-router/serve': - specifier: ^7.13.1 - version: 7.13.1(react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + specifier: ^7.14.2 + version: 7.14.2(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3) '@xyflow/react': - specifier: ^12.10.1 - version: 12.10.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: ^12.10.2 + version: 12.10.2(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) allotment: specifier: ^1.20.5 - version: 1.20.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.20.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -42,26 +42,26 @@ importers: specifier: ^0.8.5 version: 0.8.5 dotenv: - specifier: ^17.3.1 - version: 17.3.1 + specifier: ^17.4.2 + version: 17.4.2 isbot: - specifier: ^5.1.36 - version: 5.1.36 + specifier: ^5.1.39 + version: 5.1.39 monaco-xsd-code-completion: specifier: ^1.0.0 version: 1.0.0 react: - specifier: ^19.2.4 - version: 19.2.4 + specifier: ^19.2.5 + version: 19.2.5 react-complex-tree: specifier: ^2.6.1 - version: 2.6.1(react@19.2.4) + version: 2.6.1(react@19.2.5) react-dom: - specifier: ^19.2.4 - version: 19.2.4(react@19.2.4) + specifier: ^19.2.5 + version: 19.2.5(react@19.2.5) react-router: - specifier: ^7.13.1 - version: 7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: ^7.14.2 + version: 7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) remark-gfm: specifier: ^4.0.1 version: 4.0.1 @@ -72,33 +72,33 @@ importers: specifier: ^3.5.0 version: 3.5.0 xmllint-wasm: - specifier: ^5.1.0 - version: 5.1.0(@types/node@20.19.37) + specifier: ^5.2.0 + version: 5.2.0(@types/node@20.19.39) zustand: - specifier: ^5.0.11 - version: 5.0.11(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) + specifier: ^5.0.12 + version: 5.0.12(@types/react@19.2.14)(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)) devDependencies: '@eslint/js': - specifier: ^9.39.4 - version: 9.39.4 + specifier: ^10.0.1 + version: 10.0.1(eslint@10.2.1(jiti@2.6.1)) '@mdx-js/react': specifier: ^3.1.1 - version: 3.1.1(@types/react@19.2.14)(react@19.2.4) + version: 3.1.1(@types/react@19.2.14)(react@19.2.5) '@prettier/plugin-xml': - specifier: ^3.4.1 - version: 3.4.2(prettier@3.8.1) + specifier: ^3.4.2 + version: 3.4.2(prettier@3.8.3) '@react-router/dev': - specifier: ^7.13.1 - version: 7.13.1(@react-router/serve@7.13.1(react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(lightningcss@1.31.1)(react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1)) + specifier: ^7.14.2 + version: 7.14.2(@react-router/serve@7.14.2(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3))(@types/node@20.19.39)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(lightningcss@1.32.0)(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)) '@tailwindcss/vite': - specifier: ^4.2.1 - version: 4.2.1(vite@6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1)) + specifier: ^4.2.4 + version: 4.2.4(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)) '@testing-library/jest-dom': specifier: ^6.9.1 version: 6.9.1 '@testing-library/react': specifier: ^16.3.2 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@testing-library/user-event': specifier: ^14.6.1 version: 14.6.1(@testing-library/dom@10.4.1) @@ -106,8 +106,8 @@ importers: specifier: ^2.0.13 version: 2.0.13 '@types/node': - specifier: ^20.19.37 - version: 20.19.37 + specifier: ^20.19.39 + version: 20.19.39 '@types/react': specifier: ^19.2.14 version: 19.2.14 @@ -115,77 +115,77 @@ importers: specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.14) '@typescript-eslint/eslint-plugin': - specifier: ^8.57.0 - version: 8.57.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.59.0 + version: 8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3))(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^8.57.0 - version: 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.59.0 + version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) '@vitejs/plugin-react': - specifier: ^5.1.4 - version: 5.1.4(vite@6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1)) + specifier: ^5.2.0 + version: 5.2.0(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)) '@vitest/ui': - specifier: ^4.0.18 - version: 4.0.18(vitest@4.0.18) + specifier: ^4.1.5 + version: 4.1.5(vitest@4.1.5) eslint: - specifier: ^9.39.4 - version: 9.39.4(jiti@2.6.1) + specifier: ^10.2.1 + version: 10.2.1(jiti@2.6.1) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@9.39.4(jiti@2.6.1)) + version: 10.1.8(eslint@10.2.1(jiti@2.6.1)) eslint-plugin-prettier: specifier: ^5.5.5 - version: 5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1))(prettier@3.8.1) + version: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.2.1(jiti@2.6.1)))(eslint@10.2.1(jiti@2.6.1))(prettier@3.8.3) eslint-plugin-react: specifier: ^7.37.5 - version: 7.37.5(eslint@9.39.4(jiti@2.6.1)) + version: 7.37.5(eslint@10.2.1(jiti@2.6.1)) eslint-plugin-react-hooks: - specifier: ^5.2.0 - version: 5.2.0(eslint@9.39.4(jiti@2.6.1)) + specifier: ^7.1.1 + version: 7.1.1(eslint@10.2.1(jiti@2.6.1)) eslint-plugin-sonarjs: - specifier: ^3.0.7 - version: 3.0.7(eslint@9.39.4(jiti@2.6.1)) + specifier: ^4.0.3 + version: 4.0.3(eslint@10.2.1(jiti@2.6.1)) eslint-plugin-unicorn: - specifier: ^62.0.0 - version: 62.0.0(eslint@9.39.4(jiti@2.6.1)) + specifier: ^64.0.0 + version: 64.0.0(eslint@10.2.1(jiti@2.6.1)) jsdom: specifier: ^27.4.0 version: 27.4.0 prettier: - specifier: ^3.8.1 - version: 3.8.1 + specifier: ^3.8.3 + version: 3.8.3 prettier-plugin-tailwindcss: specifier: ^0.6.14 - version: 0.6.14(prettier@3.8.1) + version: 0.6.14(prettier@3.8.3) react-router-devtools: - specifier: ^1.1.10 - version: 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(vite@6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1)) + specifier: ^6.2.0 + version: 6.2.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.5(react@19.2.5))(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(solid-js@1.9.12)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)) tailwindcss: - specifier: ^4.2.1 - version: 4.2.1 + specifier: ^4.2.4 + version: 4.2.4 typescript: specifier: ^5.9.3 version: 5.9.3 typescript-eslint: - specifier: ^8.57.0 - version: 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.59.0 + version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) vite: - specifier: ^6.4.1 - version: 6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1) + specifier: ^6.4.2 + version: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0) vite-plugin-svgr: specifier: ^4.5.0 - version: 4.5.0(rollup@4.59.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1)) + version: 4.5.0(rollup@4.60.2)(typescript@5.9.3)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1)) + version: 5.1.4(typescript@5.9.3)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)) vitest: - specifier: ^4.0.18 - version: 4.0.18(@types/node@20.19.37)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1) + specifier: ^4.1.5 + version: 4.1.5(@types/node@20.19.39)(@vitest/ui@4.1.5)(jsdom@27.4.0)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)) src/main/frontend/cypress: devDependencies: cypress: - specifier: ^15.11.0 - version: 15.11.0 + specifier: ^15.14.1 + version: 15.14.1 cypress-multi-reporters: specifier: ^2.0.5 version: 2.0.5(mocha@11.7.5) @@ -291,12 +291,12 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.6': - resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.0': - resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} engines: {node: '>=6.0.0'} hasBin: true @@ -342,8 +342,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.28.6': - resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} '@babel/template@7.28.6': @@ -358,8 +358,8 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} - '@biomejs/cli-darwin-arm64@1.9.4': - resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} + '@biomejs/cli-darwin-arm64@2.4.13': + resolution: {integrity: sha512-2KImO1jhNFBa2oWConyr0x6flxbQpGKv6902uGXpYM62Xyem8U80j441SyUJ8KyngsmKbQjeIv1q2CQfDkNnYg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] @@ -374,15 +374,15 @@ packages: resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} engines: {node: '>=20.19.0'} - '@csstools/css-calc@3.1.1': - resolution: {integrity: sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==} + '@csstools/css-calc@3.2.0': + resolution: {integrity: sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-color-parser@4.0.2': - resolution: {integrity: sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==} + '@csstools/css-color-parser@4.1.0': + resolution: {integrity: sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 @@ -394,8 +394,13 @@ packages: peerDependencies: '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.1.0': - resolution: {integrity: sha512-H4tuz2nhWgNKLt1inYpoVCfbJbMwX/lQKp3g69rrrIMIYlFD9+zTykOKhNR8uGrAmbS/kT9n6hTFkmDkxLgeTA==} + '@csstools/css-syntax-patches-for-csstree@1.1.3': + resolution: {integrity: sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true '@csstools/css-tokenizer@4.0.0': resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} @@ -415,50 +420,6 @@ packages: resolution: {integrity: sha512-mepCf/e9+SKYy1d02/UkvSy6+6MoyXhVxP8lLDfA7BPE1X1d4dR0sZznmbM8/XVJ1GPM+Svnx7Xj6ZweByWUkw==} engines: {node: '>17.0.0'} - '@emotion/babel-plugin@11.13.5': - resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} - - '@emotion/cache@11.14.0': - resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} - - '@emotion/css@11.13.5': - resolution: {integrity: sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==} - - '@emotion/hash@0.9.2': - resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} - - '@emotion/memoize@0.9.0': - resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} - - '@emotion/react@11.14.0': - resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} - peerDependencies: - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true - - '@emotion/serialize@1.3.3': - resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} - - '@emotion/sheet@1.4.0': - resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} - - '@emotion/unitless@0.10.0': - resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} - - '@emotion/use-insertion-effect-with-fallbacks@1.2.0': - resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} - peerDependencies: - react: '>=16.8.0' - - '@emotion/utils@1.4.2': - resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} - - '@emotion/weak-memoize@0.4.0': - resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} - '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} @@ -625,33 +586,34 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.21.2': - resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/config-helpers@0.4.2': - resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/core@0.17.0': - resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-helpers@0.5.5': + resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/eslintrc@3.3.5': - resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/js@9.39.4': - resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true - '@eslint/object-schema@2.1.7': - resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/plugin-kit@0.4.1': - resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.7.1': + resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@exodus/bytes@1.15.0': resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} @@ -668,12 +630,6 @@ packages: '@floating-ui/dom@1.7.6': resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} - '@floating-ui/react-dom@2.1.8': - resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - '@floating-ui/utils@0.2.11': resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} @@ -685,12 +641,16 @@ packages: peerDependencies: react: ^19.1.0 || ^20.0.0 - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': @@ -701,14 +661,6 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@isaacs/balanced-match@4.0.1': - resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} - engines: {node: 20 || >=22} - - '@isaacs/brace-expansion@5.0.1': - resolution: {integrity: sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==} - engines: {node: 20 || >=22} - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -752,16 +704,13 @@ packages: '@monaco-editor/loader@1.7.0': resolution: {integrity: sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==} - '@monaco-editor/react@4.7.0-rc.0': - resolution: {integrity: sha512-YfjXkDK0bcwS0zo8PXptvQdCQfOPPtzGsAzmIv7PnoUGFdIohsR+NVDyjbajMddF+3cWUm/3q9NzP/DUke9a+w==} + '@monaco-editor/react@4.7.0': + resolution: {integrity: sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==} peerDependencies: monaco-editor: '>= 0.25.0 < 1' react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@one-ini/wasm@0.1.1': - resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} - '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -778,9 +727,6 @@ packages: peerDependencies: prettier: ^3.0.0 - '@radix-ui/number@1.1.1': - resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} - '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} @@ -797,19 +743,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-arrow@1.1.7': - resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-collapsible@1.1.12': resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} peerDependencies: @@ -863,41 +796,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-dismissable-layer@1.1.11': - resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-focus-guards@1.1.3': - resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-focus-scope@1.1.7': - resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-id@1.1.1': resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} peerDependencies: @@ -907,32 +805,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-popper@1.2.8': - resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-portal@1.1.9': - resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-presence@1.1.5': resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} peerDependencies: @@ -959,19 +831,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-select@2.2.6': - resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-slot@1.2.3': resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} peerDependencies: @@ -981,15 +840,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-callback-ref@1.1.1': - resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-use-controllable-state@1.2.2': resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} peerDependencies: @@ -1008,15 +858,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-escape-keydown@1.1.1': - resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-use-layout-effect@1.1.1': resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} peerDependencies: @@ -1026,60 +867,17 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-previous@1.1.1': - resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-rect@1.1.1': - resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-size@1.1.1': - resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-visually-hidden@1.2.3': - resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/rect@1.1.1': - resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - - '@react-router/dev@7.13.1': - resolution: {integrity: sha512-H+kEvbbOaWGaitOyL6CgqPsHqRUh66HuVRvIEaZEqdoAY/1xChdhmmq6ZumMHzcFHgHlfOcoXgNHlz6ZO4NWcg==} + '@react-router/dev@7.14.2': + resolution: {integrity: sha512-lU88Ls4iC78RdPOKkER54+hlsHzzS8WSZrf2/cGQumbIN2A5WvO0LDyv72cdJmLWujgZ9rpNoGzmqWINssShGQ==} engines: {node: '>=20.0.0'} hasBin: true peerDependencies: - '@react-router/serve': ^7.13.1 - '@vitejs/plugin-rsc': ~0.5.7 - react-router: ^7.13.1 + '@react-router/serve': ^7.14.2 + '@vitejs/plugin-rsc': ~0.5.21 + react-router: ^7.14.2 react-server-dom-webpack: ^19.2.3 - typescript: ^5.1.0 - vite: ^5.1.0 || ^6.0.0 || ^7.0.0 + typescript: ^5.1.0 || ^6.0.0 + vite: ^5.1.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 wrangler: ^3.28.2 || ^4.0.0 peerDependenciesMeta: '@react-router/serve': @@ -1093,33 +891,33 @@ packages: wrangler: optional: true - '@react-router/express@7.13.1': - resolution: {integrity: sha512-ujHom4LiEWsbnohNArwNT86QP3WRB5p+rY8AAll6s4gdrzgOXIy3FHDc3up5Lz8juUrZKh0d+B+PZa/IdDSK3A==} + '@react-router/express@7.14.2': + resolution: {integrity: sha512-IYs61kHfMWsJk/ju4Ts4hw7wblZecfXuIvqQPKEaz+gwpkJMSWDzhPpgmC16EnmBQkXPqMVpsjvNxA/d9p9ehg==} engines: {node: '>=20.0.0'} peerDependencies: express: ^4.17.1 || ^5 - react-router: 7.13.1 - typescript: ^5.1.0 + react-router: 7.14.2 + typescript: ^5.1.0 || ^6.0.0 peerDependenciesMeta: typescript: optional: true - '@react-router/node@7.13.1': - resolution: {integrity: sha512-IWPPf+Q3nJ6q4bwyTf5leeGUfg8GAxSN1RKj5wp9SK915zKK+1u4TCOfOmr8hmC6IW1fcjKV0WChkM0HkReIiw==} + '@react-router/node@7.14.2': + resolution: {integrity: sha512-8zxVfgKOXjk0k8YxSBDTFyNAuVdr+og1wFbQpmJJOxo7ObxfI81EbHenyyxGvFiw77rNFLS9Dqgnv5xZgHZfCw==} engines: {node: '>=20.0.0'} peerDependencies: - react-router: 7.13.1 - typescript: ^5.1.0 + react-router: 7.14.2 + typescript: ^5.1.0 || ^6.0.0 peerDependenciesMeta: typescript: optional: true - '@react-router/serve@7.13.1': - resolution: {integrity: sha512-vh5lr41rioXLz/zNLTYo0zq4yh97AkgEkJK7bhPeXnNbLNtI36WCZ2AeBtSJ4sdx4gx5LZvcjP8zoWFfSbNupA==} + '@react-router/serve@7.14.2': + resolution: {integrity: sha512-Rh/Mrd9+Jkf+IOd7beEccCfTDavOQRpkk0TLwLFK60dv0yUIyOTIaKxC7W6I0WMrgAjhUL09JxfMsoz2vtYhTg==} engines: {node: '>=20.0.0'} hasBin: true peerDependencies: - react-router: 7.13.1 + react-router: 7.14.2 '@remix-run/node-fetch-server@0.13.0': resolution: {integrity: sha512-1EsNo0ZpgXu/90AWoRZf/oE3RVTUS80tiTUpt+hv5pjtAkw7icN4WskDwz/KdAw5ARbJLMhZBrO1NqThmy/McA==} @@ -1136,131 +934,174 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.59.0': - resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} + '@rollup/rollup-android-arm-eabi@4.60.2': + resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.59.0': - resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} + '@rollup/rollup-android-arm64@4.60.2': + resolution: {integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.59.0': - resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} + '@rollup/rollup-darwin-arm64@4.60.2': + resolution: {integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.59.0': - resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} + '@rollup/rollup-darwin-x64@4.60.2': + resolution: {integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.59.0': - resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} + '@rollup/rollup-freebsd-arm64@4.60.2': + resolution: {integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.59.0': - resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} + '@rollup/rollup-freebsd-x64@4.60.2': + resolution: {integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.59.0': - resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==} cpu: [arm] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.59.0': - resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} + '@rollup/rollup-linux-arm-musleabihf@4.60.2': + resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==} cpu: [arm] os: [linux] + libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.59.0': - resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} + '@rollup/rollup-linux-arm64-gnu@4.60.2': + resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==} cpu: [arm64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.59.0': - resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} + '@rollup/rollup-linux-arm64-musl@4.60.2': + resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==} cpu: [arm64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.59.0': - resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} + '@rollup/rollup-linux-loong64-gnu@4.60.2': + resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==} cpu: [loong64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.59.0': - resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} + '@rollup/rollup-linux-loong64-musl@4.60.2': + resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==} cpu: [loong64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-ppc64-gnu@4.59.0': - resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==} cpu: [ppc64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.59.0': - resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} + '@rollup/rollup-linux-ppc64-musl@4.60.2': + resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==} cpu: [ppc64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-riscv64-gnu@4.59.0': - resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} + '@rollup/rollup-linux-riscv64-gnu@4.60.2': + resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==} cpu: [riscv64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.59.0': - resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} + '@rollup/rollup-linux-riscv64-musl@4.60.2': + resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==} cpu: [riscv64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.59.0': - resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} + '@rollup/rollup-linux-s390x-gnu@4.60.2': + resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==} cpu: [s390x] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.59.0': - resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} + '@rollup/rollup-linux-x64-gnu@4.60.2': + resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==} cpu: [x64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.59.0': - resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} + '@rollup/rollup-linux-x64-musl@4.60.2': + resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==} cpu: [x64] os: [linux] + libc: [musl] - '@rollup/rollup-openbsd-x64@4.59.0': - resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} + '@rollup/rollup-openbsd-x64@4.60.2': + resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.59.0': - resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} + '@rollup/rollup-openharmony-arm64@4.60.2': + resolution: {integrity: sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.59.0': - resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} + '@rollup/rollup-win32-arm64-msvc@4.60.2': + resolution: {integrity: sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.59.0': - resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} + '@rollup/rollup-win32-ia32-msvc@4.60.2': + resolution: {integrity: sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.59.0': - resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} + '@rollup/rollup-win32-x64-gnu@4.60.2': + resolution: {integrity: sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.59.0': - resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} + '@rollup/rollup-win32-x64-msvc@4.60.2': + resolution: {integrity: sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==} cpu: [x64] os: [win32] + '@solid-primitives/event-listener@2.4.5': + resolution: {integrity: sha512-nwRV558mIabl4yVAhZKY8cb6G+O1F0M6Z75ttTu5hk+SxdOnKSGj+eetDIu7Oax1P138ZdUU01qnBPR8rnxaEA==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/keyboard@1.3.5': + resolution: {integrity: sha512-sav+l+PL+74z3yaftVs7qd8c2SXkqzuxPOVibUe5wYMt+U5Hxp3V3XCPgBPN2I6cANjvoFtz0NiU8uHVLdi9FQ==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/resize-observer@2.1.5': + resolution: {integrity: sha512-AiyTknKcNBaKHbcSMuxtSNM8FjIuiSuFyFghdD0TcCMU9hKi9EmsC5pjfjDwxE+5EueB1a+T/34PLRI5vbBbKw==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/rootless@1.5.3': + resolution: {integrity: sha512-N8cIDAHbWcLahNRLr0knAAQvXyEdEMoAZvIMZKmhNb1mlx9e2UOv9BRD5YNwQUJwbNoYVhhLwFOEOcVXFx0HqA==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/static-store@0.1.3': + resolution: {integrity: sha512-uxez7SXnr5GiRnzqO2IEDjOJRIXaG+0LZLBizmUA1FwSi+hrpuMzVBwyk70m4prcl8X6FDDXUl9O8hSq8wHbBQ==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/utils@6.4.0': + resolution: {integrity: sha512-AeGTBg8Wtkh/0s+evyLtP8piQoS4wyqqQaAFs2HJcFMMjYAtUgo+ZPduRXLjPlqKVc2ejeR544oeqpbn8Egn8A==} + peerDependencies: + solid-js: ^1.6.12 + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -1332,65 +1173,69 @@ packages: peerDependencies: '@svgr/core': '*' - '@tailwindcss/node@4.2.1': - resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==} + '@tailwindcss/node@4.2.4': + resolution: {integrity: sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==} - '@tailwindcss/oxide-android-arm64@4.2.1': - resolution: {integrity: sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==} + '@tailwindcss/oxide-android-arm64@4.2.4': + resolution: {integrity: sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==} engines: {node: '>= 20'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.2.1': - resolution: {integrity: sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==} + '@tailwindcss/oxide-darwin-arm64@4.2.4': + resolution: {integrity: sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==} engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.2.1': - resolution: {integrity: sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==} + '@tailwindcss/oxide-darwin-x64@4.2.4': + resolution: {integrity: sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==} engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.2.1': - resolution: {integrity: sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==} + '@tailwindcss/oxide-freebsd-x64@4.2.4': + resolution: {integrity: sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==} engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': - resolution: {integrity: sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + resolution: {integrity: sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==} engines: {node: '>= 20'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': - resolution: {integrity: sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==} + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + resolution: {integrity: sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] - '@tailwindcss/oxide-linux-arm64-musl@4.2.1': - resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] - '@tailwindcss/oxide-linux-x64-gnu@4.2.1': - resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] - '@tailwindcss/oxide-linux-x64-musl@4.2.1': - resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} + '@tailwindcss/oxide-linux-x64-musl@4.2.4': + resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] - '@tailwindcss/oxide-wasm32-wasi@4.2.1': - resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} + '@tailwindcss/oxide-wasm32-wasi@4.2.4': + resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -1401,26 +1246,75 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': - resolution: {integrity: sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==} + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + resolution: {integrity: sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==} engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.2.1': - resolution: {integrity: sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==} + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + resolution: {integrity: sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==} engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.2.1': - resolution: {integrity: sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==} + '@tailwindcss/oxide@4.2.4': + resolution: {integrity: sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==} engines: {node: '>= 20'} - '@tailwindcss/vite@4.2.1': - resolution: {integrity: sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==} + '@tailwindcss/vite@4.2.4': + resolution: {integrity: sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==} peerDependencies: - vite: ^5.2.0 || ^6 || ^7 + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@tanstack/devtools-client@0.0.5': + resolution: {integrity: sha512-hsNDE3iu4frt9cC2ppn1mNRnLKo2uc1/1hXAyY9z4UYb+o40M2clFAhiFoo4HngjfGJDV3x18KVVIq7W4Un+zA==} + engines: {node: '>=18'} + + '@tanstack/devtools-client@0.0.6': + resolution: {integrity: sha512-f85ZJXJnDIFOoykG/BFIixuAevJovCvJF391LPs6YjBAPhGYC50NWlx1y4iF/UmK5/cCMx+/JqI5SBOz7FanQQ==} + engines: {node: '>=18'} + + '@tanstack/devtools-event-bus@0.4.0': + resolution: {integrity: sha512-1t+/csFuDzi+miDxAOh6Xv7VDE80gJEItkTcAZLjV5MRulbO/W8ocjHLI2Do/p2r2/FBU0eKCRTpdqvXaYoHpQ==} + engines: {node: '>=18'} + + '@tanstack/devtools-event-bus@0.4.1': + resolution: {integrity: sha512-cNnJ89Q021Zf883rlbBTfsaxTfi2r73/qejGtyTa7ksErF3hyDyAq1aTbo5crK9dAL7zSHh9viKY1BtMls1QOA==} + engines: {node: '>=18'} + + '@tanstack/devtools-event-client@0.4.3': + resolution: {integrity: sha512-OZI6QyULw0FI0wjgmeYzCIfbgPsOEzwJtCpa69XrfLMtNXLGnz3d/dIabk7frg0TmHo+Ah49w5I4KC7Tufwsvw==} + engines: {node: '>=18'} + hasBin: true + + '@tanstack/devtools-ui@0.5.0': + resolution: {integrity: sha512-nNZ14054n31fWB61jtWhZYLRdQ3yceCE3G/RINoINUB0RqIGZAIm9DnEDwOTAOfqt4/a/D8vNk8pJu6RQUp74g==} + engines: {node: '>=18'} + peerDependencies: + solid-js: '>=1.9.7' + + '@tanstack/devtools-vite@0.4.1': + resolution: {integrity: sha512-PkMOomcWnl/pUkCqIjqL/csjPHtkMVBirDpJVOZR7XJZDxo5CuD7B+3KsujFCF4Dsn6QYlae97gCZvxi/CB76Q==} + engines: {node: '>=18'} + peerDependencies: + vite: ^6.0.0 || ^7.0.0 + + '@tanstack/devtools@0.10.14': + resolution: {integrity: sha512-bg1e0PyjmMMsc9VSOGb9etu15CpFdAwlQ5DD2xS6N93iTPgCPWXiZQFZygrEDoKnnx1x7BM6QTaiukizaejgSA==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + solid-js: '>=1.9.7' + + '@tanstack/react-devtools@0.9.13': + resolution: {integrity: sha512-O9YXTEe2dlnw2pPNKFZ4Wk7zC4qrDvc0SAALKfMVedeZ2Dyd0LEJUabYS6GPm+DmnrBhc7nJx6Zqc9aDjFrj4g==} + engines: {node: '>=18'} + peerDependencies: + '@types/react': '>=16.8' + '@types/react-dom': '>=16.8' + react: '>=16.8' + react-dom: '>=16.8' '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} @@ -1490,12 +1384,15 @@ packages: '@types/d3-zoom@3.0.8': resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} - '@types/debug@4.1.12': - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} @@ -1517,11 +1414,11 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@20.19.37': - resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==} + '@types/node@20.19.39': + resolution: {integrity: sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==} - '@types/node@25.4.0': - resolution: {integrity: sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw==} + '@types/node@25.6.0': + resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -1531,11 +1428,6 @@ packages: peerDependencies: '@types/react': ^19.2.0 - '@types/react-reconciler@0.28.9': - resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==} - peerDependencies: - '@types/react': '*' - '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} @@ -1560,128 +1452,123 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@8.57.0': - resolution: {integrity: sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==} + '@typescript-eslint/eslint-plugin@8.59.0': + resolution: {integrity: sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.57.0 + '@typescript-eslint/parser': ^8.59.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.57.0': - resolution: {integrity: sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==} + '@typescript-eslint/parser@8.59.0': + resolution: {integrity: sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.57.0': - resolution: {integrity: sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==} + '@typescript-eslint/project-service@8.59.0': + resolution: {integrity: sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.57.0': - resolution: {integrity: sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==} + '@typescript-eslint/scope-manager@8.59.0': + resolution: {integrity: sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.57.0': - resolution: {integrity: sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==} + '@typescript-eslint/tsconfig-utils@8.59.0': + resolution: {integrity: sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.57.0': - resolution: {integrity: sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==} + '@typescript-eslint/type-utils@8.59.0': + resolution: {integrity: sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.57.0': - resolution: {integrity: sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==} + '@typescript-eslint/types@8.59.0': + resolution: {integrity: sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.57.0': - resolution: {integrity: sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==} + '@typescript-eslint/typescript-estree@8.59.0': + resolution: {integrity: sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.57.0': - resolution: {integrity: sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==} + '@typescript-eslint/utils@8.59.0': + resolution: {integrity: sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.57.0': - resolution: {integrity: sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==} + '@typescript-eslint/visitor-keys@8.59.0': + resolution: {integrity: sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@vitejs/plugin-react@5.1.4': - resolution: {integrity: sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==} + '@vitejs/plugin-react@5.2.0': + resolution: {integrity: sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - '@vitest/expect@4.0.18': - resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} + '@vitest/expect@4.1.5': + resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==} - '@vitest/mocker@4.0.18': - resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} + '@vitest/mocker@4.1.5': + resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==} peerDependencies: msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0-0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@4.0.18': - resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} + '@vitest/pretty-format@4.1.5': + resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} - '@vitest/runner@4.0.18': - resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} + '@vitest/runner@4.1.5': + resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==} - '@vitest/snapshot@4.0.18': - resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} + '@vitest/snapshot@4.1.5': + resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} - '@vitest/spy@4.0.18': - resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} + '@vitest/spy@4.1.5': + resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} - '@vitest/ui@4.0.18': - resolution: {integrity: sha512-CGJ25bc8fRi8Lod/3GHSvXRKi7nBo3kxh0ApW4yCjmrWmRmlT53B5E08XRSZRliygG0aVNxLrBEqPYdz/KcCtQ==} + '@vitest/ui@4.1.5': + resolution: {integrity: sha512-3Z9HNFiV0IF1fk0JPiK+7kE1GcaIPefQQIBYur6PM5yFIq6agys3uqP/0t966e1wXfmjbRCHDe7qW236Xjwnag==} peerDependencies: - vitest: 4.0.18 + vitest: 4.1.5 - '@vitest/utils@4.0.18': - resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + '@vitest/utils@4.1.5': + resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} '@xml-tools/parser@1.0.11': resolution: {integrity: sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==} - '@xmldom/xmldom@0.8.11': - resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} + '@xmldom/xmldom@0.8.13': + resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} engines: {node: '>=10.0.0'} - deprecated: this version has critical issues, please update to the latest version - '@xyflow/react@12.10.1': - resolution: {integrity: sha512-5eSWtIK/+rkldOuFbOOz44CRgQRjtS9v5nufk77DV+XBnfCGL9HAQ8PG00o2ZYKqkEU/Ak6wrKC95Tu+2zuK3Q==} + '@xyflow/react@12.10.2': + resolution: {integrity: sha512-CgIi6HwlcHXwlkTpr0fxLv/0sRVNZ8IdwKLzzeCscaYBwpvfcH1QFOCeaTCuEn1FQEs/B8CjnTSjhs8udgmBgQ==} peerDependencies: react: '>=17' react-dom: '>=17' - '@xyflow/system@0.0.75': - resolution: {integrity: sha512-iXs+AGFLi8w/VlAoc/iSxk+CxfT6o64Uw/k0CKASOPqjqz6E0rb5jFZgJtXGZCpfQI6OQpu5EnumP5fGxQheaQ==} - - abbrev@2.0.0: - resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + '@xyflow/system@0.0.76': + resolution: {integrity: sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA==} accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} @@ -1705,8 +1592,8 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} allotment@1.20.5: resolution: {integrity: sha512-7i4NT7ieXEyAd5lBrXmE7WHz/e7hRuo97+j+TwrPE85ha6kyFURoc76nom0dWSZ1pTKVEAMJy/+f3/Isfu/41A==} @@ -1751,10 +1638,6 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - aria-hidden@1.2.6: - resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} - engines: {node: '>=10'} - aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} @@ -1853,8 +1736,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.10.0: - resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} + baseline-browser-mapping@2.10.21: + resolution: {integrity: sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==} engines: {node: '>=6.0.0'} hasBin: true @@ -1865,18 +1748,9 @@ packages: bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} - beautify@0.0.8: - resolution: {integrity: sha512-1iF6Ey2qxDkm6bPgKcoXUmwFDpoRi5IgwefQDDQBRLxlZAAYwcULoQ2IdBArXZuSsuL7AT+KvZI9xZVLeUZPRg==} - hasBin: true - bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} - bippy@0.3.34: - resolution: {integrity: sha512-vmptmU/20UdIWHHhq7qCSHhHzK7Ro3YJ1utU0fBG7ujUc58LEfTtilKxcF0IOgSjT5XLcm7CBzDjbv4lcKApGQ==} - peerDependencies: - react: '>=17.0.1' - blob-util@2.0.2: resolution: {integrity: sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==} @@ -1887,21 +1761,21 @@ packages: resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@2.1.0: + resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} - brace-expansion@5.0.4: - resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} engines: {node: 18 || 20 || >=22} browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} - browserslist@4.28.1: - resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1918,8 +1792,8 @@ packages: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} - builtin-modules@5.0.0: - resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} + builtin-modules@5.1.0: + resolution: {integrity: sha512-c5JxaDrzwRjq3WyJkI1AGR5xy6Gr6udlt7sQPbl09+3ckB+Zo2qqQ2KhCTBr7Q8dHB43bENGYEk4xddrFH/b7A==} engines: {node: '>=18.20'} bytes@3.1.2: @@ -1938,8 +1812,8 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} engines: {node: '>= 0.4'} call-bound@1.0.4: @@ -1954,8 +1828,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001778: - resolution: {integrity: sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==} + caniuse-lite@1.0.30001790: + resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==} caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} @@ -2066,10 +1940,6 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - commander@10.0.1: - resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} - engines: {node: '>=14'} - commander@6.2.1: resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} engines: {node: '>= 6'} @@ -2089,16 +1959,9 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - concat-stream@1.6.2: - resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} - engines: {'0': node >= 0.8} - confbox@0.2.4: resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} - config-chain@1.1.13: - resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} - content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -2107,9 +1970,6 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} - convert-source-map@1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -2124,15 +1984,12 @@ packages: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} - core-js-compat@3.48.0: - resolution: {integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==} + core-js-compat@3.49.0: + resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} core-util-is@1.0.2: resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - cosmiconfig@7.1.0: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} @@ -2157,10 +2014,6 @@ packages: css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} - cssbeautify@0.3.1: - resolution: {integrity: sha512-ljnSOCOiMbklF+dwPbpooyB78foId02vUrTDogWzu6ca2DCNB7Kc/BHEGBnYOlUYtwXvSW0mWTwaiO2pwFIoRg==} - hasBin: true - cssstyle@5.3.7: resolution: {integrity: sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==} engines: {node: '>=20'} @@ -2174,8 +2027,8 @@ packages: peerDependencies: mocha: '>=3.1.2' - cypress@15.11.0: - resolution: {integrity: sha512-NXDE6/fqZuzh1Zr53nyhCCa4lcANNTYWQNP9fJO+tzD3qVTDaTUni5xXMuigYjMujQ7CRiT9RkJJONmPQSsDFw==} + cypress@15.14.1: + resolution: {integrity: sha512-AkuiHNSnmm0a+h/horcvbjmY6dWpCe1Ebp1R0LjMP5I6pjMaNA50Mw1YP/d07pLHJ/sV8FZoGecUWFCJ/Nifpw==} engines: {node: ^20.1.0 || ^22.0.0 || >=24.0.0} hasBin: true @@ -2249,9 +2102,6 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} - date-fns@4.1.0: - resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} - dateformat@4.6.3: resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} @@ -2332,9 +2182,6 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - detect-node-es@1.1.0: - resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -2346,10 +2193,6 @@ packages: resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==} engines: {node: '>=0.3.1'} - diff@8.0.3: - resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} - engines: {node: '>=0.3.1'} - doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -2369,8 +2212,8 @@ packages: dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} - dotenv@17.3.1: - resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} engines: {node: '>=12'} dunder-proto@1.0.1: @@ -2383,16 +2226,11 @@ packages: ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} - editorconfig@1.0.7: - resolution: {integrity: sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==} - engines: {node: '>=14'} - hasBin: true - ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.313: - resolution: {integrity: sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==} + electron-to-chromium@1.5.344: + resolution: {integrity: sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2407,8 +2245,8 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - enhanced-resolve@5.20.0: - resolution: {integrity: sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==} + enhanced-resolve@5.21.0: + resolution: {integrity: sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==} engines: {node: '>=10.13.0'} enquirer@2.4.1: @@ -2419,15 +2257,15 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - entities@6.0.1: - resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} - engines: {node: '>=0.12'} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - es-abstract@1.24.1: - resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} engines: {node: '>= 0.4'} es-define-property@1.0.1: @@ -2438,13 +2276,16 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-iterator-helpers@1.3.0: - resolution: {integrity: sha512-04cg8iJFDOxWcYlu0GFFWgs7vtaEPCmr5w1nrj9V3z3axu/48HCMwK6VMp45Zh3ZB+xLP1ifbJfrq86+1ypKKQ==} + es-iterator-helpers@1.3.2: + resolution: {integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==} engines: {node: '>= 0.4'} es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -2511,11 +2352,11 @@ packages: eslint-config-prettier: optional: true - eslint-plugin-react-hooks@5.2.0: - resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} - engines: {node: '>=10'} + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + engines: {node: '>=18'} peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 eslint-plugin-react@7.37.5: resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} @@ -2523,36 +2364,32 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - eslint-plugin-sonarjs@3.0.7: - resolution: {integrity: sha512-62jB20krIPvcwBLAyG3VVKa2ce2j2lL1yCb8Y0ylMRR/dLvCCTiQx8gQbXb+G81k1alPZ2/I3muZinqWQdBbzw==} + eslint-plugin-sonarjs@4.0.3: + resolution: {integrity: sha512-5drkJKLC9qQddIiaATV0e8+ygbUc7b0Ti6VB7M2d3jmKNh3X0RaiIJYTs3dr9xnlhlrxo+/s1FoO3Jgv6O/c7g==} peerDependencies: - eslint: ^8.0.0 || ^9.0.0 + eslint: ^8.0.0 || ^9.0.0 || ^10.0.0 - eslint-plugin-unicorn@62.0.0: - resolution: {integrity: sha512-HIlIkGLkvf29YEiS/ImuDZQbP12gWyx5i3C6XrRxMvVdqMroCI9qoVYCoIl17ChN+U89pn9sVwLxhIWj5nEc7g==} + eslint-plugin-unicorn@64.0.0: + resolution: {integrity: sha512-rNZwalHh8i0UfPlhNwg5BTUO1CMdKNmjqe+TgzOTZnpKoi8VBgsW7u9qCHIdpxEzZ1uwrJrPF0uRb7l//K38gA==} engines: {node: ^20.10.0 || >=21.0.0} peerDependencies: eslint: '>=9.38.0' - eslint-scope@8.4.0: - resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.2.1: - resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint-visitor-keys@5.0.1: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@9.39.4: - resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint@10.2.1: + resolution: {integrity: sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: jiti: '*' @@ -2560,9 +2397,9 @@ packages: jiti: optional: true - espree@10.4.0: - resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} esquery@1.7.0: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} @@ -2688,9 +2525,6 @@ packages: resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} engines: {node: '>= 0.8'} - find-root@1.1.0: - resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} - find-up-simple@1.0.1: resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} engines: {node: '>=18'} @@ -2707,8 +2541,8 @@ packages: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true - flatted@3.4.1: - resolution: {integrity: sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==} + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} @@ -2729,8 +2563,8 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} - framer-motion@12.35.2: - resolution: {integrity: sha512-dhfuEMaNo0hc+AEqyHiIfiJRNb9U9UQutE9FoKm5pjf7CMitp9xPEF1iWZihR1q86LBmo6EJ7S8cN8QXEy49AA==} + framer-motion@12.38.0: + resolution: {integrity: sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -2792,10 +2626,6 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} - get-nonce@1.0.1: - resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} - engines: {node: '>=6'} - get-port@5.1.1: resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} engines: {node: '>=8'} @@ -2828,12 +2658,8 @@ packages: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} - - globals@16.5.0: - resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + globals@17.5.0: + resolution: {integrity: sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==} engines: {node: '>=18'} globalthis@1.0.4: @@ -2843,6 +2669,11 @@ packages: globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + goober@2.1.18: + resolution: {integrity: sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==} + peerDependencies: + csstype: ^3.0.10 + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -2880,8 +2711,8 @@ packages: resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==} engines: {node: '>=8'} - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} hast-util-to-estree@3.1.3: @@ -2897,17 +2728,16 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true - hoist-non-react-statics@3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} html-encoding-sniffer@6.0.0: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - html@1.0.0: - resolution: {integrity: sha512-lw/7YsdKiP3kk5PnR1INY17iJuzdAtJewxr14ozKJWbbR97znovZ0mh+WEMZ8rjc3lgTK+ID/htTjuyGKB52Kw==} - hasBin: true - http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -2962,9 +2792,6 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - ini@2.0.0: resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} engines: {node: '>=10'} @@ -3129,14 +2956,11 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - isbot@5.1.36: - resolution: {integrity: sha512-C/ZtXyJqDPZ7G7JPr06ApWyYoHjYexQbS6hPYD4WYCzpv2Qes6Z+CCEfTX4Owzf+1EJ933PoI2p+B9v7wpGZBQ==} + isbot@5.1.39: + resolution: {integrity: sha512-obH0yYahGXdzNxo+djmHhBYThUKDkz565cxkIlt2L9hXfv1NlaLKoDBHo6KxXsYrIXx2RK3x5vY36CfZcobxEw==} engines: {node: '>=18'} isexe@2.0.0: @@ -3156,15 +2980,6 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - js-beautify@1.15.4: - resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==} - engines: {node: '>=14'} - hasBin: true - - js-cookie@3.0.5: - resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} - engines: {node: '>=14'} - js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3217,8 +3032,8 @@ packages: engines: {node: '>=6'} hasBin: true - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} jsprim@2.0.2: resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==} @@ -3235,78 +3050,85 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + launch-editor@2.13.2: + resolution: {integrity: sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - lightningcss-android-arm64@1.31.1: - resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [android] - lightningcss-darwin-arm64@1.31.1: - resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==} + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.31.1: - resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==} + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.31.1: - resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==} + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.31.1: - resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==} + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.31.1: - resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] - lightningcss-linux-arm64-musl@1.31.1: - resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] - lightningcss-linux-x64-gnu@1.31.1: - resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] - lightningcss-linux-x64-musl@1.31.1: - resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] - lightningcss-win32-arm64-msvc@1.31.1: - resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.31.1: - resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==} + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.31.1: - resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} lines-and-columns@1.2.4: @@ -3349,8 +3171,8 @@ packages: lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} - lodash@4.17.23: - resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} @@ -3373,8 +3195,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.2.6: - resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} + lru-cache@11.3.5: + resolution: {integrity: sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -3458,9 +3280,6 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - memoize-one@6.0.0: - resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} - merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} @@ -3601,12 +3420,8 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - minimatch@10.1.2: - resolution: {integrity: sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==} - engines: {node: 20 || >=22} - - minimatch@10.2.4: - resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} minimatch@3.1.5: @@ -3647,11 +3462,11 @@ packages: resolution: {integrity: sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==} engines: {node: '>= 0.8.0'} - motion-dom@12.35.2: - resolution: {integrity: sha512-pWXFMTwvGDbx1Fe9YL5HZebv2NhvGBzRtiNUv58aoK7+XrsuaydQ0JGRKK2r+bTKlwgSWwWxHbP5249Qr/BNpg==} + motion-dom@12.38.0: + resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==} - motion-utils@12.29.2: - resolution: {integrity: sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==} + motion-utils@12.36.0: + resolution: {integrity: sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==} mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} @@ -3686,13 +3501,8 @@ packages: resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} engines: {node: '>= 0.4'} - node-releases@2.0.36: - resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} - - nopt@7.2.1: - resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - hasBin: true + node-releases@2.0.38: + resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} @@ -3793,8 +3603,8 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} - parse5@8.0.0: - resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} @@ -3815,8 +3625,8 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - path-to-regexp@0.1.12: - resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + path-to-regexp@0.1.13: + resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} @@ -3837,8 +3647,8 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} pify@2.3.0: @@ -3856,8 +3666,8 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss@8.5.8: - resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + postcss@8.5.10: + resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -3929,8 +3739,8 @@ packages: prettier-plugin-svelte: optional: true - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} engines: {node: '>=14'} hasBin: true @@ -3942,9 +3752,6 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} @@ -3955,9 +3762,6 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} - proto-list@1.2.4: - resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} - proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -3998,23 +3802,16 @@ packages: react: 16.x || 17.x || 18.x || 19.x react-dom: 16.x || 17.x || 18.x || 19.x - react-diff-viewer-continued@4.2.0: - resolution: {integrity: sha512-KXeevuPpMRNDAtF878G04Yih/01DBBoC+RjDzWiA5S6TPtUzSfqF5XOlEWyXVWvJuz5n+EQ9QdUQd0ffK2By6w==} - engines: {node: '>= 16'} - peerDependencies: - react: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - react-dom@19.2.4: - resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + react-dom@19.2.5: + resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} peerDependencies: - react: ^19.2.4 + react: ^19.2.5 - react-hotkeys-hook@4.6.2: - resolution: {integrity: sha512-FmP+ZriY3EG59Ug/lxNfrObCnW9xQShgk7Nb83+CkpfkcCpfS95ydv+E9JuXA5cp8KtskU7LGlIARpkc92X22Q==} + react-hotkeys-hook@5.2.4: + resolution: {integrity: sha512-BgKg+A1+TawkYluh5Bo4cTmcgMN5L29uhJbDUQdHwPX+qgXRjIPYU5kIDHyxnAwCkCBiu9V5OpB2mpyeluVF2A==} peerDependencies: - react: '>=16.8.1' - react-dom: '>=16.8.1' + react: '>=16.8.0' + react-dom: '>=16.8.0' react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -4033,36 +3830,18 @@ packages: resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} engines: {node: '>=0.10.0'} - react-remove-scroll-bar@2.3.8: - resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - react-remove-scroll@2.7.2: - resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - react-router-devtools@1.1.10: - resolution: {integrity: sha512-/Lg0doiVq/Bv547SU4ZWE2UPUaNToGQ0Rnao0tWrZVvPfgGLMQJi4TLv3CxLrXFCo5QeZh0omJ1SCGXOZmpUUw==} + react-router-devtools@6.2.0: + resolution: {integrity: sha512-YzaFAyKZEtTmWzBF/moKuMtEa8Hd/xhTtUCKarrhAbZMyR8S0OpCpN0pyKrNGNz7ueOc4jvvKdE9S6Q3UTotDg==} peerDependencies: + '@types/react': '>=17' + '@types/react-dom': '>=17' react: '>=17' react-dom: '>=17' react-router: '>=7.0.0' vite: '>=5.0.0 || >=6.0.0' - react-router@7.13.1: - resolution: {integrity: sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==} + react-router@7.14.2: + resolution: {integrity: sha512-yCqNne6I8IB6rVCH7XUvlBK7/QKyqypBFGv+8dj4QBFJiiRX+FG7/nkdAvGElyvVZ/HQP5N19wzteuTARXi5Gw==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -4071,29 +3850,16 @@ packages: react-dom: optional: true - react-style-singleton@2.2.3: - resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - react-tooltip@5.30.0: - resolution: {integrity: sha512-Yn8PfbgQ/wmqnL7oBpz1QiDaLKrzZMdSUUdk7nVeGTwzbxCAJiJzR4VSYW+eIO42F1INt57sPUmpgKv0KwJKtg==} + react-tooltip@5.30.1: + resolution: {integrity: sha512-1lSPLQXuVooePxadUpmcwLgOsF1mIty7UZTJ9XnyfX4drOzStYs4JMXnazcDLguQr41W5OUZddOp9kfvArdpEQ==} peerDependencies: react: '>=16.14.0' react-dom: '>=16.14.0' - react@19.2.4: - resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + react@19.2.5: + resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} engines: {node: '>=0.10.0'} - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -4139,8 +3905,8 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} - regjsparser@0.13.0: - resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} + regjsparser@0.13.1: + resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} hasBin: true rehype-recma@1.0.0: @@ -4176,8 +3942,8 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} hasBin: true @@ -4193,16 +3959,16 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rollup@4.59.0: - resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} + rollup@4.60.2: + resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} engines: {node: '>=0.4'} safe-buffer@5.1.2: @@ -4253,6 +4019,16 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + seroval-plugins@1.5.2: + resolution: {integrity: sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.5.2: + resolution: {integrity: sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==} + engines: {node: '>=10'} + serve-static@1.16.3: resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} engines: {node: '>= 0.8.0'} @@ -4283,8 +4059,12 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} side-channel-map@1.0.1: @@ -4324,6 +4104,9 @@ packages: snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + solid-js@1.9.12: + resolution: {integrity: sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -4331,10 +4114,6 @@ packages: source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} - engines: {node: '>=0.10.0'} - source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -4361,8 +4140,8 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} @@ -4395,9 +4174,6 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} @@ -4431,9 +4207,6 @@ packages: style-to-object@1.0.14: resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} - stylis@4.2.0: - resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -4456,8 +4229,8 @@ packages: resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} engines: {node: ^14.18.0 || >=16.0.0} - systeminformation@5.31.4: - resolution: {integrity: sha512-lZppDyQx91VdS5zJvAyGkmwe+Mq6xY978BDUG2wRkWE+jkmUF5ti8cvOovFQoN5bvSFKCXVkyKEaU5ec3SJiRg==} + systeminformation@5.31.5: + resolution: {integrity: sha512-5SyLdip4/3alxD4Kh+63bUQTJmu7YMfYQTC+koZy7X73HgNqZSD2P4wOZQWtUncvPvcEmnfIjCoygN4MRoEejQ==} engines: {node: '>=8.0.0'} os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] hasBin: true @@ -4465,11 +4238,11 @@ packages: tailwind-merge@3.5.0: resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} - tailwindcss@4.2.1: - resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==} + tailwindcss@4.2.4: + resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} - tapable@2.3.0: - resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} tcomb-validation@3.4.1: @@ -4487,12 +4260,12 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyexec@1.0.2: - resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} engines: {node: '>=18'} - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} tinyrainbow@3.1.0: @@ -4502,15 +4275,15 @@ packages: tldts-core@6.1.86: resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} - tldts-core@7.0.25: - resolution: {integrity: sha512-ZjCZK0rppSBu7rjHYDYsEaMOIbbT+nWF57hKkv4IUmZWBNrBWBOjIElc0mKRgLM8bm7x/BBlof6t2gi/Oq/Asw==} + tldts-core@7.0.28: + resolution: {integrity: sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==} tldts@6.1.86: resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} hasBin: true - tldts@7.0.25: - resolution: {integrity: sha512-keinCnPbwXEUG3ilrWQZU+CqcTTzHq9m2HhoUP2l7Xmi8l1LuijAXLpAJ5zRW+ifKTNscs4NdCkfkDCBYm352w==} + tldts@7.0.28: + resolution: {integrity: sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==} hasBin: true tmp@0.2.5: @@ -4529,8 +4302,8 @@ packages: resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} engines: {node: '>=16'} - tough-cookie@6.0.0: - resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} engines: {node: '>=16'} tr46@6.0.0: @@ -4547,8 +4320,8 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - ts-api-utils@2.4.0: - resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' @@ -4575,8 +4348,9 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - turndown@7.2.2: - resolution: {integrity: sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ==} + turndown@7.2.4: + resolution: {integrity: sha512-I8yFsfRzmzK0WV1pNNOA4A7y4RDfFxPRxb3t+e3ui14qSGOxGtiSP6GjeX+Y6CHb7HYaFj7ECUD7VE5kQMZWGQ==} + engines: {node: '>=18', npm: '>=9'} tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} @@ -4613,15 +4387,12 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typedarray@0.0.6: - resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - - typescript-eslint@8.57.0: - resolution: {integrity: sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==} + typescript-eslint@8.59.0: + resolution: {integrity: sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} @@ -4635,8 +4406,8 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + undici-types@7.19.2: + resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -4680,26 +4451,6 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - use-callback-ref@1.3.3: - resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - use-sidecar@1.1.3: - resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - use-sync-external-store@1.6.0: resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: @@ -4711,9 +4462,6 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} @@ -4722,8 +4470,8 @@ packages: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true - valibot@1.2.0: - resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} + valibot@1.3.1: + resolution: {integrity: sha512-sfdRir/QFM0JaF22hqTroPc5xy4DimuGQVKFrzF1YfGwaS1nJot3Y8VqMdLO2Lg27fMzat2yD3pY5PbAYO39Gg==} peerDependencies: typescript: '>=5' peerDependenciesMeta: @@ -4762,8 +4510,8 @@ packages: vite: optional: true - vite@6.4.1: - resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + vite@6.4.2: + resolution: {integrity: sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -4802,20 +4550,23 @@ packages: yaml: optional: true - vitest@4.0.18: - resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} + vitest@4.1.5: + resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.18 - '@vitest/browser-preview': 4.0.18 - '@vitest/browser-webdriverio': 4.0.18 - '@vitest/ui': 4.0.18 + '@vitest/browser-playwright': 4.1.5 + '@vitest/browser-preview': 4.1.5 + '@vitest/browser-webdriverio': 4.1.5 + '@vitest/coverage-istanbul': 4.1.5 + '@vitest/coverage-v8': 4.1.5 + '@vitest/ui': 4.1.5 happy-dom: '*' jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -4829,6 +4580,10 @@ packages: optional: true '@vitest/browser-webdriverio': optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true '@vitest/ui': optional: true happy-dom: @@ -4907,8 +4662,8 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.19.0: - resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -4926,8 +4681,8 @@ packages: xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - xmllint-wasm@5.1.0: - resolution: {integrity: sha512-6HCIJKAJWt96UzA2dgPXsnMuYQihD7U1DU9Tu3BdXqVruha1KV8nUofOxbw8f5ULgQGdNsJMwtX3dyaTHd9hQQ==} + xmllint-wasm@5.2.0: + resolution: {integrity: sha512-GVMuR3ViU8R7sakcVm/4GClMtCV8p7xgjXZlc6GmvPpInIz4V41lmRnjSd4uKhVkf5MZj97wEZkPM4RMAhojuQ==} engines: {node: '>=16'} peerDependencies: '@types/node': '>=16' @@ -4939,8 +4694,8 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + yaml@1.10.3: + resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} engines: {node: '>= 6'} yargs-parser@21.1.1: @@ -4962,6 +4717,15 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zustand@4.5.7: resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} engines: {node: '>=12.7.0'} @@ -4977,8 +4741,8 @@ packages: react: optional: true - zustand@5.0.11: - resolution: {integrity: sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==} + zustand@5.0.12: + resolution: {integrity: sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==} engines: {node: '>=12.20.0'} peerDependencies: '@types/react': '>=18.0.0' @@ -5006,11 +4770,11 @@ snapshots: '@asamuzakjp/css-color@4.1.2': dependencies: - '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-color-parser': 4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - lru-cache: 11.2.6 + lru-cache: 11.3.5 '@asamuzakjp/dom-selector@6.8.1': dependencies: @@ -5018,7 +4782,7 @@ snapshots: bidi-js: 1.0.3 css-tree: 3.2.1 is-potential-custom-element-name: 1.0.1 - lru-cache: 11.2.6 + lru-cache: 11.3.5 '@asamuzakjp/nwsapi@2.3.9': {} @@ -5036,8 +4800,8 @@ snapshots: '@babel/generator': 7.29.1 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helpers': 7.28.6 - '@babel/parser': 7.29.0 + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 @@ -5052,11 +4816,11 @@ snapshots: '@babel/generator@7.29.1': dependencies: - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/types': 7.29.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.0.2 + jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.27.3': dependencies: @@ -5066,7 +4830,7 @@ snapshots: dependencies: '@babel/compat-data': 7.29.0 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 + browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 @@ -5136,12 +4900,12 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.28.6': + '@babel/helpers@7.29.2': dependencies: '@babel/template': 7.28.6 '@babel/types': 7.29.0 - '@babel/parser@7.29.0': + '@babel/parser@7.29.2': dependencies: '@babel/types': 7.29.0 @@ -5195,12 +4959,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/runtime@7.28.6': {} + '@babel/runtime@7.29.2': {} '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/types': 7.29.0 '@babel/traverse@7.29.0': @@ -5208,7 +4972,7 @@ snapshots: '@babel/code-frame': 7.29.0 '@babel/generator': 7.29.1 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/template': 7.28.6 '@babel/types': 7.29.0 debug: 4.4.3(supports-color@8.1.1) @@ -5220,31 +4984,31 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@biomejs/cli-darwin-arm64@1.9.4': + '@biomejs/cli-darwin-arm64@2.4.13': optional: true - '@bkrem/react-transition-group@1.3.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@bkrem/react-transition-group@1.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: chain-function: 1.0.1 dom-helpers: 3.4.0 loose-envify: 1.4.0 prop-types: 15.8.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) react-lifecycles-compat: 3.0.4 warning: 3.0.0 '@csstools/color-helpers@6.0.2': {} - '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-calc@3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-color-parser@4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-color-parser@4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/color-helpers': 6.0.2 - '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 @@ -5252,7 +5016,9 @@ snapshots: dependencies: '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.1.0': {} + '@csstools/css-syntax-patches-for-csstree@1.1.3(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 '@csstools/css-tokenizer@4.0.0': {} @@ -5290,80 +5056,6 @@ snapshots: '@dagrejs/graphlib@2.2.4': {} - '@emotion/babel-plugin@11.13.5': - dependencies: - '@babel/helper-module-imports': 7.28.6 - '@babel/runtime': 7.28.6 - '@emotion/hash': 0.9.2 - '@emotion/memoize': 0.9.0 - '@emotion/serialize': 1.3.3 - babel-plugin-macros: 3.1.0 - convert-source-map: 1.9.0 - escape-string-regexp: 4.0.0 - find-root: 1.1.0 - source-map: 0.5.7 - stylis: 4.2.0 - transitivePeerDependencies: - - supports-color - - '@emotion/cache@11.14.0': - dependencies: - '@emotion/memoize': 0.9.0 - '@emotion/sheet': 1.4.0 - '@emotion/utils': 1.4.2 - '@emotion/weak-memoize': 0.4.0 - stylis: 4.2.0 - - '@emotion/css@11.13.5': - dependencies: - '@emotion/babel-plugin': 11.13.5 - '@emotion/cache': 11.14.0 - '@emotion/serialize': 1.3.3 - '@emotion/sheet': 1.4.0 - '@emotion/utils': 1.4.2 - transitivePeerDependencies: - - supports-color - - '@emotion/hash@0.9.2': {} - - '@emotion/memoize@0.9.0': {} - - '@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4)': - dependencies: - '@babel/runtime': 7.28.6 - '@emotion/babel-plugin': 11.13.5 - '@emotion/cache': 11.14.0 - '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.4) - '@emotion/utils': 1.4.2 - '@emotion/weak-memoize': 0.4.0 - hoist-non-react-statics: 3.3.2 - react: 19.2.4 - optionalDependencies: - '@types/react': 19.2.14 - transitivePeerDependencies: - - supports-color - - '@emotion/serialize@1.3.3': - dependencies: - '@emotion/hash': 0.9.2 - '@emotion/memoize': 0.9.0 - '@emotion/unitless': 0.10.0 - '@emotion/utils': 1.4.2 - csstype: 3.2.3 - - '@emotion/sheet@1.4.0': {} - - '@emotion/unitless@0.10.0': {} - - '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.4)': - dependencies: - react: 19.2.4 - - '@emotion/utils@1.4.2': {} - - '@emotion/weak-memoize@0.4.0': {} - '@esbuild/aix-ppc64@0.25.12': optional: true @@ -5442,50 +5134,38 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.1(eslint@10.2.1(jiti@2.6.1))': dependencies: - eslint: 9.39.4(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.21.2': + '@eslint/config-array@0.23.5': dependencies: - '@eslint/object-schema': 2.1.7 + '@eslint/object-schema': 3.0.5 debug: 4.4.3(supports-color@8.1.1) - minimatch: 3.1.5 + minimatch: 10.2.5 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.4.2': + '@eslint/config-helpers@0.5.5': dependencies: - '@eslint/core': 0.17.0 + '@eslint/core': 1.2.1 - '@eslint/core@0.17.0': + '@eslint/core@1.2.1': dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.5': - dependencies: - ajv: 6.14.0 - debug: 4.4.3(supports-color@8.1.1) - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.5 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@9.39.4': {} + '@eslint/js@10.0.1(eslint@10.2.1(jiti@2.6.1))': + optionalDependencies: + eslint: 10.2.1(jiti@2.6.1) - '@eslint/object-schema@2.1.7': {} + '@eslint/object-schema@3.0.5': {} - '@eslint/plugin-kit@0.4.1': + '@eslint/plugin-kit@0.7.1': dependencies: - '@eslint/core': 0.17.0 + '@eslint/core': 1.2.1 levn: 0.4.1 '@exodus/bytes@1.15.0': {} @@ -5499,38 +5179,31 @@ snapshots: '@floating-ui/core': 1.7.5 '@floating-ui/utils': 0.2.11 - '@floating-ui/react-dom@2.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@floating-ui/dom': 1.7.6 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - '@floating-ui/utils@0.2.11': {} '@frankframework/doc-library-core@1.0.3': {} - '@frankframework/doc-library-react@1.0.3(react@19.2.4)': + '@frankframework/doc-library-react@1.0.3(react@19.2.5)': dependencies: '@frankframework/doc-library-core': 1.0.3 - react: 19.2.4 + react: 19.2.5 - '@humanfs/core@0.19.1': {} + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 - '@humanfs/node@0.16.7': + '@humanfs/node@0.16.8': dependencies: - '@humanfs/core': 0.19.1 + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 '@humanwhocodes/retry': 0.4.3 + '@humanfs/types@0.15.0': {} + '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/retry@0.4.3': {} - '@isaacs/balanced-match@4.0.1': {} - - '@isaacs/brace-expansion@5.0.1': - dependencies: - '@isaacs/balanced-match': 4.0.1 - '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -5589,322 +5262,178 @@ snapshots: transitivePeerDependencies: - supports-color - '@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.4)': - dependencies: - '@types/mdx': 2.0.13 - '@types/react': 19.2.14 - react: 19.2.4 - - '@mdx-js/rollup@3.1.1(rollup@4.59.0)': - dependencies: - '@mdx-js/mdx': 3.1.1 - '@rollup/pluginutils': 5.3.0(rollup@4.59.0) - rollup: 4.59.0 - source-map: 0.7.6 - vfile: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@mixmark-io/domino@2.2.0': {} - - '@mjackson/node-fetch-server@0.2.0': {} - - '@monaco-editor/loader@1.7.0': - dependencies: - state-local: 1.0.7 - - '@monaco-editor/react@4.7.0-rc.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@monaco-editor/loader': 1.7.0 - monaco-editor: 0.55.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - - '@one-ini/wasm@0.1.1': {} - - '@pkgjs/parseargs@0.11.0': - optional: true - - '@pkgr/core@0.2.9': {} - - '@polka/url@1.0.0-next.29': {} - - '@prettier/plugin-xml@3.4.2(prettier@3.8.1)': - dependencies: - '@xml-tools/parser': 1.0.11 - prettier: 3.8.1 - - '@radix-ui/number@1.1.1': {} - - '@radix-ui/primitive@1.1.3': {} - - '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) - - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) - - '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) - - '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) - - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.4)': - dependencies: - react: 19.2.4 - optionalDependencies: - '@types/react': 19.2.14 - - '@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.4)': + '@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5)': dependencies: - react: 19.2.4 - optionalDependencies: + '@types/mdx': 2.0.13 '@types/react': 19.2.14 + react: 19.2.5 - '@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.2.4)': + '@mdx-js/rollup@3.1.1(rollup@4.60.2)': dependencies: - react: 19.2.4 - optionalDependencies: - '@types/react': 19.2.14 + '@mdx-js/mdx': 3.1.1 + '@rollup/pluginutils': 5.3.0(rollup@4.60.2) + rollup: 4.60.2 + source-map: 0.7.6 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@mixmark-io/domino@2.2.0': {} - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.14)(react@19.2.4)': - dependencies: - react: 19.2.4 - optionalDependencies: - '@types/react': 19.2.14 + '@mjackson/node-fetch-server@0.2.0': {} - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@monaco-editor/loader@1.7.0': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + state-local: 1.0.7 - '@radix-ui/react-id@1.1.1(@types/react@19.2.14)(react@19.2.4)': + '@monaco-editor/react@4.7.0(monaco-editor@0.55.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 - optionalDependencies: - '@types/react': 19.2.14 + '@monaco-editor/loader': 1.7.0 + monaco-editor: 0.55.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) - '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@floating-ui/react-dom': 2.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/rect': 1.1.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.2.9': {} + + '@polka/url@1.0.0-next.29': {} - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@prettier/plugin-xml@3.4.2(prettier@3.8.3)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@xml-tools/parser': 1.0.11 + prettier: 3.8.3 + + '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - '@radix-ui/number': 1.1.1 - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - aria-hidden: 1.2.6 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.4)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.5)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 + react: 19.2.5 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.14)(react@19.2.4)': + '@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.5)': dependencies: - react: 19.2.4 + react: 19.2.5 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.4)': + '@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.2.5)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 + react: 19.2.5 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.4)': + '@radix-ui/react-id@1.1.1(@types/react@19.2.14)(react@19.2.5)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.2.4)': + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) optionalDependencies: '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.4)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - react: 19.2.4 + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) optionalDependencies: '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.14)(react@19.2.4)': + '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.5)': dependencies: - react: 19.2.4 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.14)(react@19.2.4)': + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.5)': dependencies: - '@radix-ui/rect': 1.1.1 - react: 19.2.4 + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-size@1.1.1(@types/react@19.2.14)(react@19.2.4)': + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.5)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.5)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.5 optionalDependencies: '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) - - '@radix-ui/rect@1.1.1': {} - '@react-router/dev@7.13.1(@react-router/serve@7.13.1(react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(lightningcss@1.31.1)(react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1))': + '@react-router/dev@7.14.2(@react-router/serve@7.14.2(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3))(@types/node@20.19.39)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(lightningcss@1.32.0)(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0))': dependencies: '@babel/core': 7.29.0 '@babel/generator': 7.29.1 - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 - '@react-router/node': 7.13.1(react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + '@react-router/node': 7.14.2(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3) '@remix-run/node-fetch-server': 0.13.0 arg: 5.0.2 babel-dead-code-elimination: 1.0.12 @@ -5912,23 +5441,23 @@ snapshots: dedent: 1.7.2(babel-plugin-macros@3.1.0) es-module-lexer: 1.7.0 exit-hook: 2.2.1 - isbot: 5.1.36 + isbot: 5.1.39 jsesc: 3.0.2 - lodash: 4.17.23 + lodash: 4.18.1 p-map: 7.0.4 pathe: 1.1.2 picocolors: 1.1.1 pkg-types: 2.3.0 - prettier: 3.8.1 + prettier: 3.8.3 react-refresh: 0.14.2 - react-router: 7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react-router: 7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) semver: 7.7.4 - tinyglobby: 0.2.15 - valibot: 1.2.0(typescript@5.9.3) - vite: 6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1) - vite-node: 3.2.4(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1) + tinyglobby: 0.2.16 + valibot: 1.3.1(typescript@5.9.3) + vite: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0) + vite-node: 3.2.4(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0) optionalDependencies: - '@react-router/serve': 7.13.1(react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + '@react-router/serve': 7.14.2(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - '@types/node' @@ -5945,31 +5474,31 @@ snapshots: - tsx - yaml - '@react-router/express@7.13.1(express@4.22.1)(react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)': + '@react-router/express@7.14.2(express@4.22.1)(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3)': dependencies: - '@react-router/node': 7.13.1(react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + '@react-router/node': 7.14.2(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3) express: 4.22.1 - react-router: 7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react-router: 7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) optionalDependencies: typescript: 5.9.3 - '@react-router/node@7.13.1(react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)': + '@react-router/node@7.14.2(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3)': dependencies: '@mjackson/node-fetch-server': 0.2.0 - react-router: 7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react-router: 7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) optionalDependencies: typescript: 5.9.3 - '@react-router/serve@7.13.1(react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)': + '@react-router/serve@7.14.2(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3)': dependencies: '@mjackson/node-fetch-server': 0.2.0 - '@react-router/express': 7.13.1(express@4.22.1)(react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) - '@react-router/node': 7.13.1(react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + '@react-router/express': 7.14.2(express@4.22.1)(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3) + '@react-router/node': 7.14.2(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3) compression: 1.8.1 express: 4.22.1 get-port: 5.1.1 morgan: 1.10.1 - react-router: 7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react-router: 7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) source-map-support: 0.5.21 transitivePeerDependencies: - supports-color @@ -5979,89 +5508,123 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.3': {} - '@rollup/pluginutils@5.3.0(rollup@4.59.0)': + '@rollup/pluginutils@5.3.0(rollup@4.60.2)': dependencies: '@types/estree': 1.0.8 estree-walker: 2.0.2 - picomatch: 4.0.3 + picomatch: 4.0.4 optionalDependencies: - rollup: 4.59.0 + rollup: 4.60.2 - '@rollup/rollup-android-arm-eabi@4.59.0': + '@rollup/rollup-android-arm-eabi@4.60.2': optional: true - '@rollup/rollup-android-arm64@4.59.0': + '@rollup/rollup-android-arm64@4.60.2': optional: true - '@rollup/rollup-darwin-arm64@4.59.0': + '@rollup/rollup-darwin-arm64@4.60.2': optional: true - '@rollup/rollup-darwin-x64@4.59.0': + '@rollup/rollup-darwin-x64@4.60.2': optional: true - '@rollup/rollup-freebsd-arm64@4.59.0': + '@rollup/rollup-freebsd-arm64@4.60.2': optional: true - '@rollup/rollup-freebsd-x64@4.59.0': + '@rollup/rollup-freebsd-x64@4.60.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.59.0': + '@rollup/rollup-linux-arm-musleabihf@4.60.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.59.0': + '@rollup/rollup-linux-arm64-gnu@4.60.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.59.0': + '@rollup/rollup-linux-arm64-musl@4.60.2': optional: true - '@rollup/rollup-linux-loong64-gnu@4.59.0': + '@rollup/rollup-linux-loong64-gnu@4.60.2': optional: true - '@rollup/rollup-linux-loong64-musl@4.59.0': + '@rollup/rollup-linux-loong64-musl@4.60.2': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.59.0': + '@rollup/rollup-linux-ppc64-gnu@4.60.2': optional: true - '@rollup/rollup-linux-ppc64-musl@4.59.0': + '@rollup/rollup-linux-ppc64-musl@4.60.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.59.0': + '@rollup/rollup-linux-riscv64-gnu@4.60.2': optional: true - '@rollup/rollup-linux-riscv64-musl@4.59.0': + '@rollup/rollup-linux-riscv64-musl@4.60.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.59.0': + '@rollup/rollup-linux-s390x-gnu@4.60.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.59.0': + '@rollup/rollup-linux-x64-gnu@4.60.2': optional: true - '@rollup/rollup-linux-x64-musl@4.59.0': + '@rollup/rollup-linux-x64-musl@4.60.2': optional: true - '@rollup/rollup-openbsd-x64@4.59.0': + '@rollup/rollup-openbsd-x64@4.60.2': optional: true - '@rollup/rollup-openharmony-arm64@4.59.0': + '@rollup/rollup-openharmony-arm64@4.60.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.59.0': + '@rollup/rollup-win32-arm64-msvc@4.60.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.59.0': + '@rollup/rollup-win32-ia32-msvc@4.60.2': optional: true - '@rollup/rollup-win32-x64-gnu@4.59.0': + '@rollup/rollup-win32-x64-gnu@4.60.2': optional: true - '@rollup/rollup-win32-x64-msvc@4.59.0': + '@rollup/rollup-win32-x64-msvc@4.60.2': optional: true + '@solid-primitives/event-listener@2.4.5(solid-js@1.9.12)': + dependencies: + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/keyboard@1.3.5(solid-js@1.9.12)': + dependencies: + '@solid-primitives/event-listener': 2.4.5(solid-js@1.9.12) + '@solid-primitives/rootless': 1.5.3(solid-js@1.9.12) + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/resize-observer@2.1.5(solid-js@1.9.12)': + dependencies: + '@solid-primitives/event-listener': 2.4.5(solid-js@1.9.12) + '@solid-primitives/rootless': 1.5.3(solid-js@1.9.12) + '@solid-primitives/static-store': 0.1.3(solid-js@1.9.12) + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/rootless@1.5.3(solid-js@1.9.12)': + dependencies: + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/static-store@0.1.3(solid-js@1.9.12)': + dependencies: + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/utils@6.4.0(solid-js@1.9.12)': + dependencies: + solid-js: 1.9.12 + '@standard-schema/spec@1.1.0': {} '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.0)': @@ -6134,78 +5697,158 @@ snapshots: transitivePeerDependencies: - supports-color - '@tailwindcss/node@4.2.1': + '@tailwindcss/node@4.2.4': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.20.0 + enhanced-resolve: 5.21.0 jiti: 2.6.1 - lightningcss: 1.31.1 + lightningcss: 1.32.0 magic-string: 0.30.21 source-map-js: 1.2.1 - tailwindcss: 4.2.1 + tailwindcss: 4.2.4 - '@tailwindcss/oxide-android-arm64@4.2.1': + '@tailwindcss/oxide-android-arm64@4.2.4': optional: true - '@tailwindcss/oxide-darwin-arm64@4.2.1': + '@tailwindcss/oxide-darwin-arm64@4.2.4': optional: true - '@tailwindcss/oxide-darwin-x64@4.2.1': + '@tailwindcss/oxide-darwin-x64@4.2.4': optional: true - '@tailwindcss/oxide-freebsd-x64@4.2.1': + '@tailwindcss/oxide-freebsd-x64@4.2.4': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.2.1': + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.2.1': + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.2.1': + '@tailwindcss/oxide-linux-x64-musl@4.2.4': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.2.1': + '@tailwindcss/oxide-wasm32-wasi@4.2.4': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.2.1': + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': optional: true - '@tailwindcss/oxide@4.2.1': + '@tailwindcss/oxide@4.2.4': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.2.1 - '@tailwindcss/oxide-darwin-arm64': 4.2.1 - '@tailwindcss/oxide-darwin-x64': 4.2.1 - '@tailwindcss/oxide-freebsd-x64': 4.2.1 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.1 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.1 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.1 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.1 - '@tailwindcss/oxide-linux-x64-musl': 4.2.1 - '@tailwindcss/oxide-wasm32-wasi': 4.2.1 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.1 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.1 - - '@tailwindcss/vite@4.2.1(vite@6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1))': - dependencies: - '@tailwindcss/node': 4.2.1 - '@tailwindcss/oxide': 4.2.1 - tailwindcss: 4.2.1 - vite: 6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1) + '@tailwindcss/oxide-android-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-x64': 4.2.4 + '@tailwindcss/oxide-freebsd-x64': 4.2.4 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.4 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.4 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-x64-musl': 4.2.4 + '@tailwindcss/oxide-wasm32-wasi': 4.2.4 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 + + '@tailwindcss/vite@4.2.4(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0))': + dependencies: + '@tailwindcss/node': 4.2.4 + '@tailwindcss/oxide': 4.2.4 + tailwindcss: 4.2.4 + vite: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0) + + '@tanstack/devtools-client@0.0.5': + dependencies: + '@tanstack/devtools-event-client': 0.4.3 + + '@tanstack/devtools-client@0.0.6': + dependencies: + '@tanstack/devtools-event-client': 0.4.3 + + '@tanstack/devtools-event-bus@0.4.0': + dependencies: + ws: 8.20.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@tanstack/devtools-event-bus@0.4.1': + dependencies: + ws: 8.20.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@tanstack/devtools-event-client@0.4.3': {} + + '@tanstack/devtools-ui@0.5.0(csstype@3.2.3)(solid-js@1.9.12)': + dependencies: + clsx: 2.1.1 + dayjs: 1.11.20 + goober: 2.1.18(csstype@3.2.3) + solid-js: 1.9.12 + transitivePeerDependencies: + - csstype + + '@tanstack/devtools-vite@0.4.1(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0))': + dependencies: + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.2 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@tanstack/devtools-client': 0.0.5 + '@tanstack/devtools-event-bus': 0.4.0 + chalk: 5.6.2 + launch-editor: 2.13.2 + picomatch: 4.0.4 + vite: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@tanstack/devtools@0.10.14(csstype@3.2.3)(solid-js@1.9.12)': + dependencies: + '@solid-primitives/event-listener': 2.4.5(solid-js@1.9.12) + '@solid-primitives/keyboard': 1.3.5(solid-js@1.9.12) + '@solid-primitives/resize-observer': 2.1.5(solid-js@1.9.12) + '@tanstack/devtools-client': 0.0.6 + '@tanstack/devtools-event-bus': 0.4.1 + '@tanstack/devtools-ui': 0.5.0(csstype@3.2.3)(solid-js@1.9.12) + clsx: 2.1.1 + goober: 2.1.18(csstype@3.2.3) + solid-js: 1.9.12 + transitivePeerDependencies: + - bufferutil + - csstype + - utf-8-validate + + '@tanstack/react-devtools@0.9.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(solid-js@1.9.12)': + dependencies: + '@tanstack/devtools': 0.10.14(csstype@3.2.3)(solid-js@1.9.12) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + transitivePeerDependencies: + - bufferutil + - csstype + - solid-js + - utf-8-validate '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.29.0 - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.2 '@types/aria-query': 5.0.4 aria-query: 5.3.0 dom-accessibility-api: 0.5.16 @@ -6222,12 +5865,12 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.2 '@testing-library/dom': 10.4.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) @@ -6240,7 +5883,7 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/types': 7.29.0 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 @@ -6252,7 +5895,7 @@ snapshots: '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/types': 7.29.0 '@types/babel__traverse@7.28.0': @@ -6287,12 +5930,14 @@ snapshots: '@types/d3-interpolate': 3.0.4 '@types/d3-selection': 3.0.11 - '@types/debug@4.1.12': + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 '@types/deep-eql@4.0.2': {} + '@types/esrecurse@4.3.1': {} + '@types/estree-jsx@1.0.5': dependencies: '@types/estree': 1.0.8 @@ -6313,25 +5958,22 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@20.19.37': + '@types/node@20.19.39': dependencies: undici-types: 6.21.0 - '@types/node@25.4.0': + '@types/node@25.6.0': dependencies: - undici-types: 7.18.2 + undici-types: 7.19.2 optional: true - '@types/parse-json@4.0.2': {} + '@types/parse-json@4.0.2': + optional: true '@types/react-dom@19.2.3(@types/react@19.2.14)': dependencies: '@types/react': 19.2.14 - '@types/react-reconciler@0.28.9(@types/react@19.2.14)': - dependencies: - '@types/react': 19.2.14 - '@types/react@19.2.14': dependencies: csstype: 3.2.3 @@ -6351,103 +5993,103 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 25.4.0 + '@types/node': 25.6.0 optional: true - '@typescript-eslint/eslint-plugin@8.57.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3))(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.57.0 - '@typescript-eslint/type-utils': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.57.0 - eslint: 9.39.4(jiti@2.6.1) + '@typescript-eslint/parser': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/type-utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.59.0 + eslint: 10.2.1(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.4.0(typescript@5.9.3) + ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.57.0 - '@typescript-eslint/types': 8.57.0 - '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.57.0 + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.59.0 debug: 4.4.3(supports-color@8.1.1) - eslint: 9.39.4(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.57.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.59.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@5.9.3) - '@typescript-eslint/types': 8.57.0 + '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@5.9.3) + '@typescript-eslint/types': 8.59.0 debug: 4.4.3(supports-color@8.1.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.57.0': + '@typescript-eslint/scope-manager@8.59.0': dependencies: - '@typescript-eslint/types': 8.57.0 - '@typescript-eslint/visitor-keys': 8.57.0 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/visitor-keys': 8.59.0 - '@typescript-eslint/tsconfig-utils@8.57.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.59.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.57.0 - '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3(supports-color@8.1.1) - eslint: 9.39.4(jiti@2.6.1) - ts-api-utils: 2.4.0(typescript@5.9.3) + eslint: 10.2.1(jiti@2.6.1) + ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.57.0': {} + '@typescript-eslint/types@8.59.0': {} - '@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.59.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.57.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@5.9.3) - '@typescript-eslint/types': 8.57.0 - '@typescript-eslint/visitor-keys': 8.57.0 + '@typescript-eslint/project-service': 8.59.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@5.9.3) + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/visitor-keys': 8.59.0 debug: 4.4.3(supports-color@8.1.1) - minimatch: 10.2.4 + minimatch: 10.2.5 semver: 7.7.4 - tinyglobby: 0.2.15 - ts-api-utils: 2.4.0(typescript@5.9.3) + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.57.0 - '@typescript-eslint/types': 8.57.0 - '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3) - eslint: 9.39.4(jiti@2.6.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@5.9.3) + eslint: 10.2.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.57.0': + '@typescript-eslint/visitor-keys@8.59.0': dependencies: - '@typescript-eslint/types': 8.57.0 + '@typescript-eslint/types': 8.59.0 eslint-visitor-keys: 5.0.1 '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react@5.1.4(vite@6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1))': + '@vitejs/plugin-react@5.2.0(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -6455,78 +6097,80 @@ snapshots: '@rolldown/pluginutils': 1.0.0-rc.3 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1) + vite: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0) transitivePeerDependencies: - supports-color - '@vitest/expect@4.0.18': + '@vitest/expect@4.1.5': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.0.18(vite@6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1))': + '@vitest/mocker@4.1.5(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0))': dependencies: - '@vitest/spy': 4.0.18 + '@vitest/spy': 4.1.5 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1) + vite: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0) - '@vitest/pretty-format@4.0.18': + '@vitest/pretty-format@4.1.5': dependencies: tinyrainbow: 3.1.0 - '@vitest/runner@4.0.18': + '@vitest/runner@4.1.5': dependencies: - '@vitest/utils': 4.0.18 + '@vitest/utils': 4.1.5 pathe: 2.0.3 - '@vitest/snapshot@4.0.18': + '@vitest/snapshot@4.1.5': dependencies: - '@vitest/pretty-format': 4.0.18 + '@vitest/pretty-format': 4.1.5 + '@vitest/utils': 4.1.5 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.0.18': {} + '@vitest/spy@4.1.5': {} - '@vitest/ui@4.0.18(vitest@4.0.18)': + '@vitest/ui@4.1.5(vitest@4.1.5)': dependencies: - '@vitest/utils': 4.0.18 + '@vitest/utils': 4.1.5 fflate: 0.8.2 - flatted: 3.4.1 + flatted: 3.4.2 pathe: 2.0.3 sirv: 3.0.2 - tinyglobby: 0.2.15 + tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vitest: 4.0.18(@types/node@20.19.37)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1) + vitest: 4.1.5(@types/node@20.19.39)(@vitest/ui@4.1.5)(jsdom@27.4.0)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)) - '@vitest/utils@4.0.18': + '@vitest/utils@4.1.5': dependencies: - '@vitest/pretty-format': 4.0.18 + '@vitest/pretty-format': 4.1.5 + convert-source-map: 2.0.0 tinyrainbow: 3.1.0 '@xml-tools/parser@1.0.11': dependencies: chevrotain: 7.1.1 - '@xmldom/xmldom@0.8.11': {} + '@xmldom/xmldom@0.8.13': {} - '@xyflow/react@12.10.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@xyflow/react@12.10.2(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - '@xyflow/system': 0.0.75 + '@xyflow/system': 0.0.76 classcat: 5.0.5 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - zustand: 4.5.7(@types/react@19.2.14)(react@19.2.4) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + zustand: 4.5.7(@types/react@19.2.14)(react@19.2.5) transitivePeerDependencies: - '@types/react' - immer - '@xyflow/system@0.0.75': + '@xyflow/system@0.0.76': dependencies: '@types/d3-drag': 3.0.7 '@types/d3-interpolate': 3.0.4 @@ -6538,8 +6182,6 @@ snapshots: d3-selection: 3.0.0 d3-zoom: 3.0.0 - abbrev@2.0.0: {} - accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -6558,23 +6200,23 @@ snapshots: clean-stack: 2.2.0 indent-string: 4.0.0 - ajv@6.14.0: + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 - allotment@1.20.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + allotment@1.20.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: classnames: 2.5.1 eventemitter3: 5.0.4 fast-deep-equal: 3.1.3 lodash.clamp: 4.0.3 lodash.debounce: 4.0.8 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - usehooks-ts: 3.1.1(react@19.2.4) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + usehooks-ts: 3.1.1(react@19.2.5) ansi-colors@4.1.3: {} @@ -6600,10 +6242,6 @@ snapshots: argparse@2.0.1: {} - aria-hidden@1.2.6: - dependencies: - tslib: 2.8.1 - aria-query@5.3.0: dependencies: dequal: 2.0.3 @@ -6619,10 +6257,10 @@ snapshots: array-includes@3.1.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 is-string: 1.1.1 @@ -6630,41 +6268,41 @@ snapshots: array.prototype.findlast@1.2.5: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-object-atoms: 1.1.1 es-shim-unscopables: 1.1.0 array.prototype.flat@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 array.prototype.flatmap@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 array.prototype.tosorted@1.1.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-shim-unscopables: 1.1.0 arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 @@ -6698,7 +6336,7 @@ snapshots: babel-dead-code-elimination@1.0.12: dependencies: '@babel/core': 7.29.0 - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 transitivePeerDependencies: @@ -6706,9 +6344,10 @@ snapshots: babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.2 cosmiconfig: 7.1.0 - resolve: 1.22.11 + resolve: 1.22.12 + optional: true bail@2.0.2: {} @@ -6718,7 +6357,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.10.0: {} + baseline-browser-mapping@2.10.21: {} basic-auth@2.0.1: dependencies: @@ -6728,23 +6367,10 @@ snapshots: dependencies: tweetnacl: 0.14.5 - beautify@0.0.8: - dependencies: - cssbeautify: 0.3.1 - html: 1.0.0 - js-beautify: 1.15.4 - bidi-js@1.0.3: dependencies: require-from-string: 2.0.2 - bippy@0.3.34(@types/react@19.2.14)(react@19.2.4): - dependencies: - '@types/react-reconciler': 0.28.9(@types/react@19.2.14) - react: 19.2.4 - transitivePeerDependencies: - - '@types/react' - blob-util@2.0.2: {} bluebird@3.7.2: {} @@ -6766,28 +6392,28 @@ snapshots: transitivePeerDependencies: - supports-color - brace-expansion@1.1.12: + brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.2: + brace-expansion@2.1.0: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.4: + brace-expansion@5.0.5: dependencies: balanced-match: 4.0.4 browser-stdout@1.3.1: {} - browserslist@4.28.1: + browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.0 - caniuse-lite: 1.0.30001778 - electron-to-chromium: 1.5.313 - node-releases: 2.0.36 - update-browserslist-db: 1.2.3(browserslist@4.28.1) + baseline-browser-mapping: 2.10.21 + caniuse-lite: 1.0.30001790 + electron-to-chromium: 1.5.344 + node-releases: 2.0.38 + update-browserslist-db: 1.2.3(browserslist@4.28.2) buffer-crc32@0.2.13: {} @@ -6800,7 +6426,7 @@ snapshots: builtin-modules@3.3.0: {} - builtin-modules@5.0.0: {} + builtin-modules@5.1.0: {} bytes@3.1.2: {} @@ -6813,7 +6439,7 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: + call-bind@1.0.9: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 @@ -6829,7 +6455,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001778: {} + caniuse-lite@1.0.30001790: {} caseless@0.12.0: {} @@ -6920,8 +6546,6 @@ snapshots: comma-separated-tokens@2.0.3: {} - commander@10.0.1: {} - commander@6.2.1: {} common-tags@1.8.2: {} @@ -6944,28 +6568,14 @@ snapshots: concat-map@0.0.1: {} - concat-stream@1.6.2: - dependencies: - buffer-from: 1.1.2 - inherits: 2.0.4 - readable-stream: 2.3.8 - typedarray: 0.0.6 - confbox@0.2.4: {} - config-chain@1.1.13: - dependencies: - ini: 1.3.8 - proto-list: 1.2.4 - content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 content-type@1.0.5: {} - convert-source-map@1.9.0: {} - convert-source-map@2.0.0: {} cookie-signature@1.0.7: {} @@ -6974,21 +6584,20 @@ snapshots: cookie@1.1.1: {} - core-js-compat@3.48.0: + core-js-compat@3.49.0: dependencies: - browserslist: 4.28.1 + browserslist: 4.28.2 core-util-is@1.0.2: {} - core-util-is@1.0.3: {} - cosmiconfig@7.1.0: dependencies: '@types/parse-json': 4.0.2 import-fresh: 3.3.1 parse-json: 5.2.0 path-type: 4.0.0 - yaml: 1.10.2 + yaml: 1.10.3 + optional: true cosmiconfig@8.3.6(typescript@5.9.3): dependencies: @@ -7012,27 +6621,25 @@ snapshots: css.escape@1.5.1: {} - cssbeautify@0.3.1: {} - cssstyle@5.3.7: dependencies: '@asamuzakjp/css-color': 4.1.2 - '@csstools/css-syntax-patches-for-csstree': 1.1.0 + '@csstools/css-syntax-patches-for-csstree': 1.1.3(css-tree@3.2.1) css-tree: 3.2.1 - lru-cache: 11.2.6 + lru-cache: 11.3.5 csstype@3.2.3: {} cypress-multi-reporters@2.0.5(mocha@11.7.5): dependencies: debug: 4.4.3(supports-color@8.1.1) - lodash: 4.17.23 + lodash: 4.18.1 mocha: 11.7.5 semver: 7.7.4 transitivePeerDependencies: - supports-color - cypress@15.11.0: + cypress@15.14.1: dependencies: '@cypress/request': 3.0.10 '@cypress/xvfb': 1.2.4(supports-color@8.1.1) @@ -7062,7 +6669,7 @@ snapshots: hasha: 5.2.2 is-installed-globally: 0.4.0 listr2: 3.14.0(enquirer@2.4.1) - lodash: 4.17.23 + lodash: 4.18.1 log-symbols: 4.1.0 minimist: 1.2.8 ospath: 1.2.2 @@ -7071,7 +6678,7 @@ snapshots: proxy-from-env: 1.0.0 request-progress: 3.0.0 supports-color: 8.1.1 - systeminformation: 5.31.4 + systeminformation: 5.31.5 tmp: 0.2.5 tree-kill: 1.2.2 tslib: 1.14.1 @@ -7125,7 +6732,7 @@ snapshots: dagre@0.8.5: dependencies: graphlib: 2.1.8 - lodash: 4.17.23 + lodash: 4.18.1 dashdash@1.14.1: dependencies: @@ -7154,8 +6761,6 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 - date-fns@4.1.0: {} - dateformat@4.6.3: {} dayjs@1.11.20: {} @@ -7212,8 +6817,6 @@ snapshots: detect-libc@2.1.2: {} - detect-node-es@1.1.0: {} - devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -7222,8 +6825,6 @@ snapshots: diff@7.0.0: {} - diff@8.0.3: {} - doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -7234,7 +6835,7 @@ snapshots: dom-helpers@3.4.0: dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.2 dompurify@3.2.7: optionalDependencies: @@ -7245,7 +6846,7 @@ snapshots: no-case: 3.0.4 tslib: 2.8.1 - dotenv@17.3.1: {} + dotenv@17.4.2: {} dunder-proto@1.0.1: dependencies: @@ -7260,16 +6861,9 @@ snapshots: jsbn: 0.1.1 safer-buffer: 2.1.2 - editorconfig@1.0.7: - dependencies: - '@one-ini/wasm': 0.1.1 - commander: 10.0.1 - minimatch: 9.0.9 - semver: 7.7.4 - ee-first@1.1.1: {} - electron-to-chromium@1.5.313: {} + electron-to-chromium@1.5.344: {} emoji-regex@8.0.0: {} @@ -7281,10 +6875,10 @@ snapshots: dependencies: once: 1.4.0 - enhanced-resolve@5.20.0: + enhanced-resolve@5.21.0: dependencies: graceful-fs: 4.2.11 - tapable: 2.3.0 + tapable: 2.3.3 enquirer@2.4.1: dependencies: @@ -7293,18 +6887,18 @@ snapshots: entities@4.5.0: {} - entities@6.0.1: {} + entities@8.0.0: {} error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 - es-abstract@1.24.1: + es-abstract@1.24.2: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 data-view-buffer: 1.0.2 data-view-byte-length: 1.0.2 @@ -7323,7 +6917,7 @@ snapshots: has-property-descriptors: 1.0.2 has-proto: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.3 internal-slot: 1.1.0 is-array-buffer: 3.0.5 is-callable: 1.2.7 @@ -7341,7 +6935,7 @@ snapshots: object.assign: 4.1.7 own-keys: 1.0.1 regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.3 + safe-array-concat: 1.1.4 safe-push-apply: 1.0.0 safe-regex-test: 1.1.0 set-proto: 1.0.0 @@ -7360,12 +6954,12 @@ snapshots: es-errors@1.3.0: {} - es-iterator-helpers@1.3.0: + es-iterator-helpers@1.3.2: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-set-tostringtag: 2.1.0 function-bind: 1.1.2 @@ -7378,10 +6972,11 @@ snapshots: internal-slot: 1.1.0 iterator.prototype: 1.1.5 math-intrinsics: 1.1.0 - safe-array-concat: 1.1.3 es-module-lexer@1.7.0: {} + es-module-lexer@2.0.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -7391,11 +6986,11 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.3 es-shim-unscopables@1.1.0: dependencies: - hasown: 2.0.2 + hasown: 2.0.3 es-to-primitive@1.3.0: dependencies: @@ -7456,34 +7051,41 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.6.1)): + eslint-config-prettier@10.1.8(eslint@10.2.1(jiti@2.6.1)): dependencies: - eslint: 9.39.4(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) - eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1))(prettier@3.8.1): + eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@10.2.1(jiti@2.6.1)))(eslint@10.2.1(jiti@2.6.1))(prettier@3.8.3): dependencies: - eslint: 9.39.4(jiti@2.6.1) - prettier: 3.8.1 + eslint: 10.2.1(jiti@2.6.1) + prettier: 3.8.3 prettier-linter-helpers: 1.0.1 synckit: 0.11.12 optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@9.39.4(jiti@2.6.1)) + eslint-config-prettier: 10.1.8(eslint@10.2.1(jiti@2.6.1)) - eslint-plugin-react-hooks@5.2.0(eslint@9.39.4(jiti@2.6.1)): + eslint-plugin-react-hooks@7.1.1(eslint@10.2.1(jiti@2.6.1)): dependencies: - eslint: 9.39.4(jiti@2.6.1) + '@babel/core': 7.29.0 + '@babel/parser': 7.29.2 + eslint: 10.2.1(jiti@2.6.1) + hermes-parser: 0.25.1 + zod: 4.3.6 + zod-validation-error: 4.0.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color - eslint-plugin-react@7.37.5(eslint@9.39.4(jiti@2.6.1)): + eslint-plugin-react@7.37.5(eslint@10.2.1(jiti@2.6.1)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 array.prototype.flatmap: 1.3.3 array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 - es-iterator-helpers: 1.3.0 - eslint: 9.39.4(jiti@2.6.1) + es-iterator-helpers: 1.3.2 + eslint: 10.2.1(jiti@2.6.1) estraverse: 5.3.0 - hasown: 2.0.2 + hasown: 2.0.3 jsx-ast-utils: 3.3.5 minimatch: 3.1.5 object.entries: 1.1.9 @@ -7495,75 +7097,72 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-sonarjs@3.0.7(eslint@9.39.4(jiti@2.6.1)): + eslint-plugin-sonarjs@4.0.3(eslint@10.2.1(jiti@2.6.1)): dependencies: '@eslint-community/regexpp': 4.12.2 builtin-modules: 3.3.0 bytes: 3.1.2 - eslint: 9.39.4(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) functional-red-black-tree: 1.0.1 + globals: 17.5.0 jsx-ast-utils-x: 0.1.0 lodash.merge: 4.6.2 - minimatch: 10.1.2 + minimatch: 10.2.5 scslre: 0.3.0 semver: 7.7.4 + ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 - eslint-plugin-unicorn@62.0.0(eslint@9.39.4(jiti@2.6.1)): + eslint-plugin-unicorn@64.0.0(eslint@10.2.1(jiti@2.6.1)): dependencies: '@babel/helper-validator-identifier': 7.28.5 - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) - '@eslint/plugin-kit': 0.4.1 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) change-case: 5.4.4 ci-info: 4.4.0 clean-regexp: 1.0.0 - core-js-compat: 3.48.0 - eslint: 9.39.4(jiti@2.6.1) - esquery: 1.7.0 + core-js-compat: 3.49.0 + eslint: 10.2.1(jiti@2.6.1) find-up-simple: 1.0.1 - globals: 16.5.0 + globals: 17.5.0 indent-string: 5.0.0 is-builtin-module: 5.0.0 jsesc: 3.1.0 pluralize: 8.0.0 regexp-tree: 0.1.27 - regjsparser: 0.13.0 + regjsparser: 0.13.1 semver: 7.7.4 strip-indent: 4.1.1 - eslint-scope@8.4.0: + eslint-scope@9.1.2: dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.2.1: {} - eslint-visitor-keys@5.0.1: {} - eslint@9.39.4(jiti@2.6.1): + eslint@10.2.1(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.2 - '@eslint/config-helpers': 0.4.2 - '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.5 - '@eslint/js': 9.39.4 - '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.7 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.5.5 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.1 + '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 - ajv: 6.14.0 - chalk: 4.1.2 + ajv: 6.15.0 cross-spawn: 7.0.6 debug: 4.4.3(supports-color@8.1.1) escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -7574,8 +7173,7 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.5 + minimatch: 10.2.5 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: @@ -7583,11 +7181,11 @@ snapshots: transitivePeerDependencies: - supports-color - espree@10.4.0: + espree@11.2.0: dependencies: acorn: 8.16.0 acorn-jsx: 5.3.2(acorn@8.16.0) - eslint-visitor-keys: 4.2.1 + eslint-visitor-keys: 5.0.1 esquery@1.7.0: dependencies: @@ -7683,7 +7281,7 @@ snapshots: methods: 1.1.2 on-finished: 2.4.1 parseurl: 1.3.3 - path-to-regexp: 0.1.12 + path-to-regexp: 0.1.13 proxy-addr: 2.0.7 qs: 6.14.2 range-parser: 1.2.1 @@ -7726,9 +7324,9 @@ snapshots: dependencies: pend: 1.2.0 - fdir@6.5.0(picomatch@4.0.3): + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: - picomatch: 4.0.3 + picomatch: 4.0.4 fflate@0.8.2: {} @@ -7752,8 +7350,6 @@ snapshots: transitivePeerDependencies: - supports-color - find-root@1.1.0: {} - find-up-simple@1.0.1: {} find-up@5.0.0: @@ -7763,12 +7359,12 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.4.1 + flatted: 3.4.2 keyv: 4.5.4 flat@5.0.2: {} - flatted@3.4.1: {} + flatted@3.4.2: {} for-each@0.3.5: dependencies: @@ -7786,33 +7382,33 @@ snapshots: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.2 + hasown: 2.0.3 mime-types: 2.1.35 forwarded@0.2.0: {} - framer-motion@12.35.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + framer-motion@12.38.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: - motion-dom: 12.35.2 - motion-utils: 12.29.2 + motion-dom: 12.38.0 + motion-utils: 12.36.0 tslib: 2.8.1 optionalDependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) fresh@0.5.2: {} fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 - jsonfile: 6.2.0 + jsonfile: 6.2.1 universalify: 2.0.1 fs-extra@9.1.0: dependencies: at-least-node: 1.0.0 graceful-fs: 4.2.11 - jsonfile: 6.2.0 + jsonfile: 6.2.1 universalify: 2.0.1 fsevents@2.3.3: @@ -7824,11 +7420,11 @@ snapshots: function.prototype.name@1.1.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 functions-have-names: 1.2.3 - hasown: 2.0.2 + hasown: 2.0.3 is-callable: 1.2.7 functional-red-black-tree@1.0.1: {} @@ -7851,11 +7447,9 @@ snapshots: get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.3 math-intrinsics: 1.1.0 - get-nonce@1.0.1: {} - get-port@5.1.1: {} get-proto@1.0.1: @@ -7894,9 +7488,7 @@ snapshots: dependencies: ini: 2.0.0 - globals@14.0.0: {} - - globals@16.5.0: {} + globals@17.5.0: {} globalthis@1.0.4: dependencies: @@ -7905,13 +7497,17 @@ snapshots: globrex@0.1.2: {} + goober@2.1.18(csstype@3.2.3): + dependencies: + csstype: 3.2.3 + gopd@1.2.0: {} graceful-fs@4.2.11: {} graphlib@2.1.8: dependencies: - lodash: 4.17.23 + lodash: 4.18.1 has-bigints@1.1.0: {} @@ -7936,7 +7532,7 @@ snapshots: is-stream: 2.0.1 type-fest: 0.8.1 - hasown@2.0.2: + hasown@2.0.3: dependencies: function-bind: 1.1.2 @@ -7987,9 +7583,11 @@ snapshots: he@1.2.0: {} - hoist-non-react-statics@3.3.2: + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: dependencies: - react-is: 16.13.1 + hermes-estree: 0.25.1 html-encoding-sniffer@6.0.0: dependencies: @@ -7997,10 +7595,6 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' - html@1.0.0: - dependencies: - concat-stream: 1.6.2 - http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -8054,8 +7648,6 @@ snapshots: inherits@2.0.4: {} - ini@1.3.8: {} - ini@2.0.0: {} inline-style-parser@0.2.7: {} @@ -8063,7 +7655,7 @@ snapshots: internal-slot@1.1.0: dependencies: es-errors: 1.3.0 - hasown: 2.0.2 + hasown: 2.0.3 side-channel: 1.1.0 ipaddr.js@1.9.1: {} @@ -8077,7 +7669,7 @@ snapshots: is-array-buffer@3.0.5: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 @@ -8102,13 +7694,13 @@ snapshots: is-builtin-module@5.0.0: dependencies: - builtin-modules: 5.0.0 + builtin-modules: 5.1.0 is-callable@1.2.7: {} is-core-module@2.16.1: dependencies: - hasown: 2.0.2 + hasown: 2.0.3 is-data-view@1.0.2: dependencies: @@ -8172,7 +7764,7 @@ snapshots: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.3 is-set@2.0.3: {} @@ -8212,11 +7804,9 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 - isarray@1.0.0: {} - isarray@2.0.5: {} - isbot@5.1.36: {} + isbot@5.1.39: {} isexe@2.0.0: {} @@ -8239,16 +7829,6 @@ snapshots: jiti@2.6.1: {} - js-beautify@1.15.4: - dependencies: - config-chain: 1.1.13 - editorconfig: 1.0.7 - glob: 10.5.0 - js-cookie: 3.0.5 - nopt: 7.2.1 - - js-cookie@3.0.5: {} - js-tokens@4.0.0: {} js-yaml@4.1.1: @@ -8269,15 +7849,15 @@ snapshots: http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 - parse5: 8.0.0 + parse5: 8.0.1 saxes: 6.0.0 symbol-tree: 3.2.4 - tough-cookie: 6.0.0 + tough-cookie: 6.0.1 w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 4.0.0 whatwg-url: 15.1.0 - ws: 8.19.0 + ws: 8.20.0 xml-name-validator: 5.0.0 transitivePeerDependencies: - '@noble/hashes' @@ -8303,7 +7883,7 @@ snapshots: json5@2.2.3: {} - jsonfile@6.2.0: + jsonfile@6.2.1: dependencies: universalify: 2.0.1 optionalDependencies: @@ -8329,59 +7909,64 @@ snapshots: dependencies: json-buffer: 3.0.1 + launch-editor@2.13.2: + dependencies: + picocolors: 1.1.1 + shell-quote: 1.8.3 + levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 - lightningcss-android-arm64@1.31.1: + lightningcss-android-arm64@1.32.0: optional: true - lightningcss-darwin-arm64@1.31.1: + lightningcss-darwin-arm64@1.32.0: optional: true - lightningcss-darwin-x64@1.31.1: + lightningcss-darwin-x64@1.32.0: optional: true - lightningcss-freebsd-x64@1.31.1: + lightningcss-freebsd-x64@1.32.0: optional: true - lightningcss-linux-arm-gnueabihf@1.31.1: + lightningcss-linux-arm-gnueabihf@1.32.0: optional: true - lightningcss-linux-arm64-gnu@1.31.1: + lightningcss-linux-arm64-gnu@1.32.0: optional: true - lightningcss-linux-arm64-musl@1.31.1: + lightningcss-linux-arm64-musl@1.32.0: optional: true - lightningcss-linux-x64-gnu@1.31.1: + lightningcss-linux-x64-gnu@1.32.0: optional: true - lightningcss-linux-x64-musl@1.31.1: + lightningcss-linux-x64-musl@1.32.0: optional: true - lightningcss-win32-arm64-msvc@1.31.1: + lightningcss-win32-arm64-msvc@1.32.0: optional: true - lightningcss-win32-x64-msvc@1.31.1: + lightningcss-win32-x64-msvc@1.32.0: optional: true - lightningcss@1.31.1: + lightningcss@1.32.0: dependencies: detect-libc: 2.1.2 optionalDependencies: - lightningcss-android-arm64: 1.31.1 - lightningcss-darwin-arm64: 1.31.1 - lightningcss-darwin-x64: 1.31.1 - lightningcss-freebsd-x64: 1.31.1 - lightningcss-linux-arm-gnueabihf: 1.31.1 - lightningcss-linux-arm64-gnu: 1.31.1 - lightningcss-linux-arm64-musl: 1.31.1 - lightningcss-linux-x64-gnu: 1.31.1 - lightningcss-linux-x64-musl: 1.31.1 - lightningcss-win32-arm64-msvc: 1.31.1 - lightningcss-win32-x64-msvc: 1.31.1 + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 lines-and-columns@1.2.4: {} @@ -8418,7 +8003,7 @@ snapshots: lodash.once@4.1.1: {} - lodash@4.17.23: {} + lodash@4.18.1: {} log-symbols@4.1.0: dependencies: @@ -8444,7 +8029,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.2.6: {} + lru-cache@11.3.5: {} lru-cache@5.1.1: dependencies: @@ -8631,8 +8216,6 @@ snapshots: media-typer@0.3.0: {} - memoize-one@6.0.0: {} - merge-descriptors@1.0.3: {} merge-stream@2.0.0: {} @@ -8883,7 +8466,7 @@ snapshots: micromark@4.0.2: dependencies: - '@types/debug': 4.1.12 + '@types/debug': 4.1.13 debug: 4.4.3(supports-color@8.1.1) decode-named-character-reference: 1.3.0 devlop: 1.1.0 @@ -8917,21 +8500,17 @@ snapshots: min-indent@1.0.1: {} - minimatch@10.1.2: + minimatch@10.2.5: dependencies: - '@isaacs/brace-expansion': 5.0.1 - - minimatch@10.2.4: - dependencies: - brace-expansion: 5.0.4 + brace-expansion: 5.0.5 minimatch@3.1.5: dependencies: - brace-expansion: 1.1.12 + brace-expansion: 1.1.14 minimatch@9.0.9: dependencies: - brace-expansion: 2.0.2 + brace-expansion: 2.1.0 minimist@1.2.8: {} @@ -8996,10 +8575,10 @@ snapshots: monaco-xsd-code-completion@1.0.0: dependencies: - '@xmldom/xmldom': 0.8.11 - prettier: 3.8.1 + '@xmldom/xmldom': 0.8.13 + prettier: 3.8.3 ts-debounce: 4.0.0 - turndown: 7.2.2 + turndown: 7.2.4 morgan@1.10.1: dependencies: @@ -9011,11 +8590,11 @@ snapshots: transitivePeerDependencies: - supports-color - motion-dom@12.35.2: + motion-dom@12.38.0: dependencies: - motion-utils: 12.29.2 + motion-utils: 12.36.0 - motion-utils@12.29.2: {} + motion-utils@12.36.0: {} mrmime@2.0.1: {} @@ -9043,11 +8622,7 @@ snapshots: object.entries: 1.1.9 semver: 6.3.1 - node-releases@2.0.36: {} - - nopt@7.2.1: - dependencies: - abbrev: 2.0.0 + node-releases@2.0.38: {} npm-run-path@4.0.1: dependencies: @@ -9061,7 +8636,7 @@ snapshots: object.assign@4.1.7: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -9070,21 +8645,21 @@ snapshots: object.entries@1.1.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 object.fromentries@2.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-object-atoms: 1.1.1 object.values@1.2.1: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -9165,9 +8740,9 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - parse5@8.0.0: + parse5@8.0.1: dependencies: - entities: 6.0.1 + entities: 8.0.0 parseurl@1.3.3: {} @@ -9182,7 +8757,7 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.3 - path-to-regexp@0.1.12: {} + path-to-regexp@0.1.13: {} path-type@4.0.0: {} @@ -9196,7 +8771,7 @@ snapshots: picocolors@1.1.1: {} - picomatch@4.0.3: {} + picomatch@4.0.4: {} pify@2.3.0: {} @@ -9210,7 +8785,7 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss@8.5.8: + postcss@8.5.10: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -9222,11 +8797,11 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier-plugin-tailwindcss@0.6.14(prettier@3.8.1): + prettier-plugin-tailwindcss@0.6.14(prettier@3.8.3): dependencies: - prettier: 3.8.1 + prettier: 3.8.3 - prettier@3.8.1: {} + prettier@3.8.3: {} pretty-bytes@5.6.0: {} @@ -9236,8 +8811,6 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 - process-nextick-args@2.0.1: {} - process@0.11.10: {} prop-types@15.8.1: @@ -9248,8 +8821,6 @@ snapshots: property-information@7.1.0: {} - proto-list@1.2.4: {} - proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -9281,13 +8852,13 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - react-complex-tree@2.6.1(react@19.2.4): + react-complex-tree@2.6.1(react@19.2.5): dependencies: - react: 19.2.4 + react: 19.2.5 - react-d3-tree@3.6.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + react-d3-tree@3.6.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: - '@bkrem/react-transition-group': 1.3.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@bkrem/react-transition-group': 1.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@types/d3-hierarchy': 1.1.11 clone: 2.1.2 d3-hierarchy: 1.1.9 @@ -9295,33 +8866,19 @@ snapshots: d3-shape: 1.3.7 d3-zoom: 3.0.0 dequal: 2.0.3 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) uuid: 8.3.2 - react-diff-viewer-continued@4.2.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): - dependencies: - '@emotion/css': 11.13.5 - '@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.4) - classnames: 2.5.1 - diff: 8.0.3 - js-yaml: 4.1.1 - memoize-one: 6.0.0 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - transitivePeerDependencies: - - '@types/react' - - supports-color - - react-dom@19.2.4(react@19.2.4): + react-dom@19.2.5(react@19.2.5): dependencies: - react: 19.2.4 + react: 19.2.5 scheduler: 0.27.0 - react-hotkeys-hook@4.6.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + react-hotkeys-hook@5.2.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) react-is@16.13.1: {} @@ -9333,92 +8890,59 @@ snapshots: react-refresh@0.18.0: {} - react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.4): - dependencies: - react: 19.2.4 - react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.4) - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.2.14 - - react-remove-scroll@2.7.2(@types/react@19.2.14)(react@19.2.4): - dependencies: - react: 19.2.4 - react-remove-scroll-bar: 2.3.8(@types/react@19.2.14)(react@19.2.4) - react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.4) - tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.2.14)(react@19.2.4) - use-sidecar: 1.1.3(@types/react@19.2.14)(react@19.2.4) - optionalDependencies: - '@types/react': 19.2.14 - - react-router-devtools@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(vite@6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1)): + react-router-devtools@6.2.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.5(react@19.2.5))(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(solid-js@1.9.12)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)): dependencies: '@babel/core': 7.29.0 '@babel/generator': 7.29.1 - '@babel/parser': 7.29.0 + '@babel/parser': 7.29.2 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 - '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-select': 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - beautify: 0.0.8 - bippy: 0.3.34(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@tanstack/devtools-client': 0.0.5 + '@tanstack/devtools-event-client': 0.4.3 + '@tanstack/devtools-vite': 0.4.1(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)) + '@tanstack/react-devtools': 0.9.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(solid-js@1.9.12) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) chalk: 5.6.2 clsx: 2.1.1 - date-fns: 4.1.0 - framer-motion: 12.35.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 - react-d3-tree: 3.6.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react-diff-viewer-continued: 4.2.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react-dom: 19.2.4(react@19.2.4) - react-hotkeys-hook: 4.6.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react-router: 7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react-tooltip: 5.30.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - vite: 6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1) + framer-motion: 12.38.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + goober: 2.1.18(csstype@3.2.3) + react: 19.2.5 + react-d3-tree: 3.6.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react-dom: 19.2.5(react@19.2.5) + react-hotkeys-hook: 5.2.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react-router: 7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react-tooltip: 5.30.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + vite: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0) optionalDependencies: - '@biomejs/cli-darwin-arm64': 1.9.4 - '@rollup/rollup-darwin-arm64': 4.59.0 - '@rollup/rollup-linux-x64-gnu': 4.59.0 + '@biomejs/cli-darwin-arm64': 2.4.13 + '@rollup/rollup-darwin-arm64': 4.60.2 + '@rollup/rollup-linux-x64-gnu': 4.60.2 transitivePeerDependencies: - '@emotion/is-prop-valid' - - '@types/react' - - '@types/react-dom' + - bufferutil + - csstype + - solid-js - supports-color + - utf-8-validate - react-router@7.13.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: cookie: 1.1.1 - react: 19.2.4 + react: 19.2.5 set-cookie-parser: 2.7.2 optionalDependencies: - react-dom: 19.2.4(react@19.2.4) - - react-style-singleton@2.2.3(@types/react@19.2.14)(react@19.2.4): - dependencies: - get-nonce: 1.0.1 - react: 19.2.4 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.2.14 + react-dom: 19.2.5(react@19.2.5) - react-tooltip@5.30.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + react-tooltip@5.30.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: '@floating-ui/dom': 1.7.6 classnames: 2.5.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - - react@19.2.4: {} + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) - readable-stream@2.3.8: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 + react@19.2.5: {} readdirp@4.1.2: {} @@ -9462,9 +8986,9 @@ snapshots: reflect.getprototypeof@1.0.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 @@ -9482,14 +9006,14 @@ snapshots: regexp.prototype.flags@1.5.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-errors: 1.3.0 get-proto: 1.0.1 gopd: 1.2.0 set-function-name: 2.0.2 - regjsparser@0.13.0: + regjsparser@0.13.1: dependencies: jsesc: 3.1.0 @@ -9552,11 +9076,13 @@ snapshots: resolve-from@4.0.0: {} - resolve@1.22.11: + resolve@1.22.12: dependencies: + es-errors: 1.3.0 is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + optional: true resolve@2.0.0-next.6: dependencies: @@ -9574,44 +9100,44 @@ snapshots: rfdc@1.4.1: {} - rollup@4.59.0: + rollup@4.60.2: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.59.0 - '@rollup/rollup-android-arm64': 4.59.0 - '@rollup/rollup-darwin-arm64': 4.59.0 - '@rollup/rollup-darwin-x64': 4.59.0 - '@rollup/rollup-freebsd-arm64': 4.59.0 - '@rollup/rollup-freebsd-x64': 4.59.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 - '@rollup/rollup-linux-arm-musleabihf': 4.59.0 - '@rollup/rollup-linux-arm64-gnu': 4.59.0 - '@rollup/rollup-linux-arm64-musl': 4.59.0 - '@rollup/rollup-linux-loong64-gnu': 4.59.0 - '@rollup/rollup-linux-loong64-musl': 4.59.0 - '@rollup/rollup-linux-ppc64-gnu': 4.59.0 - '@rollup/rollup-linux-ppc64-musl': 4.59.0 - '@rollup/rollup-linux-riscv64-gnu': 4.59.0 - '@rollup/rollup-linux-riscv64-musl': 4.59.0 - '@rollup/rollup-linux-s390x-gnu': 4.59.0 - '@rollup/rollup-linux-x64-gnu': 4.59.0 - '@rollup/rollup-linux-x64-musl': 4.59.0 - '@rollup/rollup-openbsd-x64': 4.59.0 - '@rollup/rollup-openharmony-arm64': 4.59.0 - '@rollup/rollup-win32-arm64-msvc': 4.59.0 - '@rollup/rollup-win32-ia32-msvc': 4.59.0 - '@rollup/rollup-win32-x64-gnu': 4.59.0 - '@rollup/rollup-win32-x64-msvc': 4.59.0 + '@rollup/rollup-android-arm-eabi': 4.60.2 + '@rollup/rollup-android-arm64': 4.60.2 + '@rollup/rollup-darwin-arm64': 4.60.2 + '@rollup/rollup-darwin-x64': 4.60.2 + '@rollup/rollup-freebsd-arm64': 4.60.2 + '@rollup/rollup-freebsd-x64': 4.60.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.2 + '@rollup/rollup-linux-arm-musleabihf': 4.60.2 + '@rollup/rollup-linux-arm64-gnu': 4.60.2 + '@rollup/rollup-linux-arm64-musl': 4.60.2 + '@rollup/rollup-linux-loong64-gnu': 4.60.2 + '@rollup/rollup-linux-loong64-musl': 4.60.2 + '@rollup/rollup-linux-ppc64-gnu': 4.60.2 + '@rollup/rollup-linux-ppc64-musl': 4.60.2 + '@rollup/rollup-linux-riscv64-gnu': 4.60.2 + '@rollup/rollup-linux-riscv64-musl': 4.60.2 + '@rollup/rollup-linux-s390x-gnu': 4.60.2 + '@rollup/rollup-linux-x64-gnu': 4.60.2 + '@rollup/rollup-linux-x64-musl': 4.60.2 + '@rollup/rollup-openbsd-x64': 4.60.2 + '@rollup/rollup-openharmony-arm64': 4.60.2 + '@rollup/rollup-win32-arm64-msvc': 4.60.2 + '@rollup/rollup-win32-ia32-msvc': 4.60.2 + '@rollup/rollup-win32-x64-gnu': 4.60.2 + '@rollup/rollup-win32-x64-msvc': 4.60.2 fsevents: 2.3.3 rxjs@7.8.2: dependencies: tslib: 2.8.1 - safe-array-concat@1.1.3: + safe-array-concat@1.1.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 has-symbols: 1.1.0 @@ -9674,6 +9200,12 @@ snapshots: dependencies: randombytes: 2.1.0 + seroval-plugins@1.5.2(seroval@1.5.2): + dependencies: + seroval: 1.5.2 + + seroval@1.5.2: {} + serve-static@1.16.3: dependencies: encodeurl: 2.0.0 @@ -9715,7 +9247,9 @@ snapshots: shebang-regex@3.0.0: {} - side-channel-list@1.0.0: + shell-quote@1.8.3: {} + + side-channel-list@1.0.1: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 @@ -9739,7 +9273,7 @@ snapshots: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 - side-channel-list: 1.0.0 + side-channel-list: 1.0.1 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 @@ -9772,6 +9306,12 @@ snapshots: dot-case: 3.0.4 tslib: 2.8.1 + solid-js@1.9.12: + dependencies: + csstype: 3.2.3 + seroval: 1.5.2 + seroval-plugins: 1.5.2(seroval@1.5.2) + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -9779,8 +9319,6 @@ snapshots: buffer-from: 1.1.2 source-map: 0.6.1 - source-map@0.5.7: {} - source-map@0.6.1: {} source-map@0.7.6: {} @@ -9805,7 +9343,7 @@ snapshots: statuses@2.0.2: {} - std-env@3.10.0: {} + std-env@4.1.0: {} stop-iteration-iterator@1.1.0: dependencies: @@ -9826,10 +9364,10 @@ snapshots: string.prototype.matchall@4.0.12: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 @@ -9843,35 +9381,31 @@ snapshots: string.prototype.repeat@1.0.0: dependencies: define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 string.prototype.trim@1.2.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 string.prototype.trimend@1.0.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-object-atoms: 1.1.1 - string_decoder@1.1.1: - dependencies: - safe-buffer: 5.1.2 - stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 @@ -9903,8 +9437,6 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - stylis@4.2.0: {} - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -9923,13 +9455,13 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 - systeminformation@5.31.4: {} + systeminformation@5.31.5: {} tailwind-merge@3.5.0: {} - tailwindcss@4.2.1: {} + tailwindcss@4.2.4: {} - tapable@2.3.0: {} + tapable@2.3.3: {} tcomb-validation@3.4.1: dependencies: @@ -9943,26 +9475,26 @@ snapshots: tinybench@2.9.0: {} - tinyexec@1.0.2: {} + tinyexec@1.1.1: {} - tinyglobby@0.2.15: + tinyglobby@0.2.16: dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 tinyrainbow@3.1.0: {} tldts-core@6.1.86: {} - tldts-core@7.0.25: {} + tldts-core@7.0.28: {} tldts@6.1.86: dependencies: tldts-core: 6.1.86 - tldts@7.0.25: + tldts@7.0.28: dependencies: - tldts-core: 7.0.25 + tldts-core: 7.0.28 tmp@0.2.5: {} @@ -9974,9 +9506,9 @@ snapshots: dependencies: tldts: 6.1.86 - tough-cookie@6.0.0: + tough-cookie@6.0.1: dependencies: - tldts: 7.0.25 + tldts: 7.0.28 tr46@6.0.0: dependencies: @@ -9988,7 +9520,7 @@ snapshots: trough@2.2.0: {} - ts-api-utils@2.4.0(typescript@5.9.3): + ts-api-utils@2.5.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -10006,7 +9538,7 @@ snapshots: dependencies: safe-buffer: 5.2.1 - turndown@7.2.2: + turndown@7.2.4: dependencies: '@mixmark-io/domino': 2.2.0 @@ -10033,7 +9565,7 @@ snapshots: typed-array-byte-length@1.0.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 @@ -10042,7 +9574,7 @@ snapshots: typed-array-byte-offset@1.0.4: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 @@ -10051,22 +9583,20 @@ snapshots: typed-array-length@1.0.7: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 is-typed-array: 1.1.15 possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typedarray@0.0.6: {} - - typescript-eslint@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.57.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.39.4(jiti@2.6.1) + '@typescript-eslint/eslint-plugin': 8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3))(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.59.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) + eslint: 10.2.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -10082,7 +9612,7 @@ snapshots: undici-types@6.21.0: {} - undici-types@7.18.2: + undici-types@7.19.2: optional: true unified@11.0.5: @@ -10128,9 +9658,9 @@ snapshots: untildify@4.0.0: {} - update-browserslist-db@1.2.3(browserslist@4.28.1): + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: - browserslist: 4.28.1 + browserslist: 4.28.2 escalade: 3.2.0 picocolors: 1.1.1 @@ -10138,37 +9668,20 @@ snapshots: dependencies: punycode: 2.3.1 - use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.4): - dependencies: - react: 19.2.4 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.2.14 - - use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.4): - dependencies: - detect-node-es: 1.1.0 - react: 19.2.4 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.2.14 - - use-sync-external-store@1.6.0(react@19.2.4): + use-sync-external-store@1.6.0(react@19.2.5): dependencies: - react: 19.2.4 + react: 19.2.5 - usehooks-ts@3.1.1(react@19.2.4): + usehooks-ts@3.1.1(react@19.2.5): dependencies: lodash.debounce: 4.0.8 - react: 19.2.4 - - util-deprecate@1.0.2: {} + react: 19.2.5 utils-merge@1.0.1: {} uuid@8.3.2: {} - valibot@1.2.0(typescript@5.9.3): + valibot@1.3.1(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 @@ -10190,13 +9703,13 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-node@3.2.4(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1): + vite-node@3.2.4(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0): dependencies: cac: 6.7.14 debug: 4.4.3(supports-color@8.1.1) es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1) + vite: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0) transitivePeerDependencies: - '@types/node' - jiti @@ -10211,80 +9724,70 @@ snapshots: - tsx - yaml - vite-plugin-svgr@4.5.0(rollup@4.59.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1)): + vite-plugin-svgr@4.5.0(rollup@4.60.2)(typescript@5.9.3)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)): dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.59.0) + '@rollup/pluginutils': 5.3.0(rollup@4.60.2) '@svgr/core': 8.1.0(typescript@5.9.3) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3)) - vite: 6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1) + vite: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0) transitivePeerDependencies: - rollup - supports-color - typescript - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)): dependencies: debug: 4.4.3(supports-color@8.1.1) globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1) + vite: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0) transitivePeerDependencies: - supports-color - typescript - vite@6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1): + vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0): dependencies: esbuild: 0.25.12 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.8 - rollup: 4.59.0 - tinyglobby: 0.2.15 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.10 + rollup: 4.60.2 + tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 20.19.37 + '@types/node': 20.19.39 fsevents: 2.3.3 jiti: 2.6.1 - lightningcss: 1.31.1 - - vitest@4.0.18(@types/node@20.19.37)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1): - dependencies: - '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1)) - '@vitest/pretty-format': 4.0.18 - '@vitest/runner': 4.0.18 - '@vitest/snapshot': 4.0.18 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 - es-module-lexer: 1.7.0 + lightningcss: 1.32.0 + + vitest@4.1.5(@types/node@20.19.39)(@vitest/ui@4.1.5)(jsdom@27.4.0)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)): + dependencies: + '@vitest/expect': 4.1.5 + '@vitest/mocker': 4.1.5(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)) + '@vitest/pretty-format': 4.1.5 + '@vitest/runner': 4.1.5 + '@vitest/snapshot': 4.1.5 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + es-module-lexer: 2.0.0 expect-type: 1.3.0 magic-string: 0.30.21 obug: 2.1.1 pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.10.0 + picomatch: 4.0.4 + std-env: 4.1.0 tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 + tinyexec: 1.1.1 + tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 6.4.1(@types/node@20.19.37)(jiti@2.6.1)(lightningcss@1.31.1) + vite: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 20.19.37 - '@vitest/ui': 4.0.18(vitest@4.0.18) + '@types/node': 20.19.39 + '@vitest/ui': 4.1.5(vitest@4.1.5) jsdom: 27.4.0 transitivePeerDependencies: - - jiti - - less - - lightningcss - msw - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml w3c-xmlserializer@5.0.0: dependencies: @@ -10339,7 +9842,7 @@ snapshots: which-typed-array@1.1.20: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 for-each: 0.3.5 get-proto: 1.0.1 @@ -10379,21 +9882,22 @@ snapshots: wrappy@1.0.2: {} - ws@8.19.0: {} + ws@8.20.0: {} xml-name-validator@5.0.0: {} xmlchars@2.2.0: {} - xmllint-wasm@5.1.0(@types/node@20.19.37): + xmllint-wasm@5.2.0(@types/node@20.19.39): dependencies: - '@types/node': 20.19.37 + '@types/node': 20.19.39 y18n@5.0.8: {} yallist@3.1.1: {} - yaml@1.10.2: {} + yaml@1.10.3: + optional: true yargs-parser@21.1.1: {} @@ -10421,17 +9925,23 @@ snapshots: yocto-queue@0.1.0: {} - zustand@4.5.7(@types/react@19.2.14)(react@19.2.4): + zod-validation-error@4.0.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} + + zustand@4.5.7(@types/react@19.2.14)(react@19.2.5): dependencies: - use-sync-external-store: 1.6.0(react@19.2.4) + use-sync-external-store: 1.6.0(react@19.2.5) optionalDependencies: '@types/react': 19.2.14 - react: 19.2.4 + react: 19.2.5 - zustand@5.0.11(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)): + zustand@5.0.12(@types/react@19.2.14)(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)): optionalDependencies: '@types/react': 19.2.14 - react: 19.2.4 - use-sync-external-store: 1.6.0(react@19.2.4) + react: 19.2.5 + use-sync-external-store: 1.6.0(react@19.2.5) zwitch@2.0.4: {} diff --git a/src/main/frontend/cypress/package.json b/src/main/frontend/cypress/package.json index b9e475f9..0f09df2e 100644 --- a/src/main/frontend/cypress/package.json +++ b/src/main/frontend/cypress/package.json @@ -8,7 +8,7 @@ "test:open": "cypress open" }, "devDependencies": { - "cypress": "^15.11.0", + "cypress": "^15.14.1", "cypress-multi-reporters": "^2.0.5", "mocha": "^11.7.5", "mochawesome": "^7.1.4" diff --git a/src/main/frontend/package.json b/src/main/frontend/package.json index 65b9eefd..4baa6258 100644 --- a/src/main/frontend/package.json +++ b/src/main/frontend/package.json @@ -17,61 +17,61 @@ "@frankframework/doc-library-core": "^1.0.3", "@frankframework/doc-library-react": "^1.0.3", "@mdx-js/rollup": "^3.1.1", - "@monaco-editor/react": "4.7.0-rc.0", - "@react-router/node": "^7.13.1", - "@react-router/serve": "^7.13.1", - "@xyflow/react": "^12.10.1", + "@monaco-editor/react": "4.7.0", + "@react-router/node": "^7.14.2", + "@react-router/serve": "^7.14.2", + "@xyflow/react": "^12.10.2", "allotment": "^1.20.5", "clsx": "^2.1.1", "dagre": "^0.8.5", - "dotenv": "^17.3.1", - "isbot": "^5.1.36", + "dotenv": "^17.4.2", + "isbot": "^5.1.39", "monaco-xsd-code-completion": "^1.0.0", - "react": "^19.2.4", + "react": "^19.2.5", "react-complex-tree": "^2.6.1", - "react-dom": "^19.2.4", - "react-router": "^7.13.1", + "react-dom": "^19.2.5", + "react-router": "^7.14.2", "remark-gfm": "^4.0.1", "sax-ts": "^1.2.13", "tailwind-merge": "^3.5.0", - "xmllint-wasm": "^5.1.0", - "zustand": "^5.0.11" + "xmllint-wasm": "^5.2.0", + "zustand": "^5.0.12" }, "devDependencies": { - "@eslint/js": "^9.39.4", + "@eslint/js": "^10.0.1", "@mdx-js/react": "^3.1.1", - "@react-router/dev": "^7.13.1", - "@tailwindcss/vite": "^4.2.1", + "@prettier/plugin-xml": "^3.4.2", + "@react-router/dev": "^7.14.2", + "@tailwindcss/vite": "^4.2.4", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", "@types/mdx": "^2.0.13", - "@types/node": "^20.19.37", + "@types/node": "^20.19.39", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "@typescript-eslint/eslint-plugin": "^8.57.0", - "@typescript-eslint/parser": "^8.57.0", - "@vitejs/plugin-react": "^5.1.4", - "@vitest/ui": "^4.0.18", - "eslint": "^9.39.4", + "@typescript-eslint/eslint-plugin": "^8.59.0", + "@typescript-eslint/parser": "^8.59.0", + "@vitejs/plugin-react": "^5.2.0", + "@vitest/ui": "^4.1.5", + "eslint": "^10.2.1", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-sonarjs": "^3.0.7", - "eslint-plugin-unicorn": "^62.0.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-sonarjs": "^4.0.3", + "eslint-plugin-unicorn": "^64.0.0", "jsdom": "^27.4.0", - "@prettier/plugin-xml": "^3.4.1", - "prettier": "^3.8.1", + "prettier": "^3.8.3", "prettier-plugin-tailwindcss": "^0.6.14", - "react-router-devtools": "^1.1.10", - "tailwindcss": "^4.2.1", + "react-router-devtools": "^6.2.0", + "tailwindcss": "^4.2.4", "typescript": "^5.9.3", - "typescript-eslint": "^8.57.0", - "vite": "^6.4.1", + "typescript-eslint": "^8.59.0", + "vite": "^6.4.2", "vite-plugin-svgr": "^4.5.0", "vite-tsconfig-paths": "^5.1.4", - "vitest": "^4.0.18" + "vitest": "^4.1.5" }, "packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be" } From 70f1454acce9950ddc682cf22504a75d4f88c4d1 Mon Sep 17 00:00:00 2001 From: Vivy <4380412+Matthbo@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:28:38 +0200 Subject: [PATCH 20/20] Fix linting dependency --- pnpm-lock.yaml | 235 +++++++++++++++++++-------------- src/main/frontend/package.json | 2 +- 2 files changed, 135 insertions(+), 102 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a87c0117..7cc18ed6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,7 +80,7 @@ importers: devDependencies: '@eslint/js': specifier: ^10.0.1 - version: 10.0.1(eslint@10.2.1(jiti@2.6.1)) + version: 10.0.1(eslint@9.39.4(jiti@2.6.1)) '@mdx-js/react': specifier: ^3.1.1 version: 3.1.1(@types/react@19.2.14)(react@19.2.5) @@ -116,10 +116,10 @@ importers: version: 19.2.3(@types/react@19.2.14) '@typescript-eslint/eslint-plugin': specifier: ^8.59.0 - version: 8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3))(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) + version: 8.59.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/parser': specifier: ^8.59.0 - version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) + version: 8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) '@vitejs/plugin-react': specifier: ^5.2.0 version: 5.2.0(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)) @@ -127,26 +127,26 @@ importers: specifier: ^4.1.5 version: 4.1.5(vitest@4.1.5) eslint: - specifier: ^10.2.1 - version: 10.2.1(jiti@2.6.1) + specifier: ^9.39.4 + version: 9.39.4(jiti@2.6.1) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.2.1(jiti@2.6.1)) + version: 10.1.8(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-prettier: specifier: ^5.5.5 - version: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.2.1(jiti@2.6.1)))(eslint@10.2.1(jiti@2.6.1))(prettier@3.8.3) + version: 5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1))(prettier@3.8.3) eslint-plugin-react: specifier: ^7.37.5 - version: 7.37.5(eslint@10.2.1(jiti@2.6.1)) + version: 7.37.5(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-react-hooks: specifier: ^7.1.1 - version: 7.1.1(eslint@10.2.1(jiti@2.6.1)) + version: 7.1.1(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-sonarjs: specifier: ^4.0.3 - version: 4.0.3(eslint@10.2.1(jiti@2.6.1)) + version: 4.0.3(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-unicorn: specifier: ^64.0.0 - version: 64.0.0(eslint@10.2.1(jiti@2.6.1)) + version: 64.0.0(eslint@9.39.4(jiti@2.6.1)) jsdom: specifier: ^27.4.0 version: 27.4.0 @@ -167,7 +167,7 @@ importers: version: 5.9.3 typescript-eslint: specifier: ^8.59.0 - version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) + version: 8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) vite: specifier: ^6.4.2 version: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0) @@ -586,17 +586,21 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.23.5': - resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-helpers@0.5.5': - resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@1.2.1': - resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/js@10.0.1': resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} @@ -607,13 +611,17 @@ packages: eslint: optional: true - '@eslint/object-schema@3.0.5': - resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.7.1': - resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@exodus/bytes@1.15.0': resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} @@ -1390,9 +1398,6 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - '@types/esrecurse@4.3.1': - resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} - '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} @@ -2375,21 +2380,25 @@ packages: peerDependencies: eslint: '>=9.38.0' - eslint-scope@9.1.2: - resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@5.0.1: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.2.1: - resolution: {integrity: sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: jiti: '*' @@ -2397,9 +2406,9 @@ packages: jiti: optional: true - espree@11.2.0: - resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esquery@1.7.0: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} @@ -2658,6 +2667,10 @@ packages: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + globals@17.5.0: resolution: {integrity: sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==} engines: {node: '>=18'} @@ -5134,38 +5147,54 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.2.1(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))': dependencies: - eslint: 10.2.1(jiti@2.6.1) + eslint: 9.39.4(jiti@2.6.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.23.5': + '@eslint/config-array@0.21.2': dependencies: - '@eslint/object-schema': 3.0.5 + '@eslint/object-schema': 2.1.7 debug: 4.4.3(supports-color@8.1.1) - minimatch: 10.2.5 + minimatch: 3.1.5 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.5.5': + '@eslint/config-helpers@0.4.2': dependencies: - '@eslint/core': 1.2.1 + '@eslint/core': 0.17.0 - '@eslint/core@1.2.1': + '@eslint/core@0.17.0': dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@10.0.1(eslint@10.2.1(jiti@2.6.1))': + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.15.0 + debug: 4.4.3(supports-color@8.1.1) + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@10.0.1(eslint@9.39.4(jiti@2.6.1))': optionalDependencies: - eslint: 10.2.1(jiti@2.6.1) + eslint: 9.39.4(jiti@2.6.1) + + '@eslint/js@9.39.4': {} - '@eslint/object-schema@3.0.5': {} + '@eslint/object-schema@2.1.7': {} - '@eslint/plugin-kit@0.7.1': + '@eslint/plugin-kit@0.4.1': dependencies: - '@eslint/core': 1.2.1 + '@eslint/core': 0.17.0 levn: 0.4.1 '@exodus/bytes@1.15.0': {} @@ -5936,8 +5965,6 @@ snapshots: '@types/deep-eql@4.0.2': {} - '@types/esrecurse@4.3.1': {} - '@types/estree-jsx@1.0.5': dependencies: '@types/estree': 1.0.8 @@ -5996,15 +6023,15 @@ snapshots: '@types/node': 25.6.0 optional: true - '@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3))(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.59.0 - '@typescript-eslint/type-utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.59.0 - eslint: 10.2.1(jiti@2.6.1) + eslint: 9.39.4(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -6012,14 +6039,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.59.0 '@typescript-eslint/types': 8.59.0 '@typescript-eslint/typescript-estree': 8.59.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.59.0 debug: 4.4.3(supports-color@8.1.1) - eslint: 10.2.1(jiti@2.6.1) + eslint: 9.39.4(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -6042,13 +6069,13 @@ snapshots: dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.59.0 '@typescript-eslint/typescript-estree': 8.59.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3(supports-color@8.1.1) - eslint: 10.2.1(jiti@2.6.1) + eslint: 9.39.4(jiti@2.6.1) ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -6071,13 +6098,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) '@typescript-eslint/scope-manager': 8.59.0 '@typescript-eslint/types': 8.59.0 '@typescript-eslint/typescript-estree': 8.59.0(typescript@5.9.3) - eslint: 10.2.1(jiti@2.6.1) + eslint: 9.39.4(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -7051,31 +7078,31 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.2.1(jiti@2.6.1)): + eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.6.1)): dependencies: - eslint: 10.2.1(jiti@2.6.1) + eslint: 9.39.4(jiti@2.6.1) - eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@10.2.1(jiti@2.6.1)))(eslint@10.2.1(jiti@2.6.1))(prettier@3.8.3): + eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1))(prettier@3.8.3): dependencies: - eslint: 10.2.1(jiti@2.6.1) + eslint: 9.39.4(jiti@2.6.1) prettier: 3.8.3 prettier-linter-helpers: 1.0.1 synckit: 0.11.12 optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@10.2.1(jiti@2.6.1)) + eslint-config-prettier: 10.1.8(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-react-hooks@7.1.1(eslint@10.2.1(jiti@2.6.1)): + eslint-plugin-react-hooks@7.1.1(eslint@9.39.4(jiti@2.6.1)): dependencies: '@babel/core': 7.29.0 '@babel/parser': 7.29.2 - eslint: 10.2.1(jiti@2.6.1) + eslint: 9.39.4(jiti@2.6.1) hermes-parser: 0.25.1 zod: 4.3.6 zod-validation-error: 4.0.2(zod@4.3.6) transitivePeerDependencies: - supports-color - eslint-plugin-react@7.37.5(eslint@10.2.1(jiti@2.6.1)): + eslint-plugin-react@7.37.5(eslint@9.39.4(jiti@2.6.1)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -7083,7 +7110,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.3.2 - eslint: 10.2.1(jiti@2.6.1) + eslint: 9.39.4(jiti@2.6.1) estraverse: 5.3.0 hasown: 2.0.3 jsx-ast-utils: 3.3.5 @@ -7097,12 +7124,12 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-sonarjs@4.0.3(eslint@10.2.1(jiti@2.6.1)): + eslint-plugin-sonarjs@4.0.3(eslint@9.39.4(jiti@2.6.1)): dependencies: '@eslint-community/regexpp': 4.12.2 builtin-modules: 3.3.0 bytes: 3.1.2 - eslint: 10.2.1(jiti@2.6.1) + eslint: 9.39.4(jiti@2.6.1) functional-red-black-tree: 1.0.1 globals: 17.5.0 jsx-ast-utils-x: 0.1.0 @@ -7113,15 +7140,15 @@ snapshots: ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 - eslint-plugin-unicorn@64.0.0(eslint@10.2.1(jiti@2.6.1)): + eslint-plugin-unicorn@64.0.0(eslint@9.39.4(jiti@2.6.1)): dependencies: '@babel/helper-validator-identifier': 7.28.5 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) change-case: 5.4.4 ci-info: 4.4.0 clean-regexp: 1.0.0 core-js-compat: 3.49.0 - eslint: 10.2.1(jiti@2.6.1) + eslint: 9.39.4(jiti@2.6.1) find-up-simple: 1.0.1 globals: 17.5.0 indent-string: 5.0.0 @@ -7133,36 +7160,39 @@ snapshots: semver: 7.7.4 strip-indent: 4.1.1 - eslint-scope@9.1.2: + eslint-scope@8.4.0: dependencies: - '@types/esrecurse': 4.3.1 - '@types/estree': 1.0.8 esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} + eslint-visitor-keys@4.2.1: {} + eslint-visitor-keys@5.0.1: {} - eslint@10.2.1(jiti@2.6.1): + eslint@9.39.4(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.23.5 - '@eslint/config-helpers': 0.5.5 - '@eslint/core': 1.2.1 - '@eslint/plugin-kit': 0.7.1 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 ajv: 6.15.0 + chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3(supports-color@8.1.1) escape-string-regexp: 4.0.0 - eslint-scope: 9.1.2 - eslint-visitor-keys: 5.0.1 - espree: 11.2.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -7173,7 +7203,8 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 - minimatch: 10.2.5 + lodash.merge: 4.6.2 + minimatch: 3.1.5 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: @@ -7181,11 +7212,11 @@ snapshots: transitivePeerDependencies: - supports-color - espree@11.2.0: + espree@10.4.0: dependencies: acorn: 8.16.0 acorn-jsx: 5.3.2(acorn@8.16.0) - eslint-visitor-keys: 5.0.1 + eslint-visitor-keys: 4.2.1 esquery@1.7.0: dependencies: @@ -7488,6 +7519,8 @@ snapshots: dependencies: ini: 2.0.0 + globals@14.0.0: {} + globals@17.5.0: {} globalthis@1.0.4: @@ -9590,13 +9623,13 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3))(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.59.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.59.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@5.9.3) - eslint: 10.2.1(jiti@2.6.1) + '@typescript-eslint/utils': 8.59.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.4(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color diff --git a/src/main/frontend/package.json b/src/main/frontend/package.json index 4baa6258..f99e2934 100644 --- a/src/main/frontend/package.json +++ b/src/main/frontend/package.json @@ -54,7 +54,7 @@ "@typescript-eslint/parser": "^8.59.0", "@vitejs/plugin-react": "^5.2.0", "@vitest/ui": "^4.1.5", - "eslint": "^10.2.1", + "eslint": "^9.39.4", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-react": "^7.37.5",