From 373bb0acd80009c8d5e90909ceed55d3c5f4ddb7 Mon Sep 17 00:00:00 2001 From: Mike Zorn Date: Wed, 14 Aug 2024 15:49:59 -0700 Subject: [PATCH] feat: Add dev server (AKA LaunchDevly) (#364) This adds a new command to the cli, dev-server. When you start it, an HTTP server starts running on port 8765. It provides APIs that mimic the APIs that LaunchDarkly SDKs use. You can add-projects to the dev server and they will be populated with flag values from the source-environment. You can also add-overrides to change the value served by a given flag. There's also a UI that will display which flag values are being served and allow you to set overrides. These features combine to provide a more portable solution for local & ephemeral development environments of projects using LaunchDarkly than the current state of the art: creating real environments in LaunchDarkly for each of these environments. --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- .github/actions/publish/action.yml | 39 +- .gitignore | 1 + .goreleaser.yaml | 65 +- .pre-commit-config.yaml | 7 +- Dockerfile.goreleaser | 3 - cmd/cliflags/flags.go | 44 +- cmd/config/testdata/help.golden | 2 + cmd/dev_server/dev_server.go | 60 + cmd/dev_server/overrides.go | 117 + cmd/dev_server/projects.go | 225 + cmd/dev_server/start_server.go | 86 + .../test_data/expected_template_data.json | 2 +- cmd/resources/test_data/test-openapi.json | 2 +- cmd/root.go | 5 + go.mod | 38 +- go.sum | 96 +- internal/config/config.go | 3 + internal/config/config_test.go | 3 + internal/dev_server/README.md | 4 + internal/dev_server/adapters/api.go | 39 + internal/dev_server/adapters/middleware.go | 22 + internal/dev_server/adapters/mocks/api.go | 55 + internal/dev_server/adapters/mocks/sdk.go | 57 + internal/dev_server/adapters/mocks/utils.go | 17 + internal/dev_server/adapters/sdk.go | 62 + internal/dev_server/api/api.yaml | 242 + internal/dev_server/api/oapi-codegen-cfg.yaml | 7 + internal/dev_server/api/server.gen.go | 1104 + internal/dev_server/api/server.go | 269 + internal/dev_server/db/docs.go | 2 + internal/dev_server/db/sqlite.go | 295 + internal/dev_server/db/sqlite_test.go | 223 + internal/dev_server/dev_server.go | 93 + internal/dev_server/model/docs.go | 2 + internal/dev_server/model/error.go | 23 + internal/dev_server/model/events.go | 14 + internal/dev_server/model/flags_state.go | 30 + internal/dev_server/model/flags_state_test.go | 45 + internal/dev_server/model/mocks/observer.go | 51 + internal/dev_server/model/mocks/store.go | 159 + internal/dev_server/model/observer.go | 45 + .../dev_server/model/observer_middleware.go | 25 + internal/dev_server/model/observer_test.go | 60 + internal/dev_server/model/override.go | 82 + internal/dev_server/model/override_test.go | 121 + internal/dev_server/model/project.go | 149 + internal/dev_server/model/project_test.go | 287 + internal/dev_server/model/store.go | 50 + internal/dev_server/sdk/constant_response.go | 13 + internal/dev_server/sdk/cors.go | 27 + internal/dev_server/sdk/docs.go | 34 + internal/dev_server/sdk/get_client_flags.go | 28 + internal/dev_server/sdk/get_server_flags.go | 38 + internal/dev_server/sdk/go_sdk_test.go | 161 + .../dev_server/sdk/project_key_middleware.go | 49 + internal/dev_server/sdk/routes.go | 50 + internal/dev_server/sdk/server_flags.go | 80 + internal/dev_server/sdk/store_facade.go | 42 + .../dev_server/sdk/stream_client_flags.go | 87 + .../dev_server/sdk/stream_server_flags.go | 84 + internal/dev_server/sdk/streaming.go | 95 + internal/dev_server/ui/.gitignore | 1 + internal/dev_server/ui/.npmrc | 1 + internal/dev_server/ui/.prettierrc | 6 + internal/dev_server/ui/README.md | 19 + internal/dev_server/ui/asset_handler.go | 19 + internal/dev_server/ui/dist/index.html | 65 + internal/dev_server/ui/index.html | 13 + internal/dev_server/ui/package-lock.json | 7026 + internal/dev_server/ui/package.json | 37 + internal/dev_server/ui/src/App.css | 61 + internal/dev_server/ui/src/App.tsx | 324 + internal/dev_server/ui/src/IconProvider.tsx | 28 + internal/dev_server/ui/src/main.tsx | 12 + internal/dev_server/ui/src/vite-env.d.ts | 2 + internal/dev_server/ui/tsconfig.app.json | 27 + internal/dev_server/ui/tsconfig.json | 11 + internal/dev_server/ui/tsconfig.node.json | 13 + internal/dev_server/ui/vite.config.ts | 26 + .../sdks/sdk_instructions/cpp-client-sdk.md | 2 +- .../sdk_instructions/dotnet-client-sdk.md | 2 - .../sdk_instructions/erlang-server-sdk.md | 2 +- .../sdk_instructions/flutter-client-sdk.md | 2 +- .../sdks/sdk_instructions/react-client-sdk.md | 2 +- .../sdks/sdk_instructions/react-native.md | 1 - ld-openapi.json | 2 +- tools.go | 6 + vendor/github.com/adrg/xdg/CODE_OF_CONDUCT.md | 77 + vendor/github.com/adrg/xdg/CONTRIBUTING.md | 135 + vendor/github.com/adrg/xdg/LICENSE | 21 + vendor/github.com/adrg/xdg/README.md | 280 + vendor/github.com/adrg/xdg/base_dirs.go | 68 + vendor/github.com/adrg/xdg/codecov.yml | 11 + vendor/github.com/adrg/xdg/doc.go | 99 + .../adrg/xdg/internal/pathutil/pathutil.go | 78 + .../xdg/internal/pathutil/pathutil_plan9.go | 29 + .../xdg/internal/pathutil/pathutil_unix.go | 32 + .../xdg/internal/pathutil/pathutil_windows.go | 64 + vendor/github.com/adrg/xdg/paths_darwin.go | 60 + vendor/github.com/adrg/xdg/paths_plan9.go | 55 + vendor/github.com/adrg/xdg/paths_unix.go | 71 + vendor/github.com/adrg/xdg/paths_windows.go | 168 + vendor/github.com/adrg/xdg/user_dirs.go | 40 + vendor/github.com/adrg/xdg/xdg.go | 218 + .../apapsch/go-jsonmerge/v2/.editorconfig | 6 + .../apapsch/go-jsonmerge/v2/.gitattributes | 175 + .../apapsch/go-jsonmerge/v2/.gitignore | 11 + .../apapsch/go-jsonmerge/v2/.gitlab-ci.yml | 42 + .../apapsch/go-jsonmerge/v2/.travis.yml | 19 + .../apapsch/go-jsonmerge/v2/LICENSE | 21 + .../apapsch/go-jsonmerge/v2/README.md | 81 + .../apapsch/go-jsonmerge/v2/build.cmd | 25 + .../apapsch/go-jsonmerge/v2/build.sh | 19 + .../github.com/apapsch/go-jsonmerge/v2/doc.go | 52 + .../apapsch/go-jsonmerge/v2/merge.go | 167 + .../github.com/felixge/httpsnoop/.gitignore | 0 .../github.com/felixge/httpsnoop/.travis.yml | 6 + .../github.com/felixge/httpsnoop/LICENSE.txt | 19 + vendor/github.com/felixge/httpsnoop/Makefile | 10 + vendor/github.com/felixge/httpsnoop/README.md | 95 + .../felixge/httpsnoop/capture_metrics.go | 86 + vendor/github.com/felixge/httpsnoop/docs.go | 10 + .../httpsnoop/wrap_generated_gteq_1.8.go | 436 + .../httpsnoop/wrap_generated_lt_1.8.go | 278 + .../openapi3/testdata/circularRef/base.yml | 16 - .../openapi3/testdata/circularRef/other.yml | 10 - .../testdata/recursiveRef/components/Bar.yml | 2 - .../testdata/recursiveRef/components/Cat.yml | 4 - .../testdata/recursiveRef/components/Foo.yml | 4 - .../recursiveRef/components/Foo/Foo2.yml | 4 - .../recursiveRef/components/models/error.yaml | 2 - .../testdata/recursiveRef/issue615.yml | 60 - .../testdata/recursiveRef/openapi.yml | 33 - .../recursiveRef/openapi.yml.internalized.yml | 110 - .../recursiveRef/parameters/number.yml | 4 - .../testdata/recursiveRef/paths/foo.yml | 15 - .../github.com/gorilla/handlers/.editorconfig | 20 + vendor/github.com/gorilla/handlers/.gitignore | 2 + vendor/github.com/gorilla/handlers/LICENSE | 27 + vendor/github.com/gorilla/handlers/Makefile | 34 + vendor/github.com/gorilla/handlers/README.md | 56 + .../github.com/gorilla/handlers/canonical.go | 73 + .../github.com/gorilla/handlers/compress.go | 143 + vendor/github.com/gorilla/handlers/cors.go | 352 + vendor/github.com/gorilla/handlers/doc.go | 9 + .../github.com/gorilla/handlers/handlers.go | 150 + vendor/github.com/gorilla/handlers/logging.go | 246 + .../gorilla/handlers/proxy_headers.go | 120 + .../github.com/gorilla/handlers/recovery.go | 98 + vendor/github.com/gorilla/mux/.editorconfig | 20 + vendor/github.com/gorilla/mux/.gitignore | 1 + vendor/github.com/gorilla/mux/LICENSE | 27 + vendor/github.com/gorilla/mux/Makefile | 34 + vendor/github.com/gorilla/mux/README.md | 812 + vendor/github.com/gorilla/mux/doc.go | 305 + vendor/github.com/gorilla/mux/middleware.go | 74 + vendor/github.com/gorilla/mux/mux.go | 608 + vendor/github.com/gorilla/mux/regexp.go | 388 + vendor/github.com/gorilla/mux/route.go | 765 + vendor/github.com/gorilla/mux/test_helpers.go | 19 + .../gregjones/httpcache/.travis.yml | 19 + .../gregjones/httpcache/LICENSE.txt | 7 + .../github.com/gregjones/httpcache/README.md | 25 + .../gregjones/httpcache/httpcache.go | 551 + .../github.com/launchdarkly/ccache/.gitignore | 1 + .../github.com/launchdarkly/ccache/Makefile | 5 + .../github.com/launchdarkly/ccache/bucket.go | 105 + .../github.com/launchdarkly/ccache/cache.go | 323 + .../launchdarkly/ccache/configuration.go | 103 + vendor/github.com/launchdarkly/ccache/item.go | 107 + .../launchdarkly/ccache/layeredbucket.go | 121 + .../launchdarkly/ccache/layeredcache.go | 302 + .../launchdarkly/ccache/license.txt | 19 + .../github.com/launchdarkly/ccache/readme.md | 9 + .../launchdarkly/ccache/secondarycache.go | 72 + .../launchdarkly/eventsource/.gitignore | 2 + .../launchdarkly/eventsource/.golangci.yml | 53 + .../launchdarkly/eventsource/CHANGELOG.md | 69 + .../launchdarkly/eventsource/CONTRIBUTING.md | 36 + .../launchdarkly/eventsource/LICENSE.txt | 13 + .../launchdarkly/eventsource/Makefile | 22 + .../launchdarkly/eventsource/README.md | 24 + .../eventsource/check_redirect.go | 26 + .../eventsource/check_redirect_go1.8.go | 9 + .../launchdarkly/eventsource/decoder.go | 157 + .../launchdarkly/eventsource/encoder.go | 68 + .../launchdarkly/eventsource/interface.go | 73 + .../launchdarkly/eventsource/normalise.go | 35 + .../launchdarkly/eventsource/repository.go | 53 + .../launchdarkly/eventsource/retry_delay.go | 136 + .../launchdarkly/eventsource/server.go | 356 + .../launchdarkly/eventsource/stream.go | 385 + .../eventsource/stream_options.go | 243 + .../launchdarkly/go-jsonstream/v3/LICENSE.txt | 13 + .../go-jsonstream/v3/jreader/errors.go | 97 + .../go-jsonstream/v3/jreader/interfaces.go | 76 + .../go-jsonstream/v3/jreader/package_info.go | 50 + .../go-jsonstream/v3/jreader/reader.go | 398 + .../go-jsonstream/v3/jreader/reader_array.go | 64 + .../v3/jreader/reader_init_default.go | 12 + .../v3/jreader/reader_init_easyjson.go | 22 + .../go-jsonstream/v3/jreader/reader_object.go | 173 + .../v3/jreader/reader_unmarshal.go | 17 + .../v3/jreader/token_reader_default.go | 585 + .../v3/jreader/token_reader_easyjson.go | 265 + .../go-jsonstream/v3/jwriter/interfaces.go | 10 + .../go-jsonstream/v3/jwriter/no_op_writer.go | 15 + .../go-jsonstream/v3/jwriter/package_info.go | 54 + .../v3/jwriter/streamable_buffer.go | 70 + .../v3/jwriter/test_behavior_default.go | 10 + .../v3/jwriter/test_behavior_easyjson.go | 10 + .../v3/jwriter/token_writer_default.go | 192 + .../v3/jwriter/token_writer_easyjson.go | 159 + .../go-jsonstream/v3/jwriter/writer.go | 192 + .../go-jsonstream/v3/jwriter/writer_array.go | 82 + .../v3/jwriter/writer_init_default.go | 26 + .../v3/jwriter/writer_init_easyjson.go | 22 + .../v3/jwriter/writer_marshal.go | 13 + .../go-jsonstream/v3/jwriter/writer_object.go | 56 + .../launchdarkly/go-sdk-common/v3/LICENSE.txt | 13 + .../go-sdk-common/v3/ldattr/constants.go | 23 + .../go-sdk-common/v3/ldattr/package.go | 10 + .../go-sdk-common/v3/ldattr/ref.go | 280 + .../v3/ldcontext/builder_multi.go | 188 + .../v3/ldcontext/builder_simple.go | 610 + .../go-sdk-common/v3/ldcontext/constants.go | 11 + .../v3/ldcontext/constructors.go | 56 + .../go-sdk-common/v3/ldcontext/context.go | 463 + .../v3/ldcontext/context_easyjson.go | 376 + .../v3/ldcontext/context_marshaling.go | 81 + .../v3/ldcontext/context_serialization.go | 69 + .../v3/ldcontext/context_unmarshaling.go | 352 + .../v3/ldcontext/event_output_context.go | 16 + .../go-sdk-common/v3/ldcontext/kind.go | 47 + .../v3/ldcontext/package_info.go | 6 + .../v3/lderrors/attribute_errors.go | 29 + .../v3/lderrors/context_errors.go | 93 + .../go-sdk-common/v3/lderrors/package_info.go | 19 + .../go-sdk-common/v3/ldlog/logging.go | 313 + .../go-sdk-common/v3/ldlog/package_info.go | 7 + .../v3/ldmigration/ldmigration_info.go | 2 + .../go-sdk-common/v3/ldmigration/migration.go | 112 + .../go-sdk-common/v3/ldreason/detail.go | 46 + .../go-sdk-common/v3/ldreason/package_info.go | 7 + .../go-sdk-common/v3/ldreason/reason.go | 290 + .../ldreason/reason_serialization_easyjson.go | 34 + .../v3/ldsampling/package_info.go | 2 + .../go-sdk-common/v3/ldsampling/sampler.go | 50 + .../go-sdk-common/v3/ldtime/package_info.go | 2 + .../go-sdk-common/v3/ldtime/unix_millis.go | 25 + .../go-sdk-common/v3/ldvalue/constants.go | 37 + .../go-sdk-common/v3/ldvalue/interfaces.go | 14 + .../go-sdk-common/v3/ldvalue/optional_bool.go | 75 + .../go-sdk-common/v3/ldvalue/optional_int.go | 78 + .../v3/ldvalue/optional_string.go | 86 + .../v3/ldvalue/optionals_generic.go | 46 + .../v3/ldvalue/optionals_json_conversion.go | 175 + .../optionals_json_easyjson_conversion.go | 83 + .../v3/ldvalue/optionals_text_conversion.go | 153 + .../go-sdk-common/v3/ldvalue/package_info.go | 41 + .../go-sdk-common/v3/ldvalue/value_array.go | 250 + .../go-sdk-common/v3/ldvalue/value_base.go | 557 + .../v3/ldvalue/value_complex_types.go | 330 + .../v3/ldvalue/value_json_conversion.go | 291 + .../ldvalue/value_json_easyjson_conversion.go | 124 + .../go-sdk-common/v3/ldvalue/value_map.go | 270 + .../v3/ldvalue/value_text_conversion.go | 71 + .../launchdarkly/go-sdk-events/v3/.gitignore | 3 + .../go-sdk-events/v3/.golangci.yml | 52 + .../go-sdk-events/v3/CHANGELOG.md | 67 + .../launchdarkly/go-sdk-events/v3/CODEOWNERS | 2 + .../go-sdk-events/v3/CONTRIBUTING.md | 69 + .../launchdarkly/go-sdk-events/v3/LICENSE.txt | 13 + .../launchdarkly/go-sdk-events/v3/Makefile | 63 + .../launchdarkly/go-sdk-events/v3/README.md | 33 + .../launchdarkly/go-sdk-events/v3/config.go | 59 + .../go-sdk-events/v3/context_formatter.go | 347 + .../go-sdk-events/v3/diagnostic_events.go | 169 + .../go-sdk-events/v3/event_processor.go | 465 + .../go-sdk-events/v3/event_sender.go | 210 + .../go-sdk-events/v3/event_summarizer.go | 97 + .../go-sdk-events/v3/events_output.go | 240 + .../launchdarkly/go-sdk-events/v3/inputs.go | 278 + .../go-sdk-events/v3/interfaces.go | 73 + .../go-sdk-events/v3/lru_cache.go | 44 + .../go-sdk-events/v3/null_event_processor.go | 31 + .../launchdarkly/go-sdk-events/v3/outbox.go | 60 + .../go-sdk-events/v3/package_info.go | 2 + .../launchdarkly/go-sdk-events/v3/util.go | 36 + .../launchdarkly/go-semver/.gitignore | 2 + .../launchdarkly/go-semver/CHANGELOG.md | 15 + .../launchdarkly/go-semver/CONTRIBUTING.md | 51 + .../launchdarkly/go-semver/LICENSE.txt | 13 + .../launchdarkly/go-semver/Makefile | 46 + .../launchdarkly/go-semver/README.md | 21 + .../launchdarkly/go-semver/compare.go | 93 + .../launchdarkly/go-semver/parse.go | 187 + .../github.com/launchdarkly/go-semver/scan.go | 63 + .../launchdarkly/go-semver/syntax.go | 56 + .../go-semver/values_generator.go | 88 + .../launchdarkly/go-semver/version.go | 38 + .../go-server-sdk-evaluation/v3/.gitignore | 5 + .../go-server-sdk-evaluation/v3/.golangci.yml | 50 + .../go-server-sdk-evaluation/v3/CHANGELOG.md | 97 + .../go-server-sdk-evaluation/v3/CODEOWNERS | 2 + .../v3/CONTRIBUTING.md | 85 + .../go-server-sdk-evaluation/v3/LICENSE.txt | 13 + .../go-server-sdk-evaluation/v3/Makefile | 74 + .../go-server-sdk-evaluation/v3/README.md | 39 + .../go-server-sdk-evaluation/v3/SECURITY.md | 5 + .../go-server-sdk-evaluation/v3/errors.go | 110 + .../go-server-sdk-evaluation/v3/evaluator.go | 409 + .../v3/evaluator_bucketing.go | 109 + .../v3/evaluator_clause.go | 196 + .../v3/evaluator_options.go | 52 + .../v3/evaluator_segment.go | 195 + .../go-server-sdk-evaluation/v3/interfaces.go | 134 + .../v3/internal/local_buffer.go | 68 + .../v3/internal/package_info.go | 3 + .../v3/internal/parse_hex.go | 26 + .../v3/ldmodel/eval_accessors.go | 194 + .../v3/ldmodel/model_flag.go | 323 + .../v3/ldmodel/model_marshal.go | 259 + .../v3/ldmodel/model_segment.go | 110 + .../v3/ldmodel/model_serialization.go | 128 + .../ldmodel/model_serialization_easyjson.go | 44 + .../v3/ldmodel/model_unmarshal.go | 355 + .../v3/ldmodel/operators.go | 70 + .../v3/ldmodel/package_info.go | 20 + .../v3/ldmodel/parse_time.go | 198 + .../v3/ldmodel/parse_values.go | 42 + .../v3/ldmodel/preprocess.go | 165 + .../v3/ldmodel/type_conversions.go | 56 + .../v3/package_info.go | 6 + .../launchdarkly/go-server-sdk/v7/.gitignore | 8 + .../go-server-sdk/v7/.golangci.yml | 53 + .../v7/.release-please-manifest.json | 4 + .../go-server-sdk/v7/.sdk_metadata.json | 12 + .../go-server-sdk/v7/CHANGELOG.md | 595 + .../launchdarkly/go-server-sdk/v7/CODEOWNERS | 2 + .../go-server-sdk/v7/CONTRIBUTING.md | 132 + .../launchdarkly/go-server-sdk/v7/LICENSE.txt | 13 + .../launchdarkly/go-server-sdk/v7/Makefile | 146 + .../launchdarkly/go-server-sdk/v7/README.md | 79 + .../launchdarkly/go-server-sdk/v7/SECURITY.md | 5 + .../v7/client_context_from_config.go | 84 + .../launchdarkly/go-server-sdk/v7/config.go | 200 + .../v7/interfaces/application_info.go | 26 + .../big_segment_store_status_provider.go | 76 + .../v7/interfaces/client_interface.go | 271 + .../interfaces/data_source_status_provider.go | 233 + .../interfaces/data_store_status_provider.go | 80 + .../v7/interfaces/flag_tracker.go | 105 + .../v7/interfaces/flagstate/flags_state.go | 227 + .../v7/interfaces/flagstate/package_info.go | 3 + .../v7/interfaces/package_info.go | 3 + .../v7/interfaces/service_endpoints.go | 42 + .../big_segment_store_status_provider_impl.go | 47 + .../v7/internal/bigsegments/package_info.go | 8 + .../v7/internal/bigsegments/user_hash.go | 13 + .../go-server-sdk/v7/internal/broadcasters.go | 88 + .../v7/internal/client_context_impl.go | 13 + .../go-server-sdk/v7/internal/concurrent.go | 38 + .../internal/datakinds/data_kind_internal.go | 14 + .../v7/internal/datakinds/data_kinds_impl.go | 135 + .../v7/internal/datakinds/package_info.go | 5 + .../datasource/data_model_dependencies.go | 196 + .../data_source_status_provider_impl.go | 39 + .../data_source_update_sink_impl.go | 390 + .../v7/internal/datasource/helpers.go | 127 + .../internal/datasource/null_data_source.go | 22 + .../v7/internal/datasource/package_info.go | 6 + .../datasource/polling_data_source.go | 208 + .../datasource/polling_http_request.go | 132 + .../datasource/streaming_data_source.go | 414 + .../streaming_data_source_events.go | 178 + .../datastore/data_store_eval_impl.go | 44 + .../datastore/data_store_status_poller.go | 97 + .../data_store_status_provider_impl.go | 40 + .../datastore/data_store_update_sink_impl.go | 51 + .../datastore/in_memory_data_store_impl.go | 142 + .../v7/internal/datastore/package_info.go | 7 + .../persistent_data_store_wrapper.go | 437 + .../internal/endpoints/configure_endpoints.go | 97 + .../v7/internal/endpoints/package_info.go | 2 + .../internal/endpoints/standard_endpoints.go | 20 + .../v7/internal/flag_tracker_impl.go | 105 + .../v7/internal/hooks/evaluation_execution.go | 68 + .../v7/internal/hooks/iterator.go | 62 + .../v7/internal/hooks/package_info.go | 2 + .../go-server-sdk/v7/internal/hooks/runner.go | 64 + .../go-server-sdk/v7/internal/log_messages.go | 16 + .../go-server-sdk/v7/internal/package_info.go | 4 + .../go-server-sdk/v7/internal/version.go | 4 + .../launchdarkly/go-server-sdk/v7/ldclient.go | 1329 + .../go-server-sdk/v7/ldclient_events.go | 353 + .../big_segments_configuration_builder.go | 163 + .../external_updates_data_source.go | 48 + .../http_configuration_builder.go | 282 + .../v7/ldcomponents/in_memory_data_store.go | 25 + .../logging_configuration_builder.go | 142 + .../v7/ldcomponents/no_events.go | 33 + .../v7/ldcomponents/package_info.go | 9 + .../persistent_data_store_builder.go | 116 + .../polling_data_source_builder.go | 112 + .../v7/ldcomponents/send_events.go | 218 + .../v7/ldcomponents/service_endpoints.go | 56 + .../streaming_data_source_builder.go | 106 + .../v7/ldhooks/evaluation_series_context.go | 46 + .../v7/ldhooks/evaluation_series_data.go | 77 + .../go-server-sdk/v7/ldhooks/hooks.go | 93 + .../go-server-sdk/v7/ldhooks/metadata.go | 33 + .../go-server-sdk/v7/ldhooks/package_info.go | 5 + .../go-server-sdk/v7/ldhttp/http_transport.go | 144 + .../go-server-sdk/v7/migration_op_tracker.go | 182 + .../launchdarkly/go-server-sdk/v7/migrator.go | 257 + .../go-server-sdk/v7/migrator_base.go | 141 + .../go-server-sdk/v7/migrator_builder.go | 116 + .../go-server-sdk/v7/package_info.go | 19 + .../v7/release-please-config.json | 30 + .../v7/server_side_diagnostics.go | 109 + .../v7/subsystems/big_segments.go | 84 + .../v7/subsystems/client_context.go | 92 + .../v7/subsystems/component_configurer.go | 9 + .../v7/subsystems/data_source.go | 20 + .../v7/subsystems/data_source_update_sink.go | 59 + .../go-server-sdk/v7/subsystems/data_store.go | 75 + .../v7/subsystems/data_store_update_sink.go | 16 + .../v7/subsystems/diagnostic_description.go | 17 + .../v7/subsystems/http_configuration.go | 20 + .../ldstoreimpl/big_segment_store_wrapper.go | 286 + .../ldstoreimpl/big_segments_config_extra.go | 58 + .../ldstoreimpl/big_segments_membership.go | 83 + .../v7/subsystems/ldstoreimpl/data_kinds.go | 26 + .../subsystems/ldstoreimpl/data_store_eval.go | 21 + .../v7/subsystems/ldstoreimpl/package_info.go | 4 + .../ldstoretypes/data_store_types.go | 92 + .../subsystems/ldstoretypes/package_info.go | 3 + .../v7/subsystems/logging_configuration.go | 26 + .../v7/subsystems/package_info.go | 10 + .../v7/subsystems/persistent_data_store.go | 86 + .../github.com/mattn/go-isatty/isatty_bsd.go | 3 +- .../mattn/go-isatty/isatty_others.go | 5 +- .../mattn/go-isatty/isatty_tcgets.go | 3 +- .../github.com/mattn/go-sqlite3/.codecov.yml | 4 + vendor/github.com/mattn/go-sqlite3/.gitignore | 14 + vendor/github.com/mattn/go-sqlite3/LICENSE | 21 + vendor/github.com/mattn/go-sqlite3/README.md | 603 + vendor/github.com/mattn/go-sqlite3/backup.go | 85 + .../github.com/mattn/go-sqlite3/callback.go | 411 + vendor/github.com/mattn/go-sqlite3/convert.go | 299 + vendor/github.com/mattn/go-sqlite3/doc.go | 134 + vendor/github.com/mattn/go-sqlite3/error.go | 150 + .../mattn/go-sqlite3/sqlite3-binding.c | 256040 +++++++++++++++ .../mattn/go-sqlite3/sqlite3-binding.h | 13456 + vendor/github.com/mattn/go-sqlite3/sqlite3.go | 2281 + .../mattn/go-sqlite3/sqlite3_context.go | 103 + .../mattn/go-sqlite3/sqlite3_func_crypt.go | 120 + .../mattn/go-sqlite3/sqlite3_go18.go | 54 + .../mattn/go-sqlite3/sqlite3_libsqlite3.go | 22 + .../go-sqlite3/sqlite3_load_extension.go | 85 + .../go-sqlite3/sqlite3_load_extension_omit.go | 25 + .../sqlite3_opt_allow_uri_authority.go | 16 + .../mattn/go-sqlite3/sqlite3_opt_app_armor.go | 16 + .../go-sqlite3/sqlite3_opt_column_metadata.go | 22 + .../go-sqlite3/sqlite3_opt_foreign_keys.go | 16 + .../mattn/go-sqlite3/sqlite3_opt_fts5.go | 15 + .../mattn/go-sqlite3/sqlite3_opt_icu.go | 20 + .../go-sqlite3/sqlite3_opt_introspect.go | 16 + .../go-sqlite3/sqlite3_opt_math_functions.go | 15 + .../mattn/go-sqlite3/sqlite3_opt_os_trace.go | 15 + .../mattn/go-sqlite3/sqlite3_opt_preupdate.go | 21 + .../go-sqlite3/sqlite3_opt_preupdate_hook.go | 113 + .../go-sqlite3/sqlite3_opt_preupdate_omit.go | 22 + .../go-sqlite3/sqlite3_opt_secure_delete.go | 16 + .../sqlite3_opt_secure_delete_fast.go | 16 + .../mattn/go-sqlite3/sqlite3_opt_serialize.go | 83 + .../go-sqlite3/sqlite3_opt_serialize_omit.go | 21 + .../mattn/go-sqlite3/sqlite3_opt_stat4.go | 16 + .../go-sqlite3/sqlite3_opt_unlock_notify.c | 85 + .../go-sqlite3/sqlite3_opt_unlock_notify.go | 93 + .../mattn/go-sqlite3/sqlite3_opt_userauth.go | 295 + .../go-sqlite3/sqlite3_opt_userauth_omit.go | 158 + .../go-sqlite3/sqlite3_opt_vacuum_full.go | 16 + .../go-sqlite3/sqlite3_opt_vacuum_incr.go | 16 + .../mattn/go-sqlite3/sqlite3_opt_vtable.go | 721 + .../mattn/go-sqlite3/sqlite3_other.go | 18 + .../mattn/go-sqlite3/sqlite3_solaris.go | 15 + .../mattn/go-sqlite3/sqlite3_trace.go | 288 + .../mattn/go-sqlite3/sqlite3_type.go | 108 + .../go-sqlite3/sqlite3_usleep_windows.go | 42 + .../mattn/go-sqlite3/sqlite3_windows.go | 18 + .../github.com/mattn/go-sqlite3/sqlite3ext.h | 728 + .../mattn/go-sqlite3/static_mock.go | 38 + .../microcosm-cc/bluemonday/helpers.go | 6 +- .../microcosm-cc/bluemonday/policy.go | 51 + .../microcosm-cc/bluemonday/sanitize.go | 53 +- .../oapi-codegen/oapi-codegen/v2/LICENSE | 201 + .../v2/cmd/oapi-codegen/oapi-codegen.go | 549 + .../oapi-codegen/v2/pkg/codegen/codegen.go | 1185 + .../v2/pkg/codegen/configuration.go | 189 + .../oapi-codegen/v2/pkg/codegen/extension.go | 102 + .../v2/pkg/codegen/externalref.go | 80 + .../oapi-codegen/v2/pkg/codegen/filter.go | 88 + .../oapi-codegen/v2/pkg/codegen/inline.go | 77 + .../v2/pkg/codegen/merge_schemas.go | 249 + .../v2/pkg/codegen/merge_schemas_v1.go | 111 + .../oapi-codegen/v2/pkg/codegen/operations.go | 1098 + .../oapi-codegen/v2/pkg/codegen/prune.go | 489 + .../oapi-codegen/v2/pkg/codegen/schema.go | 879 + .../v2/pkg/codegen/template_helpers.go | 326 + .../templates/additional-properties.tmpl | 72 + .../codegen/templates/chi/chi-handler.tmpl | 50 + .../codegen/templates/chi/chi-interface.tmpl | 17 + .../codegen/templates/chi/chi-middleware.tmpl | 261 + .../templates/client-with-responses.tmpl | 120 + .../v2/pkg/codegen/templates/client.tmpl | 312 + .../v2/pkg/codegen/templates/constants.tmpl | 15 + .../templates/echo/echo-interface.tmpl | 7 + .../codegen/templates/echo/echo-register.tmpl | 33 + .../codegen/templates/echo/echo-wrappers.tmpl | 131 + .../templates/fiber/fiber-handler.tmpl | 25 + .../templates/fiber/fiber-interface.tmpl | 7 + .../templates/fiber/fiber-middleware.tmpl | 169 + .../codegen/templates/gin/gin-interface.tmpl | 7 + .../codegen/templates/gin/gin-register.tmpl | 33 + .../codegen/templates/gin/gin-wrappers.tmpl | 186 + .../templates/gorilla/gorilla-interface.tmpl | 7 + .../templates/gorilla/gorilla-middleware.tmpl | 261 + .../templates/gorilla/gorilla-register.tmpl | 49 + .../v2/pkg/codegen/templates/imports.tmpl | 50 + .../v2/pkg/codegen/templates/inline.tmpl | 87 + .../codegen/templates/iris/iris-handler.tmpl | 23 + .../templates/iris/iris-interface.tmpl | 7 + .../templates/iris/iris-middleware.tmpl | 161 + .../v2/pkg/codegen/templates/param-types.tmpl | 6 + .../pkg/codegen/templates/request-bodies.tmpl | 11 + .../templates/stdhttp/std-http-handler.tmpl | 49 + .../templates/stdhttp/std-http-interface.tmpl | 7 + .../stdhttp/std-http-middleware.tmpl | 261 + .../codegen/templates/strict/strict-echo.tmpl | 97 + .../strict/strict-fiber-interface.tmpl | 145 + .../templates/strict/strict-fiber.tmpl | 90 + .../codegen/templates/strict/strict-gin.tmpl | 106 + .../codegen/templates/strict/strict-http.tmpl | 121 + .../templates/strict/strict-interface.tmpl | 147 + .../strict/strict-iris-interface.tmpl | 147 + .../codegen/templates/strict/strict-iris.tmpl | 107 + .../templates/strict/strict-responses.tmpl | 41 + .../v2/pkg/codegen/templates/typedef.tmpl | 4 + .../union-and-additional-properties.tmpl | 72 + .../v2/pkg/codegen/templates/union.tmpl | 140 + .../v2/pkg/codegen/test_schema.json | 15 + .../v2/pkg/codegen/test_spec.yaml | 207 + .../oapi-codegen/v2/pkg/codegen/utils.go | 1066 + .../oapi-codegen/v2/pkg/util/inputmapping.go | 79 + .../oapi-codegen/v2/pkg/util/isjson.go | 14 + .../oapi-codegen/v2/pkg/util/loader.go | 37 + .../oapi-codegen/runtime/.gitignore | 1 + .../github.com/oapi-codegen/runtime/LICENSE | 201 + .../github.com/oapi-codegen/runtime/Makefile | 32 + .../github.com/oapi-codegen/runtime/README.md | 6 + .../github.com/oapi-codegen/runtime/bind.go | 24 + .../oapi-codegen/runtime/bindform.go | 318 + .../oapi-codegen/runtime/bindparam.go | 555 + .../oapi-codegen/runtime/bindstring.go | 174 + .../oapi-codegen/runtime/deepobject.go | 371 + .../oapi-codegen/runtime/jsonmerge.go | 34 + .../oapi-codegen/runtime/renovate.json | 6 + .../runtime/strictmiddleware/nethttp/main.go | 16 + .../oapi-codegen/runtime/styleparam.go | 478 + .../oapi-codegen/runtime/types/date.go | 43 + .../oapi-codegen/runtime/types/email.go | 40 + .../oapi-codegen/runtime/types/file.go | 71 + .../oapi-codegen/runtime/types/regexes.go | 11 + .../oapi-codegen/runtime/types/uuid.go | 7 + .../patrickmn/go-cache/CONTRIBUTORS | 9 + vendor/github.com/patrickmn/go-cache/LICENSE | 19 + .../github.com/patrickmn/go-cache/README.md | 83 + vendor/github.com/patrickmn/go-cache/cache.go | 1161 + .../github.com/patrickmn/go-cache/sharded.go | 192 + vendor/go.uber.org/mock/AUTHORS | 12 + vendor/go.uber.org/mock/LICENSE | 202 + vendor/go.uber.org/mock/gomock/call.go | 508 + vendor/go.uber.org/mock/gomock/callset.go | 164 + vendor/go.uber.org/mock/gomock/controller.go | 318 + vendor/go.uber.org/mock/gomock/doc.go | 60 + vendor/go.uber.org/mock/gomock/matchers.go | 443 + .../go.uber.org/mock/mockgen/generic_go118.go | 130 + .../mock/mockgen/generic_notgo118.go | 41 + vendor/go.uber.org/mock/mockgen/mockgen.go | 883 + .../go.uber.org/mock/mockgen/model/model.go | 533 + vendor/go.uber.org/mock/mockgen/parse.go | 805 + vendor/go.uber.org/mock/mockgen/reflect.go | 256 + vendor/go.uber.org/mock/mockgen/version.go | 31 + vendor/golang.org/x/exp/maps/maps.go | 94 + vendor/golang.org/x/mod/LICENSE | 27 + vendor/golang.org/x/mod/PATENTS | 22 + .../x/mod/internal/lazyregexp/lazyre.go | 78 + vendor/golang.org/x/mod/modfile/print.go | 184 + vendor/golang.org/x/mod/modfile/read.go | 958 + vendor/golang.org/x/mod/modfile/rule.go | 1766 + vendor/golang.org/x/mod/modfile/work.go | 335 + vendor/golang.org/x/mod/module/module.go | 843 + vendor/golang.org/x/mod/module/pseudo.go | 250 + vendor/golang.org/x/mod/semver/semver.go | 401 + vendor/golang.org/x/net/html/doc.go | 2 +- vendor/golang.org/x/sync/errgroup/errgroup.go | 3 + .../x/sync/singleflight/singleflight.go | 214 + vendor/golang.org/x/sys/unix/asm_zos_s390x.s | 665 +- vendor/golang.org/x/sys/unix/bpxsvc_zos.go | 657 + vendor/golang.org/x/sys/unix/bpxsvc_zos.s | 192 + vendor/golang.org/x/sys/unix/epoll_zos.go | 220 - vendor/golang.org/x/sys/unix/fstatfs_zos.go | 163 - vendor/golang.org/x/sys/unix/mkerrors.sh | 2 + vendor/golang.org/x/sys/unix/mmap_nomremap.go | 2 +- vendor/golang.org/x/sys/unix/pagesize_unix.go | 2 +- .../x/sys/unix/readdirent_getdirentries.go | 2 +- vendor/golang.org/x/sys/unix/sockcmsg_zos.go | 58 + .../golang.org/x/sys/unix/symaddr_zos_s390x.s | 75 + .../x/sys/unix/syscall_zos_s390x.go | 1509 +- vendor/golang.org/x/sys/unix/sysvshm_unix.go | 2 +- .../x/sys/unix/sysvshm_unix_other.go | 2 +- vendor/golang.org/x/sys/unix/zerrors_linux.go | 29 +- .../x/sys/unix/zerrors_linux_386.go | 1 + .../x/sys/unix/zerrors_linux_amd64.go | 1 + .../x/sys/unix/zerrors_linux_arm64.go | 1 + .../x/sys/unix/zerrors_zos_s390x.go | 233 +- .../x/sys/unix/zsymaddr_zos_s390x.s | 364 + .../x/sys/unix/zsyscall_zos_s390x.go | 3113 +- .../x/sys/unix/zsysnum_linux_386.go | 5 + .../x/sys/unix/zsysnum_linux_amd64.go | 5 + .../x/sys/unix/zsysnum_linux_arm.go | 5 + .../x/sys/unix/zsysnum_linux_arm64.go | 5 + .../x/sys/unix/zsysnum_linux_loong64.go | 5 + .../x/sys/unix/zsysnum_linux_mips.go | 5 + .../x/sys/unix/zsysnum_linux_mips64.go | 5 + .../x/sys/unix/zsysnum_linux_mips64le.go | 5 + .../x/sys/unix/zsysnum_linux_mipsle.go | 5 + .../x/sys/unix/zsysnum_linux_ppc.go | 5 + .../x/sys/unix/zsysnum_linux_ppc64.go | 5 + .../x/sys/unix/zsysnum_linux_ppc64le.go | 5 + .../x/sys/unix/zsysnum_linux_riscv64.go | 5 + .../x/sys/unix/zsysnum_linux_s390x.go | 5 + .../x/sys/unix/zsysnum_linux_sparc64.go | 5 + .../x/sys/unix/zsysnum_zos_s390x.go | 5507 +- vendor/golang.org/x/sys/unix/ztypes_linux.go | 59 +- .../golang.org/x/sys/unix/ztypes_linux_386.go | 8 - .../x/sys/unix/ztypes_linux_amd64.go | 9 - .../golang.org/x/sys/unix/ztypes_linux_arm.go | 9 - .../x/sys/unix/ztypes_linux_arm64.go | 9 - .../x/sys/unix/ztypes_linux_loong64.go | 9 - .../x/sys/unix/ztypes_linux_mips.go | 9 - .../x/sys/unix/ztypes_linux_mips64.go | 9 - .../x/sys/unix/ztypes_linux_mips64le.go | 9 - .../x/sys/unix/ztypes_linux_mipsle.go | 9 - .../golang.org/x/sys/unix/ztypes_linux_ppc.go | 9 - .../x/sys/unix/ztypes_linux_ppc64.go | 9 - .../x/sys/unix/ztypes_linux_ppc64le.go | 9 - .../x/sys/unix/ztypes_linux_riscv64.go | 9 - .../x/sys/unix/ztypes_linux_s390x.go | 9 - .../x/sys/unix/ztypes_linux_sparc64.go | 9 - .../golang.org/x/sys/unix/ztypes_zos_s390x.go | 146 +- vendor/golang.org/x/sys/windows/aliases.go | 2 +- .../x/sys/windows/security_windows.go | 1 + .../x/sys/windows/syscall_windows.go | 82 + .../golang.org/x/sys/windows/types_windows.go | 24 + .../x/sys/windows/zsyscall_windows.go | 135 +- vendor/golang.org/x/text/cases/cases.go | 162 + vendor/golang.org/x/text/cases/context.go | 376 + vendor/golang.org/x/text/cases/fold.go | 34 + vendor/golang.org/x/text/cases/icu.go | 61 + vendor/golang.org/x/text/cases/info.go | 82 + vendor/golang.org/x/text/cases/map.go | 816 + .../golang.org/x/text/cases/tables10.0.0.go | 2255 + .../golang.org/x/text/cases/tables11.0.0.go | 2316 + .../golang.org/x/text/cases/tables12.0.0.go | 2359 + .../golang.org/x/text/cases/tables13.0.0.go | 2399 + .../golang.org/x/text/cases/tables15.0.0.go | 2527 + vendor/golang.org/x/text/cases/tables9.0.0.go | 2215 + vendor/golang.org/x/text/cases/trieval.go | 217 + vendor/golang.org/x/text/internal/internal.go | 49 + .../x/text/internal/language/common.go | 16 + .../x/text/internal/language/compact.go | 29 + .../text/internal/language/compact/compact.go | 61 + .../internal/language/compact/language.go | 260 + .../text/internal/language/compact/parents.go | 120 + .../text/internal/language/compact/tables.go | 1015 + .../x/text/internal/language/compact/tags.go | 91 + .../x/text/internal/language/compose.go | 167 + .../x/text/internal/language/coverage.go | 28 + .../x/text/internal/language/language.go | 627 + .../x/text/internal/language/lookup.go | 412 + .../x/text/internal/language/match.go | 226 + .../x/text/internal/language/parse.go | 608 + .../x/text/internal/language/tables.go | 3494 + .../x/text/internal/language/tags.go | 48 + vendor/golang.org/x/text/internal/match.go | 67 + vendor/golang.org/x/text/internal/tag/tag.go | 100 + vendor/golang.org/x/text/language/coverage.go | 187 + vendor/golang.org/x/text/language/doc.go | 98 + vendor/golang.org/x/text/language/language.go | 605 + vendor/golang.org/x/text/language/match.go | 735 + vendor/golang.org/x/text/language/parse.go | 256 + vendor/golang.org/x/text/language/tables.go | 298 + vendor/golang.org/x/text/language/tags.go | 145 + vendor/golang.org/x/tools/LICENSE | 27 + vendor/golang.org/x/tools/PATENTS | 22 + .../x/tools/go/ast/astutil/enclosing.go | 634 + .../x/tools/go/ast/astutil/imports.go | 485 + .../x/tools/go/ast/astutil/rewrite.go | 486 + .../golang.org/x/tools/go/ast/astutil/util.go | 18 + vendor/golang.org/x/tools/imports/forward.go | 77 + .../x/tools/internal/event/core/event.go | 85 + .../x/tools/internal/event/core/export.go | 70 + .../x/tools/internal/event/core/fast.go | 77 + .../empty.s => tools/internal/event/doc.go} | 7 +- .../x/tools/internal/event/event.go | 127 + .../x/tools/internal/event/keys/keys.go | 564 + .../x/tools/internal/event/keys/standard.go | 22 + .../x/tools/internal/event/keys/util.go | 21 + .../x/tools/internal/event/label/label.go | 215 + .../x/tools/internal/gocommand/invoke.go | 548 + .../x/tools/internal/gocommand/vendor.go | 163 + .../x/tools/internal/gocommand/version.go | 71 + .../x/tools/internal/gopathwalk/walk.go | 337 + .../x/tools/internal/imports/fix.go | 1896 + .../x/tools/internal/imports/imports.go | 354 + .../x/tools/internal/imports/mod.go | 841 + .../x/tools/internal/imports/mod_cache.go | 331 + .../x/tools/internal/imports/sortimports.go | 297 + .../x/tools/internal/stdlib/manifest.go | 17320 + .../x/tools/internal/stdlib/stdlib.go | 97 + vendor/gopkg.in/yaml.v2/.travis.yml | 17 + vendor/gopkg.in/yaml.v2/LICENSE | 201 + vendor/gopkg.in/yaml.v2/LICENSE.libyaml | 31 + vendor/gopkg.in/yaml.v2/NOTICE | 13 + vendor/gopkg.in/yaml.v2/README.md | 133 + vendor/gopkg.in/yaml.v2/apic.go | 744 + vendor/gopkg.in/yaml.v2/decode.go | 815 + vendor/gopkg.in/yaml.v2/emitterc.go | 1685 + vendor/gopkg.in/yaml.v2/encode.go | 390 + vendor/gopkg.in/yaml.v2/parserc.go | 1095 + vendor/gopkg.in/yaml.v2/readerc.go | 412 + vendor/gopkg.in/yaml.v2/resolve.go | 258 + vendor/gopkg.in/yaml.v2/scannerc.go | 2711 + vendor/gopkg.in/yaml.v2/sorter.go | 113 + vendor/gopkg.in/yaml.v2/writerc.go | 26 + vendor/gopkg.in/yaml.v2/yaml.go | 478 + vendor/gopkg.in/yaml.v2/yamlh.go | 739 + vendor/gopkg.in/yaml.v2/yamlprivateh.go | 173 + vendor/modules.txt | 133 +- 754 files changed, 421906 insertions(+), 4658 deletions(-) create mode 100644 cmd/dev_server/dev_server.go create mode 100644 cmd/dev_server/overrides.go create mode 100644 cmd/dev_server/projects.go create mode 100644 cmd/dev_server/start_server.go create mode 100644 internal/dev_server/README.md create mode 100644 internal/dev_server/adapters/api.go create mode 100644 internal/dev_server/adapters/middleware.go create mode 100644 internal/dev_server/adapters/mocks/api.go create mode 100644 internal/dev_server/adapters/mocks/sdk.go create mode 100644 internal/dev_server/adapters/mocks/utils.go create mode 100644 internal/dev_server/adapters/sdk.go create mode 100644 internal/dev_server/api/api.yaml create mode 100644 internal/dev_server/api/oapi-codegen-cfg.yaml create mode 100644 internal/dev_server/api/server.gen.go create mode 100644 internal/dev_server/api/server.go create mode 100644 internal/dev_server/db/docs.go create mode 100644 internal/dev_server/db/sqlite.go create mode 100644 internal/dev_server/db/sqlite_test.go create mode 100644 internal/dev_server/dev_server.go create mode 100644 internal/dev_server/model/docs.go create mode 100644 internal/dev_server/model/error.go create mode 100644 internal/dev_server/model/events.go create mode 100644 internal/dev_server/model/flags_state.go create mode 100644 internal/dev_server/model/flags_state_test.go create mode 100644 internal/dev_server/model/mocks/observer.go create mode 100644 internal/dev_server/model/mocks/store.go create mode 100644 internal/dev_server/model/observer.go create mode 100644 internal/dev_server/model/observer_middleware.go create mode 100644 internal/dev_server/model/observer_test.go create mode 100644 internal/dev_server/model/override.go create mode 100644 internal/dev_server/model/override_test.go create mode 100644 internal/dev_server/model/project.go create mode 100644 internal/dev_server/model/project_test.go create mode 100644 internal/dev_server/model/store.go create mode 100644 internal/dev_server/sdk/constant_response.go create mode 100644 internal/dev_server/sdk/cors.go create mode 100644 internal/dev_server/sdk/docs.go create mode 100644 internal/dev_server/sdk/get_client_flags.go create mode 100644 internal/dev_server/sdk/get_server_flags.go create mode 100644 internal/dev_server/sdk/go_sdk_test.go create mode 100644 internal/dev_server/sdk/project_key_middleware.go create mode 100644 internal/dev_server/sdk/routes.go create mode 100644 internal/dev_server/sdk/server_flags.go create mode 100644 internal/dev_server/sdk/store_facade.go create mode 100644 internal/dev_server/sdk/stream_client_flags.go create mode 100644 internal/dev_server/sdk/stream_server_flags.go create mode 100644 internal/dev_server/sdk/streaming.go create mode 100644 internal/dev_server/ui/.gitignore create mode 100644 internal/dev_server/ui/.npmrc create mode 100644 internal/dev_server/ui/.prettierrc create mode 100644 internal/dev_server/ui/README.md create mode 100644 internal/dev_server/ui/asset_handler.go create mode 100644 internal/dev_server/ui/dist/index.html create mode 100644 internal/dev_server/ui/index.html create mode 100644 internal/dev_server/ui/package-lock.json create mode 100644 internal/dev_server/ui/package.json create mode 100644 internal/dev_server/ui/src/App.css create mode 100644 internal/dev_server/ui/src/App.tsx create mode 100644 internal/dev_server/ui/src/IconProvider.tsx create mode 100644 internal/dev_server/ui/src/main.tsx create mode 100644 internal/dev_server/ui/src/vite-env.d.ts create mode 100644 internal/dev_server/ui/tsconfig.app.json create mode 100644 internal/dev_server/ui/tsconfig.json create mode 100644 internal/dev_server/ui/tsconfig.node.json create mode 100644 internal/dev_server/ui/vite.config.ts create mode 100644 tools.go create mode 100644 vendor/github.com/adrg/xdg/CODE_OF_CONDUCT.md create mode 100644 vendor/github.com/adrg/xdg/CONTRIBUTING.md create mode 100644 vendor/github.com/adrg/xdg/LICENSE create mode 100644 vendor/github.com/adrg/xdg/README.md create mode 100644 vendor/github.com/adrg/xdg/base_dirs.go create mode 100644 vendor/github.com/adrg/xdg/codecov.yml create mode 100644 vendor/github.com/adrg/xdg/doc.go create mode 100644 vendor/github.com/adrg/xdg/internal/pathutil/pathutil.go create mode 100644 vendor/github.com/adrg/xdg/internal/pathutil/pathutil_plan9.go create mode 100644 vendor/github.com/adrg/xdg/internal/pathutil/pathutil_unix.go create mode 100644 vendor/github.com/adrg/xdg/internal/pathutil/pathutil_windows.go create mode 100644 vendor/github.com/adrg/xdg/paths_darwin.go create mode 100644 vendor/github.com/adrg/xdg/paths_plan9.go create mode 100644 vendor/github.com/adrg/xdg/paths_unix.go create mode 100644 vendor/github.com/adrg/xdg/paths_windows.go create mode 100644 vendor/github.com/adrg/xdg/user_dirs.go create mode 100644 vendor/github.com/adrg/xdg/xdg.go create mode 100644 vendor/github.com/apapsch/go-jsonmerge/v2/.editorconfig create mode 100644 vendor/github.com/apapsch/go-jsonmerge/v2/.gitattributes create mode 100644 vendor/github.com/apapsch/go-jsonmerge/v2/.gitignore create mode 100644 vendor/github.com/apapsch/go-jsonmerge/v2/.gitlab-ci.yml create mode 100644 vendor/github.com/apapsch/go-jsonmerge/v2/.travis.yml create mode 100644 vendor/github.com/apapsch/go-jsonmerge/v2/LICENSE create mode 100644 vendor/github.com/apapsch/go-jsonmerge/v2/README.md create mode 100644 vendor/github.com/apapsch/go-jsonmerge/v2/build.cmd create mode 100644 vendor/github.com/apapsch/go-jsonmerge/v2/build.sh create mode 100644 vendor/github.com/apapsch/go-jsonmerge/v2/doc.go create mode 100644 vendor/github.com/apapsch/go-jsonmerge/v2/merge.go create mode 100644 vendor/github.com/felixge/httpsnoop/.gitignore create mode 100644 vendor/github.com/felixge/httpsnoop/.travis.yml create mode 100644 vendor/github.com/felixge/httpsnoop/LICENSE.txt create mode 100644 vendor/github.com/felixge/httpsnoop/Makefile create mode 100644 vendor/github.com/felixge/httpsnoop/README.md create mode 100644 vendor/github.com/felixge/httpsnoop/capture_metrics.go create mode 100644 vendor/github.com/felixge/httpsnoop/docs.go create mode 100644 vendor/github.com/felixge/httpsnoop/wrap_generated_gteq_1.8.go create mode 100644 vendor/github.com/felixge/httpsnoop/wrap_generated_lt_1.8.go delete mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/testdata/circularRef/base.yml delete mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/testdata/circularRef/other.yml delete mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Bar.yml delete mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Cat.yml delete mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Foo.yml delete mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Foo/Foo2.yml delete mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/models/error.yaml delete mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/issue615.yml delete mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/openapi.yml delete mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/openapi.yml.internalized.yml delete mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/parameters/number.yml delete mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/paths/foo.yml create mode 100644 vendor/github.com/gorilla/handlers/.editorconfig create mode 100644 vendor/github.com/gorilla/handlers/.gitignore create mode 100644 vendor/github.com/gorilla/handlers/LICENSE create mode 100644 vendor/github.com/gorilla/handlers/Makefile create mode 100644 vendor/github.com/gorilla/handlers/README.md create mode 100644 vendor/github.com/gorilla/handlers/canonical.go create mode 100644 vendor/github.com/gorilla/handlers/compress.go create mode 100644 vendor/github.com/gorilla/handlers/cors.go create mode 100644 vendor/github.com/gorilla/handlers/doc.go create mode 100644 vendor/github.com/gorilla/handlers/handlers.go create mode 100644 vendor/github.com/gorilla/handlers/logging.go create mode 100644 vendor/github.com/gorilla/handlers/proxy_headers.go create mode 100644 vendor/github.com/gorilla/handlers/recovery.go create mode 100644 vendor/github.com/gorilla/mux/.editorconfig create mode 100644 vendor/github.com/gorilla/mux/.gitignore create mode 100644 vendor/github.com/gorilla/mux/LICENSE create mode 100644 vendor/github.com/gorilla/mux/Makefile create mode 100644 vendor/github.com/gorilla/mux/README.md create mode 100644 vendor/github.com/gorilla/mux/doc.go create mode 100644 vendor/github.com/gorilla/mux/middleware.go create mode 100644 vendor/github.com/gorilla/mux/mux.go create mode 100644 vendor/github.com/gorilla/mux/regexp.go create mode 100644 vendor/github.com/gorilla/mux/route.go create mode 100644 vendor/github.com/gorilla/mux/test_helpers.go create mode 100644 vendor/github.com/gregjones/httpcache/.travis.yml create mode 100644 vendor/github.com/gregjones/httpcache/LICENSE.txt create mode 100644 vendor/github.com/gregjones/httpcache/README.md create mode 100644 vendor/github.com/gregjones/httpcache/httpcache.go create mode 100644 vendor/github.com/launchdarkly/ccache/.gitignore create mode 100644 vendor/github.com/launchdarkly/ccache/Makefile create mode 100644 vendor/github.com/launchdarkly/ccache/bucket.go create mode 100644 vendor/github.com/launchdarkly/ccache/cache.go create mode 100644 vendor/github.com/launchdarkly/ccache/configuration.go create mode 100644 vendor/github.com/launchdarkly/ccache/item.go create mode 100644 vendor/github.com/launchdarkly/ccache/layeredbucket.go create mode 100644 vendor/github.com/launchdarkly/ccache/layeredcache.go create mode 100644 vendor/github.com/launchdarkly/ccache/license.txt create mode 100644 vendor/github.com/launchdarkly/ccache/readme.md create mode 100644 vendor/github.com/launchdarkly/ccache/secondarycache.go create mode 100644 vendor/github.com/launchdarkly/eventsource/.gitignore create mode 100644 vendor/github.com/launchdarkly/eventsource/.golangci.yml create mode 100644 vendor/github.com/launchdarkly/eventsource/CHANGELOG.md create mode 100644 vendor/github.com/launchdarkly/eventsource/CONTRIBUTING.md create mode 100644 vendor/github.com/launchdarkly/eventsource/LICENSE.txt create mode 100644 vendor/github.com/launchdarkly/eventsource/Makefile create mode 100644 vendor/github.com/launchdarkly/eventsource/README.md create mode 100644 vendor/github.com/launchdarkly/eventsource/check_redirect.go create mode 100644 vendor/github.com/launchdarkly/eventsource/check_redirect_go1.8.go create mode 100644 vendor/github.com/launchdarkly/eventsource/decoder.go create mode 100644 vendor/github.com/launchdarkly/eventsource/encoder.go create mode 100644 vendor/github.com/launchdarkly/eventsource/interface.go create mode 100644 vendor/github.com/launchdarkly/eventsource/normalise.go create mode 100644 vendor/github.com/launchdarkly/eventsource/repository.go create mode 100644 vendor/github.com/launchdarkly/eventsource/retry_delay.go create mode 100644 vendor/github.com/launchdarkly/eventsource/server.go create mode 100644 vendor/github.com/launchdarkly/eventsource/stream.go create mode 100644 vendor/github.com/launchdarkly/eventsource/stream_options.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/LICENSE.txt create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jreader/errors.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jreader/interfaces.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jreader/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jreader/reader.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jreader/reader_array.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jreader/reader_init_default.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jreader/reader_init_easyjson.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jreader/reader_object.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jreader/reader_unmarshal.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jreader/token_reader_default.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jreader/token_reader_easyjson.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jwriter/interfaces.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jwriter/no_op_writer.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jwriter/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jwriter/streamable_buffer.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jwriter/test_behavior_default.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jwriter/test_behavior_easyjson.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jwriter/token_writer_default.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jwriter/token_writer_easyjson.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jwriter/writer.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jwriter/writer_array.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jwriter/writer_init_default.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jwriter/writer_init_easyjson.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jwriter/writer_marshal.go create mode 100644 vendor/github.com/launchdarkly/go-jsonstream/v3/jwriter/writer_object.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/LICENSE.txt create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldattr/constants.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldattr/package.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldattr/ref.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldcontext/builder_multi.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldcontext/builder_simple.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldcontext/constants.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldcontext/constructors.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldcontext/context.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldcontext/context_easyjson.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldcontext/context_marshaling.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldcontext/context_serialization.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldcontext/context_unmarshaling.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldcontext/event_output_context.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldcontext/kind.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldcontext/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/lderrors/attribute_errors.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/lderrors/context_errors.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/lderrors/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldlog/logging.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldlog/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldmigration/ldmigration_info.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldmigration/migration.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldreason/detail.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldreason/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldreason/reason.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldreason/reason_serialization_easyjson.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldsampling/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldsampling/sampler.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldtime/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldtime/unix_millis.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldvalue/constants.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldvalue/interfaces.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldvalue/optional_bool.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldvalue/optional_int.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldvalue/optional_string.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldvalue/optionals_generic.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldvalue/optionals_json_conversion.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldvalue/optionals_json_easyjson_conversion.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldvalue/optionals_text_conversion.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldvalue/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldvalue/value_array.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldvalue/value_base.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldvalue/value_complex_types.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldvalue/value_json_conversion.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldvalue/value_json_easyjson_conversion.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldvalue/value_map.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-common/v3/ldvalue/value_text_conversion.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/.gitignore create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/.golangci.yml create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/CHANGELOG.md create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/CODEOWNERS create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/CONTRIBUTING.md create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/LICENSE.txt create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/Makefile create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/README.md create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/config.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/context_formatter.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/diagnostic_events.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/event_processor.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/event_sender.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/event_summarizer.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/events_output.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/inputs.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/interfaces.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/lru_cache.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/null_event_processor.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/outbox.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-sdk-events/v3/util.go create mode 100644 vendor/github.com/launchdarkly/go-semver/.gitignore create mode 100644 vendor/github.com/launchdarkly/go-semver/CHANGELOG.md create mode 100644 vendor/github.com/launchdarkly/go-semver/CONTRIBUTING.md create mode 100644 vendor/github.com/launchdarkly/go-semver/LICENSE.txt create mode 100644 vendor/github.com/launchdarkly/go-semver/Makefile create mode 100644 vendor/github.com/launchdarkly/go-semver/README.md create mode 100644 vendor/github.com/launchdarkly/go-semver/compare.go create mode 100644 vendor/github.com/launchdarkly/go-semver/parse.go create mode 100644 vendor/github.com/launchdarkly/go-semver/scan.go create mode 100644 vendor/github.com/launchdarkly/go-semver/syntax.go create mode 100644 vendor/github.com/launchdarkly/go-semver/values_generator.go create mode 100644 vendor/github.com/launchdarkly/go-semver/version.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/.gitignore create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/.golangci.yml create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/CHANGELOG.md create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/CODEOWNERS create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/CONTRIBUTING.md create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/LICENSE.txt create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/Makefile create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/README.md create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/SECURITY.md create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/errors.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/evaluator.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/evaluator_bucketing.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/evaluator_clause.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/evaluator_options.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/evaluator_segment.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/interfaces.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/internal/local_buffer.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/internal/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/internal/parse_hex.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/ldmodel/eval_accessors.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/ldmodel/model_flag.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/ldmodel/model_marshal.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/ldmodel/model_segment.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/ldmodel/model_serialization.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/ldmodel/model_serialization_easyjson.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/ldmodel/model_unmarshal.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/ldmodel/operators.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/ldmodel/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/ldmodel/parse_time.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/ldmodel/parse_values.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/ldmodel/preprocess.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/ldmodel/type_conversions.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk-evaluation/v3/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/.gitignore create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/.golangci.yml create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/.release-please-manifest.json create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/.sdk_metadata.json create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/CHANGELOG.md create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/CODEOWNERS create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/CONTRIBUTING.md create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/LICENSE.txt create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/Makefile create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/README.md create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/SECURITY.md create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/client_context_from_config.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/config.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/interfaces/application_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/interfaces/big_segment_store_status_provider.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/interfaces/client_interface.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/interfaces/data_source_status_provider.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/interfaces/data_store_status_provider.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/interfaces/flag_tracker.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/interfaces/flagstate/flags_state.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/interfaces/flagstate/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/interfaces/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/interfaces/service_endpoints.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/bigsegments/big_segment_store_status_provider_impl.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/bigsegments/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/bigsegments/user_hash.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/broadcasters.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/client_context_impl.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/concurrent.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datakinds/data_kind_internal.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datakinds/data_kinds_impl.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datakinds/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datasource/data_model_dependencies.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datasource/data_source_status_provider_impl.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datasource/data_source_update_sink_impl.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datasource/helpers.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datasource/null_data_source.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datasource/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datasource/polling_data_source.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datasource/polling_http_request.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datasource/streaming_data_source.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datasource/streaming_data_source_events.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datastore/data_store_eval_impl.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datastore/data_store_status_poller.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datastore/data_store_status_provider_impl.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datastore/data_store_update_sink_impl.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datastore/in_memory_data_store_impl.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datastore/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/datastore/persistent_data_store_wrapper.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/endpoints/configure_endpoints.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/endpoints/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/endpoints/standard_endpoints.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/flag_tracker_impl.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/hooks/evaluation_execution.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/hooks/iterator.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/hooks/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/hooks/runner.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/log_messages.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/internal/version.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldclient.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldclient_events.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldcomponents/big_segments_configuration_builder.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldcomponents/external_updates_data_source.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldcomponents/http_configuration_builder.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldcomponents/in_memory_data_store.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldcomponents/logging_configuration_builder.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldcomponents/no_events.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldcomponents/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldcomponents/persistent_data_store_builder.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldcomponents/polling_data_source_builder.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldcomponents/send_events.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldcomponents/service_endpoints.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldcomponents/streaming_data_source_builder.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldhooks/evaluation_series_context.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldhooks/evaluation_series_data.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldhooks/hooks.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldhooks/metadata.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldhooks/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/ldhttp/http_transport.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/migration_op_tracker.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/migrator.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/migrator_base.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/migrator_builder.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/release-please-config.json create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/server_side_diagnostics.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/big_segments.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/client_context.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/component_configurer.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/data_source.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/data_source_update_sink.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/data_store.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/data_store_update_sink.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/diagnostic_description.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/http_configuration.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/ldstoreimpl/big_segment_store_wrapper.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/ldstoreimpl/big_segments_config_extra.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/ldstoreimpl/big_segments_membership.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/ldstoreimpl/data_kinds.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/ldstoreimpl/data_store_eval.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/ldstoreimpl/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/ldstoretypes/data_store_types.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/ldstoretypes/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/logging_configuration.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/package_info.go create mode 100644 vendor/github.com/launchdarkly/go-server-sdk/v7/subsystems/persistent_data_store.go create mode 100644 vendor/github.com/mattn/go-sqlite3/.codecov.yml create mode 100644 vendor/github.com/mattn/go-sqlite3/.gitignore create mode 100644 vendor/github.com/mattn/go-sqlite3/LICENSE create mode 100644 vendor/github.com/mattn/go-sqlite3/README.md create mode 100644 vendor/github.com/mattn/go-sqlite3/backup.go create mode 100644 vendor/github.com/mattn/go-sqlite3/callback.go create mode 100644 vendor/github.com/mattn/go-sqlite3/convert.go create mode 100644 vendor/github.com/mattn/go-sqlite3/doc.go create mode 100644 vendor/github.com/mattn/go-sqlite3/error.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3-binding.c create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3-binding.h create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_context.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_func_crypt.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_go18.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_libsqlite3.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_load_extension.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_load_extension_omit.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_allow_uri_authority.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_app_armor.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_column_metadata.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_foreign_keys.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_fts5.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_icu.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_introspect.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_math_functions.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_os_trace.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_preupdate.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_preupdate_hook.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_preupdate_omit.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_secure_delete.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_secure_delete_fast.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_serialize.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_serialize_omit.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_stat4.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_unlock_notify.c create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_unlock_notify.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_userauth.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_userauth_omit.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vacuum_full.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vacuum_incr.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vtable.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_other.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_solaris.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_trace.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_type.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_usleep_windows.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_windows.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3ext.h create mode 100644 vendor/github.com/mattn/go-sqlite3/static_mock.go create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/LICENSE create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen/oapi-codegen.go create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/codegen.go create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/configuration.go create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/extension.go create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/externalref.go create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/filter.go create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/inline.go create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/merge_schemas.go create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/merge_schemas_v1.go create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/operations.go create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/prune.go create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/schema.go create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/template_helpers.go create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/additional-properties.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/chi/chi-handler.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/chi/chi-interface.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/chi/chi-middleware.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/client-with-responses.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/client.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/constants.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/echo/echo-interface.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/echo/echo-register.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/echo/echo-wrappers.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/fiber/fiber-handler.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/fiber/fiber-interface.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/fiber/fiber-middleware.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/gin/gin-interface.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/gin/gin-register.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/gin/gin-wrappers.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/gorilla/gorilla-interface.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/gorilla/gorilla-middleware.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/gorilla/gorilla-register.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/imports.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/inline.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/iris/iris-handler.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/iris/iris-interface.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/iris/iris-middleware.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/param-types.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/request-bodies.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/stdhttp/std-http-handler.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/stdhttp/std-http-interface.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/stdhttp/std-http-middleware.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/strict/strict-echo.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/strict/strict-fiber-interface.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/strict/strict-fiber.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/strict/strict-gin.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/strict/strict-http.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/strict/strict-interface.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/strict/strict-iris-interface.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/strict/strict-iris.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/strict/strict-responses.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/typedef.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/union-and-additional-properties.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/templates/union.tmpl create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/test_schema.json create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/test_spec.yaml create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen/utils.go create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/util/inputmapping.go create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/util/isjson.go create mode 100644 vendor/github.com/oapi-codegen/oapi-codegen/v2/pkg/util/loader.go create mode 100644 vendor/github.com/oapi-codegen/runtime/.gitignore create mode 100644 vendor/github.com/oapi-codegen/runtime/LICENSE create mode 100644 vendor/github.com/oapi-codegen/runtime/Makefile create mode 100644 vendor/github.com/oapi-codegen/runtime/README.md create mode 100644 vendor/github.com/oapi-codegen/runtime/bind.go create mode 100644 vendor/github.com/oapi-codegen/runtime/bindform.go create mode 100644 vendor/github.com/oapi-codegen/runtime/bindparam.go create mode 100644 vendor/github.com/oapi-codegen/runtime/bindstring.go create mode 100644 vendor/github.com/oapi-codegen/runtime/deepobject.go create mode 100644 vendor/github.com/oapi-codegen/runtime/jsonmerge.go create mode 100644 vendor/github.com/oapi-codegen/runtime/renovate.json create mode 100644 vendor/github.com/oapi-codegen/runtime/strictmiddleware/nethttp/main.go create mode 100644 vendor/github.com/oapi-codegen/runtime/styleparam.go create mode 100644 vendor/github.com/oapi-codegen/runtime/types/date.go create mode 100644 vendor/github.com/oapi-codegen/runtime/types/email.go create mode 100644 vendor/github.com/oapi-codegen/runtime/types/file.go create mode 100644 vendor/github.com/oapi-codegen/runtime/types/regexes.go create mode 100644 vendor/github.com/oapi-codegen/runtime/types/uuid.go create mode 100644 vendor/github.com/patrickmn/go-cache/CONTRIBUTORS create mode 100644 vendor/github.com/patrickmn/go-cache/LICENSE create mode 100644 vendor/github.com/patrickmn/go-cache/README.md create mode 100644 vendor/github.com/patrickmn/go-cache/cache.go create mode 100644 vendor/github.com/patrickmn/go-cache/sharded.go create mode 100644 vendor/go.uber.org/mock/AUTHORS create mode 100644 vendor/go.uber.org/mock/LICENSE create mode 100644 vendor/go.uber.org/mock/gomock/call.go create mode 100644 vendor/go.uber.org/mock/gomock/callset.go create mode 100644 vendor/go.uber.org/mock/gomock/controller.go create mode 100644 vendor/go.uber.org/mock/gomock/doc.go create mode 100644 vendor/go.uber.org/mock/gomock/matchers.go create mode 100644 vendor/go.uber.org/mock/mockgen/generic_go118.go create mode 100644 vendor/go.uber.org/mock/mockgen/generic_notgo118.go create mode 100644 vendor/go.uber.org/mock/mockgen/mockgen.go create mode 100644 vendor/go.uber.org/mock/mockgen/model/model.go create mode 100644 vendor/go.uber.org/mock/mockgen/parse.go create mode 100644 vendor/go.uber.org/mock/mockgen/reflect.go create mode 100644 vendor/go.uber.org/mock/mockgen/version.go create mode 100644 vendor/golang.org/x/exp/maps/maps.go create mode 100644 vendor/golang.org/x/mod/LICENSE create mode 100644 vendor/golang.org/x/mod/PATENTS create mode 100644 vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go create mode 100644 vendor/golang.org/x/mod/modfile/print.go create mode 100644 vendor/golang.org/x/mod/modfile/read.go create mode 100644 vendor/golang.org/x/mod/modfile/rule.go create mode 100644 vendor/golang.org/x/mod/modfile/work.go create mode 100644 vendor/golang.org/x/mod/module/module.go create mode 100644 vendor/golang.org/x/mod/module/pseudo.go create mode 100644 vendor/golang.org/x/mod/semver/semver.go create mode 100644 vendor/golang.org/x/sync/singleflight/singleflight.go create mode 100644 vendor/golang.org/x/sys/unix/bpxsvc_zos.go create mode 100644 vendor/golang.org/x/sys/unix/bpxsvc_zos.s delete mode 100644 vendor/golang.org/x/sys/unix/epoll_zos.go delete mode 100644 vendor/golang.org/x/sys/unix/fstatfs_zos.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_zos.go create mode 100644 vendor/golang.org/x/sys/unix/symaddr_zos_s390x.s create mode 100644 vendor/golang.org/x/sys/unix/zsymaddr_zos_s390x.s create mode 100644 vendor/golang.org/x/text/cases/cases.go create mode 100644 vendor/golang.org/x/text/cases/context.go create mode 100644 vendor/golang.org/x/text/cases/fold.go create mode 100644 vendor/golang.org/x/text/cases/icu.go create mode 100644 vendor/golang.org/x/text/cases/info.go create mode 100644 vendor/golang.org/x/text/cases/map.go create mode 100644 vendor/golang.org/x/text/cases/tables10.0.0.go create mode 100644 vendor/golang.org/x/text/cases/tables11.0.0.go create mode 100644 vendor/golang.org/x/text/cases/tables12.0.0.go create mode 100644 vendor/golang.org/x/text/cases/tables13.0.0.go create mode 100644 vendor/golang.org/x/text/cases/tables15.0.0.go create mode 100644 vendor/golang.org/x/text/cases/tables9.0.0.go create mode 100644 vendor/golang.org/x/text/cases/trieval.go create mode 100644 vendor/golang.org/x/text/internal/internal.go create mode 100644 vendor/golang.org/x/text/internal/language/common.go create mode 100644 vendor/golang.org/x/text/internal/language/compact.go create mode 100644 vendor/golang.org/x/text/internal/language/compact/compact.go create mode 100644 vendor/golang.org/x/text/internal/language/compact/language.go create mode 100644 vendor/golang.org/x/text/internal/language/compact/parents.go create mode 100644 vendor/golang.org/x/text/internal/language/compact/tables.go create mode 100644 vendor/golang.org/x/text/internal/language/compact/tags.go create mode 100644 vendor/golang.org/x/text/internal/language/compose.go create mode 100644 vendor/golang.org/x/text/internal/language/coverage.go create mode 100644 vendor/golang.org/x/text/internal/language/language.go create mode 100644 vendor/golang.org/x/text/internal/language/lookup.go create mode 100644 vendor/golang.org/x/text/internal/language/match.go create mode 100644 vendor/golang.org/x/text/internal/language/parse.go create mode 100644 vendor/golang.org/x/text/internal/language/tables.go create mode 100644 vendor/golang.org/x/text/internal/language/tags.go create mode 100644 vendor/golang.org/x/text/internal/match.go create mode 100644 vendor/golang.org/x/text/internal/tag/tag.go create mode 100644 vendor/golang.org/x/text/language/coverage.go create mode 100644 vendor/golang.org/x/text/language/doc.go create mode 100644 vendor/golang.org/x/text/language/language.go create mode 100644 vendor/golang.org/x/text/language/match.go create mode 100644 vendor/golang.org/x/text/language/parse.go create mode 100644 vendor/golang.org/x/text/language/tables.go create mode 100644 vendor/golang.org/x/text/language/tags.go create mode 100644 vendor/golang.org/x/tools/LICENSE create mode 100644 vendor/golang.org/x/tools/PATENTS create mode 100644 vendor/golang.org/x/tools/go/ast/astutil/enclosing.go create mode 100644 vendor/golang.org/x/tools/go/ast/astutil/imports.go create mode 100644 vendor/golang.org/x/tools/go/ast/astutil/rewrite.go create mode 100644 vendor/golang.org/x/tools/go/ast/astutil/util.go create mode 100644 vendor/golang.org/x/tools/imports/forward.go create mode 100644 vendor/golang.org/x/tools/internal/event/core/event.go create mode 100644 vendor/golang.org/x/tools/internal/event/core/export.go create mode 100644 vendor/golang.org/x/tools/internal/event/core/fast.go rename vendor/golang.org/x/{sys/windows/empty.s => tools/internal/event/doc.go} (51%) create mode 100644 vendor/golang.org/x/tools/internal/event/event.go create mode 100644 vendor/golang.org/x/tools/internal/event/keys/keys.go create mode 100644 vendor/golang.org/x/tools/internal/event/keys/standard.go create mode 100644 vendor/golang.org/x/tools/internal/event/keys/util.go create mode 100644 vendor/golang.org/x/tools/internal/event/label/label.go create mode 100644 vendor/golang.org/x/tools/internal/gocommand/invoke.go create mode 100644 vendor/golang.org/x/tools/internal/gocommand/vendor.go create mode 100644 vendor/golang.org/x/tools/internal/gocommand/version.go create mode 100644 vendor/golang.org/x/tools/internal/gopathwalk/walk.go create mode 100644 vendor/golang.org/x/tools/internal/imports/fix.go create mode 100644 vendor/golang.org/x/tools/internal/imports/imports.go create mode 100644 vendor/golang.org/x/tools/internal/imports/mod.go create mode 100644 vendor/golang.org/x/tools/internal/imports/mod_cache.go create mode 100644 vendor/golang.org/x/tools/internal/imports/sortimports.go create mode 100644 vendor/golang.org/x/tools/internal/stdlib/manifest.go create mode 100644 vendor/golang.org/x/tools/internal/stdlib/stdlib.go create mode 100644 vendor/gopkg.in/yaml.v2/.travis.yml create mode 100644 vendor/gopkg.in/yaml.v2/LICENSE create mode 100644 vendor/gopkg.in/yaml.v2/LICENSE.libyaml create mode 100644 vendor/gopkg.in/yaml.v2/NOTICE create mode 100644 vendor/gopkg.in/yaml.v2/README.md create mode 100644 vendor/gopkg.in/yaml.v2/apic.go create mode 100644 vendor/gopkg.in/yaml.v2/decode.go create mode 100644 vendor/gopkg.in/yaml.v2/emitterc.go create mode 100644 vendor/gopkg.in/yaml.v2/encode.go create mode 100644 vendor/gopkg.in/yaml.v2/parserc.go create mode 100644 vendor/gopkg.in/yaml.v2/readerc.go create mode 100644 vendor/gopkg.in/yaml.v2/resolve.go create mode 100644 vendor/gopkg.in/yaml.v2/scannerc.go create mode 100644 vendor/gopkg.in/yaml.v2/sorter.go create mode 100644 vendor/gopkg.in/yaml.v2/writerc.go create mode 100644 vendor/gopkg.in/yaml.v2/yaml.go create mode 100644 vendor/gopkg.in/yaml.v2/yamlh.go create mode 100644 vendor/gopkg.in/yaml.v2/yamlprivateh.go diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 7d074e57..e340ae31 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -31,4 +31,4 @@ The version of this CLI that you are using. For instance, Ubuntu 16.04, Windows 10, or macOS 14.4. **Additional context** -Add any other context about the problem here. \ No newline at end of file +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index c76148d1..c30165cb 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -17,4 +17,4 @@ A clear and concise description of what you want to happen. A clear and concise description of any alternative solutions or features you've considered. **Additional context** -Add any other context about the feature request here. \ No newline at end of file +Add any other context about the feature request here. diff --git a/.github/actions/publish/action.yml b/.github/actions/publish/action.yml index 413366fe..42633b1e 100644 --- a/.github/actions/publish/action.yml +++ b/.github/actions/publish/action.yml @@ -14,6 +14,7 @@ inputs: tag: description: 'Tag to upload artifacts to.' required: true + outputs: hashes: description: sha256sum hashes of built artifacts @@ -29,17 +30,45 @@ runs: with: platforms: linux/amd64,linux/arm64/v8,linux/arm/v7,linux/386 - name: Login to Docker + shell: bash + run: echo $DOCKER_TOKEN | docker login --username $DOCKER_USERNAME --password-stdin + + - name: Set up goreleaser + # Note: that we're unable to use the normal goreleaser actions and have to use this docker image. + # This is because the dev server piece of the CLI uses SQLite which requires CGO and cross compilation. + # We're using the goreleaser-cross image to facilitate this. See also: https://github.com/goreleaser/goreleaser-cross shell: bash run: | - echo $DOCKER_TOKEN | docker login --username $DOCKER_USERNAME --password-stdin + CONTAINER_ID="$( + docker run --detach \ + --volume "$PWD:$PWD" \ + --entrypoint tail \ + ghcr.io/goreleaser/goreleaser-cross:latest \ + -f /dev/null + )" + docker exec --tty "$CONTAINER_ID" dpkg --add-architecture i386 + docker exec --tty "$CONTAINER_ID" apt-get update + docker exec --tty "$CONTAINER_ID" apt-get install --no-install-recommends -y -q crossbuild-essential-i386 + docker exec --tty "$CONTAINER_ID" git config --global --add safe.directory '*' + echo "CONTAINER_ID=$CONTAINER_ID" >> "$GITHUB_ENV" + - name: Run Goreleaser - uses: goreleaser/goreleaser-action@v5 - with: - version: latest - args: release ${{ inputs.dry-run == 'true' && '--skip=publish' || '' }} --config .goreleaser.yaml + shell: bash + run: docker exec + --env GITHUB_TOKEN + --env HOMEBREW_DEPLOY_KEY + --workdir "$PWD" + --tty + "$CONTAINER_ID" + goreleaser release ${{ inputs.dry-run == 'true' && '--skip=publish' || '' }} --config .goreleaser.yaml env: GITHUB_TOKEN: ${{ inputs.token }} HOMEBREW_DEPLOY_KEY: ${{ inputs.homebrew-gh-secret }} + - name: Upload assets + uses: actions/upload-artifact@v4 + with: + name: ldcli + path: dist/* - name: Hash build artifacts for provenance id: hash shell: bash diff --git a/.gitignore b/.gitignore index b2125838..4b7275ac 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ dist/ *.log node_modules/ +devserver.db diff --git a/.goreleaser.yaml b/.goreleaser.yaml index e2daddf2..e4c0c7ff 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,3 +1,12 @@ +version: 2 + +project_name: ldcli + +env: + - GO111MODULE=on # Ensure we aren't using anything in GOPATH when building + - CGO_ENABLED=1 # Needed for SQLite support + - DOCKER_CLI_EXPERIMENTAL=enabled # This is on by default in docker versions >= 20.10 + builds: - id: ldcli binary: ldcli @@ -6,43 +15,68 @@ builds: - linux - windows goarch: - - 386 + - "386" - amd64 - arm64 + ldflags: + - -s # Omit all symbol information to minimize binary size + - -w # Omit DWARF to minimize binary size + - -X 'main.version={{.Version}}' + ignore: + - goos: darwin + goarch: "386" env: - - CGO_ENABLED=0 + # The below environment variables set up the c compiler toolchain for CGO. + # Templates are used to vary the toolchain based on OS & platform. + - TOOLCHAIN_BASE= + {{- if eq .Os "darwin" -}} + o + {{- if eq .Arch "arm64" -}}a{{- end -}} + 64-clang + {{- else -}} + {{- if eq .Os "windows" -}}/llvm-mingw/bin/{{- end -}} + {{- if eq .Arch "386" -}}i686{{- end -}} + {{- if eq .Arch "arm64" -}}aarch64{{- end -}} + {{- if eq .Arch "amd64" -}}x86_64{{- end -}} + - + {{- if eq .Os "windows" -}}w64-mingw32{{- end -}} + {{- if eq .Os "linux" -}}linux-gnu{{- end -}} + {{- end -}} + - CC={{ .Env.TOOLCHAIN_BASE }}{{ if ne .Os "darwin" }}-gcc{{ end }} + - CXX={{ .Env.TOOLCHAIN_BASE }}{{ if eq .Os "darwin" }}++{{ else }}-g++{{ end }} + dockers: # AMD64 - image_templates: - - "launchdarkly/ldcli:{{ .Version }}-amd64" - - "launchdarkly/ldcli:v{{ .Major }}-amd64" - - "launchdarkly/ldcli:latest-amd64" + - "launchdarkly/ldcli:{{ .Version }}-amd64" + - "launchdarkly/ldcli:v{{ .Major }}-amd64" + - "launchdarkly/ldcli:latest-amd64" goos: linux goarch: amd64 dockerfile: Dockerfile.goreleaser skip_push: false build_flag_templates: - - "--pull" - - "--platform=linux/amd64" + - "--pull" + - "--platform=linux/amd64" # ARM64v8 - image_templates: - - "launchdarkly/ldcli:{{ .Version }}-arm64v8" - - "launchdarkly/ldcli:v{{ .Major }}-arm64v8" - - "launchdarkly/ldcli:latest-arm64v8" + - "launchdarkly/ldcli:{{ .Version }}-arm64v8" + - "launchdarkly/ldcli:v{{ .Major }}-arm64v8" + - "launchdarkly/ldcli:latest-arm64v8" goos: linux goarch: arm64 dockerfile: Dockerfile.goreleaser skip_push: false build_flag_templates: - - "--pull" - - "--platform=linux/arm64/v8" + - "--pull" + - "--platform=linux/arm64/v8" docker_manifests: - name_template: "launchdarkly/ldcli:{{ .Version}}" skip_push: false image_templates: - - "launchdarkly/ldcli:{{ .Version }}-amd64" - - "launchdarkly/ldcli:{{ .Version }}-arm64v8" + - "launchdarkly/ldcli:{{ .Version }}-amd64" + - "launchdarkly/ldcli:{{ .Version }}-arm64v8" - name_template: "launchdarkly/ldcli:v{{ .Major }}" skip_push: false @@ -56,8 +90,7 @@ docker_manifests: - "launchdarkly/ldcli:latest-amd64" - "launchdarkly/ldcli:latest-arm64v8" brews: - - - name: ldcli + - name: ldcli description: "The official command line interface for managing LaunchDarkly feature flags." homepage: "https://launchdarkly.com" repository: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ac80f255..f7386d6b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,12 @@ repos: exclude: '^vendor/|^.circleci/image/tools' name: run-go-tests - id: discarded-stacktrace - exclude: '(^vendor/)' + exclude: '(^vendor/|.*gen\.go)' - id: go-mod-tidy - id: go-mod-verify - id: go-mod-vendor-no-change + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: end-of-file-fixer + exclude: '^vendor' diff --git a/Dockerfile.goreleaser b/Dockerfile.goreleaser index 6f220799..7f83c35d 100644 --- a/Dockerfile.goreleaser +++ b/Dockerfile.goreleaser @@ -1,8 +1,5 @@ FROM alpine:3.19.1 -RUN apk update -RUN apk add --no-cache git - COPY ldcli /ldcli LABEL homepage="https://www.launchdarkly.com" diff --git a/cmd/cliflags/flags.go b/cmd/cliflags/flags.go index baab681f..66fa9487 100644 --- a/cmd/cliflags/flags.go +++ b/cmd/cliflags/flags.go @@ -1,36 +1,44 @@ package cliflags const ( - BaseURIDefault = "https://app.launchdarkly.com" + BaseURIDefault = "https://app.launchdarkly.com" + DevStreamURIDefault = "https://stream.launchdarkly.com" + PortDefault = "8765" - AccessTokenFlag = "access-token" - AnalyticsOptOut = "analytics-opt-out" - BaseURIFlag = "base-uri" - DataFlag = "data" - EmailsFlag = "emails" - EnvironmentFlag = "environment" - FlagFlag = "flag" - OutputFlag = "output" - ProjectFlag = "project" - RoleFlag = "role" + AccessTokenFlag = "access-token" + AnalyticsOptOut = "analytics-opt-out" + BaseURIFlag = "base-uri" + DataFlag = "data" + DevStreamURIFlag = "dev-stream-uri" + EmailsFlag = "emails" + EnvironmentFlag = "environment" + FlagFlag = "flag" + OutputFlag = "output" + PortFlag = "port" + ProjectFlag = "project" + RoleFlag = "role" AccessTokenFlagDescription = "LaunchDarkly access token with write-level access" AnalyticsOptOutDescription = "Opt out of analytics tracking" BaseURIFlagDescription = "LaunchDarkly base URI" + DevStreamURIDescription = "Streaming service endpoint that the dev server uses to obtain authoritative flag data. This may be a LaunchDarkly or Relay Proxy endpoint" EnvironmentFlagDescription = "Default environment key" FlagFlagDescription = "Default feature flag key" OutputFlagDescription = "Command response output format in either JSON or plain text" + PortFlagDescription = "Port for the dev server to run on" ProjectFlagDescription = "Default project key" ) func AllFlagsHelp() map[string]string { return map[string]string{ - AccessTokenFlag: AccessTokenFlagDescription, - AnalyticsOptOut: AnalyticsOptOutDescription, - BaseURIFlag: BaseURIFlagDescription, - EnvironmentFlag: EnvironmentFlagDescription, - FlagFlag: FlagFlagDescription, - OutputFlag: OutputFlagDescription, - ProjectFlag: ProjectFlagDescription, + AccessTokenFlag: AccessTokenFlagDescription, + AnalyticsOptOut: AnalyticsOptOutDescription, + BaseURIFlag: BaseURIFlagDescription, + DevStreamURIFlag: DevStreamURIDescription, + EnvironmentFlag: EnvironmentFlagDescription, + FlagFlag: FlagFlagDescription, + OutputFlag: OutputFlagDescription, + PortFlag: PortFlagDescription, + ProjectFlag: ProjectFlagDescription, } } diff --git a/cmd/config/testdata/help.golden b/cmd/config/testdata/help.golden index ea1b4541..6745e12a 100644 --- a/cmd/config/testdata/help.golden +++ b/cmd/config/testdata/help.golden @@ -4,9 +4,11 @@ Supported settings: - `access-token`: LaunchDarkly access token with write-level access - `analytics-opt-out`: Opt out of analytics tracking - `base-uri`: LaunchDarkly base URI +- `dev-stream-uri`: Streaming service endpoint that the dev server uses to obtain authoritative flag data. This may be a LaunchDarkly or Relay Proxy endpoint - `environment`: Default environment key - `flag`: Default feature flag key - `output`: Command response output format in either JSON or plain text +- `port`: Port for the dev server to run on - `project`: Default project key Usage: diff --git a/cmd/dev_server/dev_server.go b/cmd/dev_server/dev_server.go new file mode 100644 index 00000000..947fdd2c --- /dev/null +++ b/cmd/dev_server/dev_server.go @@ -0,0 +1,60 @@ +package dev_server + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/launchdarkly/ldcli/cmd/cliflags" + resourcecmd "github.com/launchdarkly/ldcli/cmd/resources" + "github.com/launchdarkly/ldcli/internal/dev_server" + "github.com/launchdarkly/ldcli/internal/resources" +) + +func NewDevServerCmd(client resources.Client, ldClient dev_server.Client) *cobra.Command { + cmd := &cobra.Command{ + Use: "dev-server", + Short: "Development server", + Long: "Start and use a local development server for overriding flag values.", + } + + cmd.PersistentFlags().String( + cliflags.DevStreamURIFlag, + cliflags.DevStreamURIDefault, + cliflags.DevStreamURIDescription, + ) + _ = viper.BindPFlag(cliflags.DevStreamURIFlag, cmd.PersistentFlags().Lookup(cliflags.DevStreamURIFlag)) + + cmd.PersistentFlags().String( + cliflags.PortFlag, + cliflags.PortDefault, + cliflags.PortFlagDescription, + ) + + _ = viper.BindPFlag(cliflags.PortFlag, cmd.PersistentFlags().Lookup(cliflags.PortFlag)) + + // Add subcommands here + cmd.AddGroup(&cobra.Group{ID: "projects", Title: "Project commands:"}) + cmd.AddCommand(NewListProjectsCmd(client)) + cmd.AddCommand(NewGetProjectCmd(client)) + cmd.AddCommand(NewSyncProjectCmd(client)) + cmd.AddCommand(NewRemoveProjectCmd(client)) + cmd.AddCommand(NewAddProjectCmd(client)) + + cmd.AddGroup(&cobra.Group{ID: "overrides", Title: "Override commands:"}) + cmd.AddCommand(NewAddOverrideCmd(client)) + cmd.AddCommand(NewRemoveOverrideCmd(client)) + cmd.AddGroup(&cobra.Group{ID: "server", Title: "Server commands:"}) + + cmd.AddCommand(NewStartServerCmd(ldClient)) + cmd.AddCommand(NewUICmd()) + + cmd.SetUsageTemplate(resourcecmd.SubcommandUsageTemplate()) + + return cmd +} + +func getDevServerUrl() string { + return fmt.Sprintf("http://localhost:%s", viper.GetString(cliflags.PortFlag)) +} diff --git a/cmd/dev_server/overrides.go b/cmd/dev_server/overrides.go new file mode 100644 index 00000000..9230a7e4 --- /dev/null +++ b/cmd/dev_server/overrides.go @@ -0,0 +1,117 @@ +package dev_server + +import ( + "encoding/json" + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/launchdarkly/ldcli/cmd/cliflags" + resourcescmd "github.com/launchdarkly/ldcli/cmd/resources" + "github.com/launchdarkly/ldcli/cmd/validators" + "github.com/launchdarkly/ldcli/internal/output" + "github.com/launchdarkly/ldcli/internal/resources" +) + +func NewAddOverrideCmd(client resources.Client) *cobra.Command { + cmd := &cobra.Command{ + GroupID: "overrides", + Args: validators.Validate(), + Long: "override flag value with value provided in the body", + RunE: addOverride(client), + Short: "override flag value", + Use: "add-override", + } + + cmd.SetUsageTemplate(resourcescmd.SubcommandUsageTemplate()) + + cmd.Flags().String(cliflags.ProjectFlag, "", "The project key") + _ = cmd.MarkFlagRequired(cliflags.ProjectFlag) + _ = cmd.Flags().SetAnnotation(cliflags.ProjectFlag, "required", []string{"true"}) + _ = viper.BindPFlag(cliflags.ProjectFlag, cmd.Flags().Lookup(cliflags.ProjectFlag)) + + cmd.Flags().String(cliflags.FlagFlag, "", "The flag key") + _ = cmd.MarkFlagRequired(cliflags.FlagFlag) + _ = cmd.Flags().SetAnnotation(cliflags.FlagFlag, "required", []string{"true"}) + _ = viper.BindPFlag(cliflags.FlagFlag, cmd.Flags().Lookup(cliflags.FlagFlag)) + + cmd.Flags().String(cliflags.DataFlag, "", "flag value to override flag with. The json representation of the variation value") + _ = cmd.MarkFlagRequired(cliflags.DataFlag) + _ = cmd.Flags().SetAnnotation(cliflags.DataFlag, "required", []string{"true"}) + _ = viper.BindPFlag(cliflags.DataFlag, cmd.Flags().Lookup(cliflags.DataFlag)) + + return cmd +} + +func addOverride(client resources.Client) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + var data interface{} + err := json.Unmarshal([]byte(viper.GetString(cliflags.DataFlag)), &data) + if err != nil { + return err + } + + jsonData, err := json.Marshal(data) + if err != nil { + return err + } + + path := fmt.Sprintf("%s/dev/projects/%s/overrides/%s", getDevServerUrl(), viper.GetString(cliflags.ProjectFlag), viper.GetString(cliflags.FlagFlag)) + res, err := client.MakeUnauthenticatedRequest( + "PUT", + path, + jsonData, + ) + if err != nil { + return output.NewCmdOutputError(err, viper.GetString(cliflags.OutputFlag)) + } + + fmt.Fprint(cmd.OutOrStdout(), string(res)) + + return nil + } +} + +func NewRemoveOverrideCmd(client resources.Client) *cobra.Command { + cmd := &cobra.Command{ + GroupID: "overrides", + Args: validators.Validate(), + Long: "remove override for flag", + RunE: removeOverride(client), + Short: "remove override", + Use: "remove-override", + } + + cmd.SetUsageTemplate(resourcescmd.SubcommandUsageTemplate()) + + cmd.Flags().String(cliflags.ProjectFlag, "", "The project key") + _ = cmd.MarkFlagRequired(cliflags.ProjectFlag) + _ = cmd.Flags().SetAnnotation(cliflags.ProjectFlag, "required", []string{"true"}) + _ = viper.BindPFlag(cliflags.ProjectFlag, cmd.Flags().Lookup(cliflags.ProjectFlag)) + + cmd.Flags().String(cliflags.FlagFlag, "", "The flag key") + _ = cmd.MarkFlagRequired(cliflags.FlagFlag) + _ = cmd.Flags().SetAnnotation(cliflags.FlagFlag, "required", []string{"true"}) + _ = viper.BindPFlag(cliflags.FlagFlag, cmd.Flags().Lookup(cliflags.FlagFlag)) + + return cmd +} + +func removeOverride(client resources.Client) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + path := fmt.Sprintf("%s/dev/projects/%s/overrides/%s", getDevServerUrl(), viper.GetString(cliflags.ProjectFlag), viper.GetString(cliflags.FlagFlag)) + res, err := client.MakeUnauthenticatedRequest( + "DELETE", + path, + nil, + ) + if err != nil { + return output.NewCmdOutputError(err, viper.GetString(cliflags.OutputFlag)) + } + + fmt.Fprint(cmd.OutOrStdout(), string(res)) + + return nil + } +} diff --git a/cmd/dev_server/projects.go b/cmd/dev_server/projects.go new file mode 100644 index 00000000..887ab402 --- /dev/null +++ b/cmd/dev_server/projects.go @@ -0,0 +1,225 @@ +package dev_server + +import ( + "encoding/json" + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/launchdarkly/ldcli/cmd/cliflags" + resourcescmd "github.com/launchdarkly/ldcli/cmd/resources" + "github.com/launchdarkly/ldcli/cmd/validators" + "github.com/launchdarkly/ldcli/internal/output" + "github.com/launchdarkly/ldcli/internal/resources" +) + +func NewListProjectsCmd(client resources.Client) *cobra.Command { + cmd := &cobra.Command{ + GroupID: "projects", + Args: validators.Validate(), + Long: "lists all projects that have been configured for the dev server", + RunE: listProjects(client), + Short: "list all projects", + Use: "list-projects", + } + + cmd.SetUsageTemplate(resourcescmd.SubcommandUsageTemplate()) + + return cmd +} + +func listProjects(client resources.Client) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + + path := getDevServerUrl() + "/dev/projects" + res, err := client.MakeUnauthenticatedRequest( + "GET", + path, + nil, + ) + if err != nil { + return output.NewCmdOutputError(err, viper.GetString(cliflags.OutputFlag)) + } + + fmt.Fprint(cmd.OutOrStdout(), string(res)) + + return nil + } +} + +func NewGetProjectCmd(client resources.Client) *cobra.Command { + cmd := &cobra.Command{ + GroupID: "projects", + Args: validators.Validate(), + Long: "get the specified project and its configuration for syncing from the LaunchDarkly Service", + RunE: getProject(client), + Short: "get a project", + Use: "get-project", + } + + cmd.SetUsageTemplate(resourcescmd.SubcommandUsageTemplate()) + + cmd.Flags().String(cliflags.ProjectFlag, "", "The project key") + _ = cmd.MarkFlagRequired(cliflags.ProjectFlag) + _ = cmd.Flags().SetAnnotation(cliflags.ProjectFlag, "required", []string{"true"}) + _ = viper.BindPFlag(cliflags.ProjectFlag, cmd.Flags().Lookup(cliflags.ProjectFlag)) + + return cmd +} + +func getProject(client resources.Client) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + + path := getDevServerUrl() + "/dev/projects/" + viper.GetString(cliflags.ProjectFlag) + res, err := client.MakeUnauthenticatedRequest( + "GET", + path, + nil, + ) + if err != nil { + return output.NewCmdOutputError(err, viper.GetString(cliflags.OutputFlag)) + } + fmt.Fprint(cmd.OutOrStdout(), string(res)) + + return nil + } +} + +func NewSyncProjectCmd(client resources.Client) *cobra.Command { + cmd := &cobra.Command{ + GroupID: "projects", + Args: validators.Validate(), + Long: "sync the specified project and its flag configuration with the LaunchDarkly Service", + RunE: syncProject(client), + Short: "sync project", + Use: "sync-project", + } + + cmd.SetUsageTemplate(resourcescmd.SubcommandUsageTemplate()) + + cmd.Flags().String(cliflags.ProjectFlag, "", "The project key") + _ = cmd.MarkFlagRequired(cliflags.ProjectFlag) + _ = cmd.Flags().SetAnnotation(cliflags.ProjectFlag, "required", []string{"true"}) + _ = viper.BindPFlag(cliflags.ProjectFlag, cmd.Flags().Lookup(cliflags.ProjectFlag)) + + return cmd +} + +func syncProject(client resources.Client) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + + path := getDevServerUrl() + "/dev/projects/" + viper.GetString(cliflags.ProjectFlag) + "/sync" + res, err := client.MakeUnauthenticatedRequest( + "PATCH", + path, + nil, + ) + if err != nil { + return output.NewCmdOutputError(err, viper.GetString(cliflags.OutputFlag)) + } + fmt.Fprint(cmd.OutOrStdout(), string(res)) + + return nil + } +} + +func NewRemoveProjectCmd(client resources.Client) *cobra.Command { + cmd := &cobra.Command{ + GroupID: "projects", + Args: validators.Validate(), + Long: "remove the specified project from the dev server", + RunE: deleteProject(client), + Short: "remove a project", + Use: "remove-project", + } + + cmd.SetUsageTemplate(resourcescmd.SubcommandUsageTemplate()) + + cmd.Flags().String(cliflags.ProjectFlag, "", "The project key") + _ = cmd.MarkFlagRequired(cliflags.ProjectFlag) + _ = cmd.Flags().SetAnnotation(cliflags.ProjectFlag, "required", []string{"true"}) + _ = viper.BindPFlag(cliflags.ProjectFlag, cmd.Flags().Lookup(cliflags.ProjectFlag)) + + return cmd +} + +func deleteProject(client resources.Client) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + + path := getDevServerUrl() + "/dev/projects/" + viper.GetString(cliflags.ProjectFlag) + res, err := client.MakeUnauthenticatedRequest( + "DELETE", + path, + nil, + ) + if err != nil { + return output.NewCmdOutputError(err, viper.GetString(cliflags.OutputFlag)) + } + + fmt.Fprint(cmd.OutOrStdout(), string(res)) + + return nil + } +} + +func NewAddProjectCmd(client resources.Client) *cobra.Command { + cmd := &cobra.Command{ + GroupID: "projects", + Args: validators.Validate(), + Long: "Add the project to the dev server", + RunE: addProject(client), + Short: "add a project", + Use: "add-project", + } + + cmd.SetUsageTemplate(resourcescmd.SubcommandUsageTemplate()) + + cmd.Flags().String(cliflags.ProjectFlag, "", "The project key") + _ = cmd.MarkFlagRequired(cliflags.ProjectFlag) + _ = cmd.Flags().SetAnnotation(cliflags.ProjectFlag, "required", []string{"true"}) + _ = viper.BindPFlag(cliflags.ProjectFlag, cmd.Flags().Lookup(cliflags.ProjectFlag)) + + cmd.Flags().String("source", "", "environment to copy flag values from") + _ = cmd.MarkFlagRequired("source") + _ = cmd.Flags().SetAnnotation("source", "required", []string{"true"}) + _ = viper.BindPFlag("source", cmd.Flags().Lookup("source")) + + cmd.Flags().String("context", "", `Stringified JSON representation of your context object ex. {"user": { "email": "youremail@gmail.com", "username": "foo", "key": "bar"}}`) + _ = viper.BindPFlag("context", cmd.Flags().Lookup("context")) + + return cmd +} + +type postBody struct { + SourceEnvironmentKey string `json:"sourceEnvironmentKey"` + Context json.RawMessage `json:"context,omitempty"` +} + +func addProject(client resources.Client) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + body := postBody{SourceEnvironmentKey: viper.GetString("source")} + if viper.IsSet("context") { + body.Context = json.RawMessage(viper.GetString("context")) + } + + jsonData, err := json.Marshal(body) + if err != nil { + return err + } + + path := getDevServerUrl() + "/dev/projects/" + viper.GetString(cliflags.ProjectFlag) + res, err := client.MakeUnauthenticatedRequest( + "POST", + path, + jsonData, + ) + if err != nil { + return output.NewCmdOutputError(err, viper.GetString(cliflags.OutputFlag)) + } + + fmt.Fprint(cmd.OutOrStdout(), string(res)) + + return nil + } +} diff --git a/cmd/dev_server/start_server.go b/cmd/dev_server/start_server.go new file mode 100644 index 00000000..0f5d881b --- /dev/null +++ b/cmd/dev_server/start_server.go @@ -0,0 +1,86 @@ +package dev_server + +import ( + "context" + "errors" + "log" + "os/exec" + "runtime" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/launchdarkly/ldcli/cmd/cliflags" + resourcescmd "github.com/launchdarkly/ldcli/cmd/resources" + "github.com/launchdarkly/ldcli/cmd/validators" + "github.com/launchdarkly/ldcli/internal/dev_server" +) + +func NewStartServerCmd(client dev_server.Client) *cobra.Command { + cmd := &cobra.Command{ + GroupID: "server", + Args: validators.Validate(), + Long: "start the dev server", + RunE: startServer(client), + Short: "start the dev server", + Use: "start", + } + + cmd.SetUsageTemplate(resourcescmd.SubcommandUsageTemplate()) + + return cmd +} + +func startServer(client dev_server.Client) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + params := dev_server.ServerParams{ + AccessToken: viper.GetString(cliflags.AccessTokenFlag), + BaseURI: viper.GetString(cliflags.BaseURIFlag), + DevStreamURI: viper.GetString(cliflags.DevStreamURIFlag), + Port: viper.GetString(cliflags.PortFlag), + } + + client.RunServer(ctx, params) + + return nil + } +} + +func NewUICmd() *cobra.Command { + cmd := &cobra.Command{ + GroupID: "server", + Args: validators.Validate(), + Long: "open the dev ui in your default browser", + RunE: openUI(), + Short: "open the ui", + Use: "ui", + } + + cmd.SetUsageTemplate(resourcescmd.SubcommandUsageTemplate()) + + return cmd +} + +func openUI() func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + url := getDevServerUrl() + + var err error + switch runtime.GOOS { + case "linux": + err = exec.Command("xdg-open", url).Start() + case "windows": + err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() + case "darwin": + err = exec.Command("open", url).Start() + default: + err = errors.New("unsupported platform") + } + if err != nil { + log.Fatalf("Unable to open ui, %v", err) + } + + return nil + } +} diff --git a/cmd/resources/test_data/expected_template_data.json b/cmd/resources/test_data/expected_template_data.json index 21a43efb..dd5633b6 100644 --- a/cmd/resources/test_data/expected_template_data.json +++ b/cmd/resources/test_data/expected_template_data.json @@ -117,4 +117,4 @@ } } } -} \ No newline at end of file +} diff --git a/cmd/resources/test_data/test-openapi.json b/cmd/resources/test_data/test-openapi.json index d263a1b4..7661df9a 100644 --- a/cmd/resources/test_data/test-openapi.json +++ b/cmd/resources/test_data/test-openapi.json @@ -1320,4 +1320,4 @@ } } } -} \ No newline at end of file +} diff --git a/cmd/root.go b/cmd/root.go index 1416e3b7..fbc36290 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,12 +17,14 @@ import ( cmdAnalytics "github.com/launchdarkly/ldcli/cmd/analytics" "github.com/launchdarkly/ldcli/cmd/cliflags" configcmd "github.com/launchdarkly/ldcli/cmd/config" + devcmd "github.com/launchdarkly/ldcli/cmd/dev_server" flagscmd "github.com/launchdarkly/ldcli/cmd/flags" logincmd "github.com/launchdarkly/ldcli/cmd/login" memberscmd "github.com/launchdarkly/ldcli/cmd/members" resourcecmd "github.com/launchdarkly/ldcli/cmd/resources" "github.com/launchdarkly/ldcli/internal/analytics" "github.com/launchdarkly/ldcli/internal/config" + "github.com/launchdarkly/ldcli/internal/dev_server" "github.com/launchdarkly/ldcli/internal/environments" errs "github.com/launchdarkly/ldcli/internal/errors" "github.com/launchdarkly/ldcli/internal/flags" @@ -32,6 +34,7 @@ import ( ) type APIClients struct { + DevClient dev_server.Client EnvironmentsClient environments.Client FlagsClient flags.Client MembersClient members.Client @@ -190,6 +193,7 @@ func NewRootCommand( cmd.AddCommand(NewQuickStartCmd(analyticsTrackerFn, clients.EnvironmentsClient, clients.FlagsClient)) cmd.AddCommand(logincmd.NewLoginCmd(resources.NewClient(version))) cmd.AddCommand(resourcecmd.NewResourcesCmd()) + cmd.AddCommand(devcmd.NewDevServerCmd(resources.NewClient(version), dev_server.NewClient(version))) resourcecmd.AddAllResourceCmds(cmd, clients.ResourcesClient, analyticsTrackerFn) // add non-generated commands @@ -211,6 +215,7 @@ func NewRootCommand( func Execute(version string) { clients := APIClients{ + DevClient: dev_server.NewClient(version), EnvironmentsClient: environments.NewClient(version), FlagsClient: flags.NewClient(version), MembersClient: members.NewClient(version), diff --git a/go.mod b/go.mod index 6f65e4ac..58aefd19 100644 --- a/go.mod +++ b/go.mod @@ -1,60 +1,79 @@ module github.com/launchdarkly/ldcli -go 1.21 +go 1.22 require ( + github.com/adrg/xdg v0.4.0 github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/bubbletea v0.25.0 github.com/charmbracelet/glamour v0.6.0 github.com/charmbracelet/lipgloss v0.10.0 github.com/getkin/kin-openapi v0.124.0 github.com/google/uuid v1.6.0 + github.com/gorilla/handlers v1.5.2 + github.com/gorilla/mux v1.8.1 github.com/iancoleman/strcase v0.3.0 github.com/launchdarkly/api-client-go/v14 v14.0.0 + github.com/launchdarkly/go-sdk-common/v3 v3.1.0 + github.com/launchdarkly/go-server-sdk/v7 v7.4.1 github.com/launchdarkly/sdk-meta/api v0.2.0 + github.com/mattn/go-sqlite3 v1.14.22 github.com/mitchellh/go-homedir v1.1.0 github.com/muesli/reflow v0.3.0 + github.com/oapi-codegen/oapi-codegen/v2 v2.3.0 + github.com/oapi-codegen/runtime v1.1.1 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.9.0 + go.uber.org/mock v0.4.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 - golang.org/x/term v0.18.0 + golang.org/x/term v0.21.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/alecthomas/chroma v0.10.0 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dlclark/regexp2 v1.4.0 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/swag v0.22.8 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/gorilla/css v1.0.0 // indirect + github.com/gregjones/httpcache v0.0.0-20171119193500-2bcd89a1743f // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/yaml v0.2.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/launchdarkly/ccache v1.1.0 // indirect + github.com/launchdarkly/eventsource v1.6.2 // indirect + github.com/launchdarkly/go-jsonstream/v3 v3.0.0 // indirect + github.com/launchdarkly/go-sdk-events/v3 v3.2.0 // indirect + github.com/launchdarkly/go-semver v1.0.2 // indirect + github.com/launchdarkly/go-server-sdk-evaluation/v3 v3.0.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/microcosm-cc/bluemonday v1.0.21 // indirect + github.com/microcosm-cc/bluemonday v1.0.25 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -71,12 +90,15 @@ require ( github.com/yuin/goldmark-emoji v1.0.1 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/net v0.23.0 // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/net v0.26.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect - golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.22.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 65ecfccb..cada5711 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,13 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= +github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= @@ -42,6 +47,7 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= @@ -69,6 +75,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -121,8 +129,8 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -139,6 +147,12 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gregjones/httpcache v0.0.0-20171119193500-2bcd89a1743f h1:kOkUP6rcVVqC+KlKKENKtgfFfJyDySYhqL9srXooghY= +github.com/gregjones/httpcache v0.0.0-20171119193500-2bcd89a1743f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -154,6 +168,9 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/karlseguin/expect v1.0.2-0.20190806010014-778a5f0c6003 h1:vJ0Snvo+SLMY72r5J4sEfkuE7AFbixEP2qRbEcum/wA= +github.com/karlseguin/expect v1.0.2-0.20190806010014-778a5f0c6003/go.mod h1:zNBxMY8P21owkeogJELCLeHIt+voOSduHYTFUbwRAV8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -166,6 +183,26 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/launchdarkly/api-client-go/v14 v14.0.0 h1:fZfi5zKwgjpaOgK4NKcU5mJT2C8sYsR8nnuJYTaFvNU= github.com/launchdarkly/api-client-go/v14 v14.0.0/go.mod h1:K7ejD5nn9ar94p/5qrQ0t9iJygdIQyH70U9M9rYvw5Y= +github.com/launchdarkly/ccache v1.1.0 h1:voD1M+ZJXR3MREOKtBwgTF9hYHl1jg+vFKS/+VAkR2k= +github.com/launchdarkly/ccache v1.1.0/go.mod h1:TlxzrlnzvYeXiLHmesMuvoZetu4Z97cV1SsdqqBJi1Q= +github.com/launchdarkly/eventsource v1.6.2 h1:5SbcIqzUomn+/zmJDrkb4LYw7ryoKFzH/0TbR0/3Bdg= +github.com/launchdarkly/eventsource v1.6.2/go.mod h1:LHxSeb4OnqznNZxCSXbFghxS/CjIQfzHovNoAqbO/Wk= +github.com/launchdarkly/go-jsonstream/v3 v3.0.0 h1:qJF/WI09EUJ7kSpmP5d1Rhc81NQdYUhP17McKfUq17E= +github.com/launchdarkly/go-jsonstream/v3 v3.0.0/go.mod h1:/1Gyml6fnD309JOvunOSfyysWbZ/ZzcA120gF/cQtC4= +github.com/launchdarkly/go-sdk-common/v3 v3.1.0 h1:KNCP5rfkOt/25oxGLAVgaU1BgrZnzH9Y/3Z6I8bMwDg= +github.com/launchdarkly/go-sdk-common/v3 v3.1.0/go.mod h1:mXFmDGEh4ydK3QilRhrAyKuf9v44VZQWnINyhqbbOd0= +github.com/launchdarkly/go-sdk-events/v3 v3.2.0 h1:FUby/4cUSVDghCkFDpvy+7vZlIW4+CK95HjQnuqGXVs= +github.com/launchdarkly/go-sdk-events/v3 v3.2.0/go.mod h1:oepYWQ2RvvjfL2WxkE1uJJIuRsIMOP4WIVgUpXRPcNI= +github.com/launchdarkly/go-semver v1.0.2 h1:sYVRnuKyvxlmQCnCUyDkAhtmzSFRoX6rG2Xa21Mhg+w= +github.com/launchdarkly/go-semver v1.0.2/go.mod h1:xFmMwXba5Mb+3h72Z+VeSs9ahCvKo2QFUTHRNHVqR28= +github.com/launchdarkly/go-server-sdk-evaluation/v3 v3.0.0 h1:nQbR1xCpkdU9Z71FI28bWTi5LrmtSVURy0UFcBVD5ZU= +github.com/launchdarkly/go-server-sdk-evaluation/v3 v3.0.0/go.mod h1:cwk7/7SzNB2wZbCZS7w2K66klMLBe3NFM3/qd3xnsRc= +github.com/launchdarkly/go-server-sdk/v7 v7.4.1 h1:JBr1f3fowUFfSdqm9GjYSe5IMCngbq37l94r8ITEl0A= +github.com/launchdarkly/go-server-sdk/v7 v7.4.1/go.mod h1:EY2ag+p9HnNXiG4pJ+y7QG2gqCYEoYD+NJgwkhmUUqk= +github.com/launchdarkly/go-test-helpers/v2 v2.2.0 h1:L3kGILP/6ewikhzhdNkHy1b5y4zs50LueWenVF0sBbs= +github.com/launchdarkly/go-test-helpers/v2 v2.2.0/go.mod h1:L7+th5govYp5oKU9iN7To5PgznBuIjBPn+ejqKR0avw= +github.com/launchdarkly/go-test-helpers/v3 v3.0.2 h1:rh0085g1rVJM5qIukdaQ8z1XTWZztbJ49vRZuveqiuU= +github.com/launchdarkly/go-test-helpers/v3 v3.0.2/go.mod h1:u2ZvJlc/DDJTFrshWW50tWMZHLVYXofuSHUfTU/eIwM= github.com/launchdarkly/sdk-meta/api v0.2.0 h1:bBUBGodr52+3ObGOu1tf9CHR/jNh6HcZ3/yYxGPR7JI= github.com/launchdarkly/sdk-meta/api v0.2.0/go.mod h1:vXfR0z4XBz49IYT/2GDEza+Iat3PcuBCC438AZT6oDg= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -175,8 +212,8 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -184,8 +221,11 @@ github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= +github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= +github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -201,8 +241,14 @@ github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKt github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/oapi-codegen/oapi-codegen/v2 v2.3.0 h1:rICjNsHbPP1LttefanBPnwsSwl09SqhCO7Ee623qR84= +github.com/oapi-codegen/oapi-codegen/v2 v2.3.0/go.mod h1:4k+cJeSq5ntkwlcpQSxLxICCxQzCL772o30PxdibRt4= +github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= +github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= @@ -241,6 +287,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -248,6 +295,9 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -256,8 +306,10 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ= +github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -273,6 +325,8 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -312,6 +366,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -339,8 +395,8 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -357,8 +413,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -385,23 +441,24 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -445,6 +502,8 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -536,6 +595,9 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/config/config.go b/internal/config/config.go index ec56a7df..b6f72004 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -23,6 +23,7 @@ type Config struct { AccessToken string `json:"access-token,omitempty" yaml:"access-token,omitempty"` AnalyticsOptOut *bool `json:"analytics-opt-out,omitempty" yaml:"analytics-opt-out,omitempty"` BaseURI string `json:"base-uri,omitempty" yaml:"base-uri,omitempty"` + DevStreamURI string `json:"dev-stream-uri,omitempty" yaml:"dev-stream-uri,omitempty"` Environment string `json:"environment,omitempty" yaml:"environment,omitempty"` Flag string `json:"flag,omitempty" yaml:"flag,omitempty"` Output string `json:"output,omitempty" yaml:"output,omitempty"` @@ -80,6 +81,8 @@ func (c Config) Update(kvs []string) (Config, []string, error) { c.AnalyticsOptOut = &val case cliflags.BaseURIFlag: c.BaseURI = v + case cliflags.DevStreamURIFlag: + c.DevStreamURI = v case cliflags.EnvironmentFlag: c.Environment = v case cliflags.FlagFlag: diff --git a/internal/config/config_test.go b/internal/config/config_test.go index f52b4667..7fd99561 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -74,6 +74,7 @@ func TestUpdate(t *testing.T) { "access-token", "test-access-token", "analytics-opt-out", "true", "base-uri", "http://test.com", + "dev-stream-uri", "http://relay.com", "environment", "test-environment", "flag", "test-flag", "output", "plaintext", @@ -85,6 +86,7 @@ func TestUpdate(t *testing.T) { assert.Equal(t, "test-access-token", result.AccessToken) assert.True(t, *result.AnalyticsOptOut) assert.Equal(t, "http://test.com", result.BaseURI) + assert.Equal(t, "http://relay.com", result.DevStreamURI) assert.Equal(t, "test-environment", result.Environment) assert.Equal(t, "test-flag", result.Flag) assert.Equal(t, "plaintext", result.Output) @@ -95,6 +97,7 @@ func TestUpdate(t *testing.T) { "access-token", "analytics-opt-out", "base-uri", + "dev-stream-uri", "environment", "flag", "output", diff --git a/internal/dev_server/README.md b/internal/dev_server/README.md new file mode 100644 index 00000000..4466b8ae --- /dev/null +++ b/internal/dev_server/README.md @@ -0,0 +1,4 @@ +# dev server +The dev server is a go server that ldcli can run. It provides a local-only version of all the APIs that support LaunchDarkly SDKs. You can use it to serve flags to local and ephemeral environments. It copies flag _values_ for a project from a source environment and serves those. There are also APIs that let you override those values so that you can enable a feature just in your dev environment, e.g. + +The build of the dev server is incorporated into the ldcli build itself. The UI provided by the dev server has a [manual build](./ui/README.md). diff --git a/internal/dev_server/adapters/api.go b/internal/dev_server/adapters/api.go new file mode 100644 index 00000000..6833d32e --- /dev/null +++ b/internal/dev_server/adapters/api.go @@ -0,0 +1,39 @@ +package adapters + +import ( + "context" + + ldapi "github.com/launchdarkly/api-client-go/v14" + "github.com/pkg/errors" +) + +const ctxKeyApi = ctxKey("adapters.api") + +func WithApi(ctx context.Context, s Api) context.Context { + return context.WithValue(ctx, ctxKeyApi, s) +} + +func GetApi(ctx context.Context) Api { + return ctx.Value(ctxKeyApi).(Api) +} + +//go:generate go run go.uber.org/mock/mockgen -destination mocks/api.go -package mocks . Api +type Api interface { + GetSdkKey(ctx context.Context, projectKey, environmentKey string) (string, error) +} + +type apiClientApi struct { + apiClient ldapi.APIClient +} + +func NewApi(client ldapi.APIClient) Api { + return apiClientApi{client} +} + +func (a apiClientApi) GetSdkKey(ctx context.Context, projectKey, environmentKey string) (string, error) { + environment, _, err := a.apiClient.EnvironmentsApi.GetEnvironment(ctx, projectKey, environmentKey).Execute() + if err != nil { + return "", errors.Wrap(err, "unable to get SDK key from LD API") + } + return environment.ApiKey, nil +} diff --git a/internal/dev_server/adapters/middleware.go b/internal/dev_server/adapters/middleware.go new file mode 100644 index 00000000..3f2d0a07 --- /dev/null +++ b/internal/dev_server/adapters/middleware.go @@ -0,0 +1,22 @@ +package adapters + +import ( + "net/http" + + ldapi "github.com/launchdarkly/api-client-go/v14" +) + +type ctxKey string + +// Middleware puts adapters on to the context for consumption by other things +func Middleware(client ldapi.APIClient, streamingUrl string) func(handler http.Handler) http.Handler { + return func(handler http.Handler) http.Handler { + return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + ctx := request.Context() + ctx = WithSdk(ctx, newSdk(streamingUrl)) + ctx = WithApi(ctx, NewApi(client)) + request = request.WithContext(ctx) + handler.ServeHTTP(writer, request) + }) + } +} diff --git a/internal/dev_server/adapters/mocks/api.go b/internal/dev_server/adapters/mocks/api.go new file mode 100644 index 00000000..efacb902 --- /dev/null +++ b/internal/dev_server/adapters/mocks/api.go @@ -0,0 +1,55 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/launchdarkly/ldcli/internal/dev_server/adapters (interfaces: Api) +// +// Generated by this command: +// +// mockgen -destination mocks/api.go -package mocks . Api +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockApi is a mock of Api interface. +type MockApi struct { + ctrl *gomock.Controller + recorder *MockApiMockRecorder +} + +// MockApiMockRecorder is the mock recorder for MockApi. +type MockApiMockRecorder struct { + mock *MockApi +} + +// NewMockApi creates a new mock instance. +func NewMockApi(ctrl *gomock.Controller) *MockApi { + mock := &MockApi{ctrl: ctrl} + mock.recorder = &MockApiMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockApi) EXPECT() *MockApiMockRecorder { + return m.recorder +} + +// GetSdkKey mocks base method. +func (m *MockApi) GetSdkKey(arg0 context.Context, arg1, arg2 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSdkKey", arg0, arg1, arg2) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSdkKey indicates an expected call of GetSdkKey. +func (mr *MockApiMockRecorder) GetSdkKey(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSdkKey", reflect.TypeOf((*MockApi)(nil).GetSdkKey), arg0, arg1, arg2) +} diff --git a/internal/dev_server/adapters/mocks/sdk.go b/internal/dev_server/adapters/mocks/sdk.go new file mode 100644 index 00000000..80125d01 --- /dev/null +++ b/internal/dev_server/adapters/mocks/sdk.go @@ -0,0 +1,57 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/launchdarkly/ldcli/internal/dev_server/adapters (interfaces: Sdk) +// +// Generated by this command: +// +// mockgen -destination mocks/sdk.go -package mocks . Sdk +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + ldcontext "github.com/launchdarkly/go-sdk-common/v3/ldcontext" + flagstate "github.com/launchdarkly/go-server-sdk/v7/interfaces/flagstate" + gomock "go.uber.org/mock/gomock" +) + +// MockSdk is a mock of Sdk interface. +type MockSdk struct { + ctrl *gomock.Controller + recorder *MockSdkMockRecorder +} + +// MockSdkMockRecorder is the mock recorder for MockSdk. +type MockSdkMockRecorder struct { + mock *MockSdk +} + +// NewMockSdk creates a new mock instance. +func NewMockSdk(ctrl *gomock.Controller) *MockSdk { + mock := &MockSdk{ctrl: ctrl} + mock.recorder = &MockSdkMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSdk) EXPECT() *MockSdkMockRecorder { + return m.recorder +} + +// GetAllFlagsState mocks base method. +func (m *MockSdk) GetAllFlagsState(arg0 context.Context, arg1 ldcontext.Context, arg2 string) (flagstate.AllFlags, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAllFlagsState", arg0, arg1, arg2) + ret0, _ := ret[0].(flagstate.AllFlags) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAllFlagsState indicates an expected call of GetAllFlagsState. +func (mr *MockSdkMockRecorder) GetAllFlagsState(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllFlagsState", reflect.TypeOf((*MockSdk)(nil).GetAllFlagsState), arg0, arg1, arg2) +} diff --git a/internal/dev_server/adapters/mocks/utils.go b/internal/dev_server/adapters/mocks/utils.go new file mode 100644 index 00000000..4cf012b6 --- /dev/null +++ b/internal/dev_server/adapters/mocks/utils.go @@ -0,0 +1,17 @@ +package mocks + +import ( + "context" + + "github.com/launchdarkly/ldcli/internal/dev_server/adapters" + "go.uber.org/mock/gomock" +) + +func WithMockApiAndSdk(ctx context.Context, controller *gomock.Controller) (context.Context, *MockApi, *MockSdk) { + api := NewMockApi(controller) + ctx = adapters.WithApi(ctx, api) + sdk := NewMockSdk(controller) + ctx = adapters.WithSdk(ctx, sdk) + + return ctx, api, sdk +} diff --git a/internal/dev_server/adapters/sdk.go b/internal/dev_server/adapters/sdk.go new file mode 100644 index 00000000..1d64bfb6 --- /dev/null +++ b/internal/dev_server/adapters/sdk.go @@ -0,0 +1,62 @@ +package adapters + +import ( + "context" + "log" + "time" + + "github.com/launchdarkly/go-sdk-common/v3/ldcontext" + "github.com/launchdarkly/go-sdk-common/v3/ldlog" + ldsdk "github.com/launchdarkly/go-server-sdk/v7" + "github.com/launchdarkly/go-server-sdk/v7/interfaces/flagstate" + "github.com/launchdarkly/go-server-sdk/v7/ldcomponents" + "github.com/pkg/errors" +) + +const ctxKeySdk = ctxKey("adapters.sdk") + +func WithSdk(ctx context.Context, s Sdk) context.Context { + return context.WithValue(ctx, ctxKeySdk, s) +} + +func GetSdk(ctx context.Context) Sdk { + return ctx.Value(ctxKeySdk).(Sdk) +} + +//go:generate go run go.uber.org/mock/mockgen -destination mocks/sdk.go -package mocks . Sdk +type Sdk interface { + GetAllFlagsState(ctx context.Context, ldContext ldcontext.Context, sdkKey string) (flagstate.AllFlags, error) +} + +type streamingSdk struct { + streamingUrl string +} + +func newSdk(streamingUrl string) Sdk { + return streamingSdk{ + streamingUrl: streamingUrl, + } +} + +func (s streamingSdk) GetAllFlagsState(ctx context.Context, ldContext ldcontext.Context, sdkKey string) (flagstate.AllFlags, error) { + config := ldsdk.Config{ + DiagnosticOptOut: true, + Events: ldcomponents.NoEvents(), + Logging: ldcomponents.Logging().MinLevel(ldlog.Debug), + } + if s.streamingUrl != "" { + config.ServiceEndpoints.Streaming = s.streamingUrl + } + ldClient, err := ldsdk.MakeCustomClient(sdkKey, config, 5*time.Second) + if err != nil { + return flagstate.AllFlags{}, errors.Wrap(err, "unable to get source flags from LD SDK") + } + defer func() { + err := ldClient.Close() + if err != nil { + log.Printf("error while closing SDK client: %+v", err) + } + }() + flags := ldClient.AllFlagsState(ldContext) + return flags, nil +} diff --git a/internal/dev_server/api/api.yaml b/internal/dev_server/api/api.yaml new file mode 100644 index 00000000..6bd6e55d --- /dev/null +++ b/internal/dev_server/api/api.yaml @@ -0,0 +1,242 @@ +openapi: 3.0.3 +info: + title: LaunchDarkly Dev Server + description: | + LaunchDarkly Dev Server provides a simplified, local feature flagging server that can be used with LanchDarkly SDKs. + This API allows for the syncing of flags with a remote LaunchDarkly project and to configure local overrides of + those flags. + version: 1.0.0 +servers: + - url: "http" +paths: + /dev/projects: + get: + summary: lists all projects that have been configured for the dev server + responses: + 200: + description: OK. List of projects + content: + application/json: + schema: + description: list of project keys. + type: array + items: + type: string + uniqueItems: true + /dev/projects/{projectKey}/sync: + patch: + summary: updates the flag state for the given project and source environment + parameters: + - $ref: "#/components/parameters/projectKey" + - $ref: "#/components/parameters/expand" + responses: + 200: + $ref: "#/components/responses/Project" + 404: + description: No project found + /dev/projects/{projectKey}: + get: + summary: get the specified project and its configuration for syncing from the LaunchDarkly Service + parameters: + - $ref: "#/components/parameters/projectKey" + - $ref: "#/components/parameters/expand" + responses: + 200: + $ref: "#/components/responses/Project" + 404: + description: No project found + patch: + summary: updates the project context or sourceEnvironmentKey + parameters: + - $ref: "#/components/parameters/projectKey" + - $ref: "#/components/parameters/expand" + requestBody: + content: + application/json: + schema: + type: object + properties: + sourceEnvironmentKey: + type: string + description: environment to copy flag values from + context: + $ref: "#/components/schemas/Context" + responses: + 200: + $ref: "#/components/responses/Project" + 404: + description: No project found + delete: + summary: remove the specified project from the dev server + parameters: + - $ref: "#/components/parameters/projectKey" + responses: + 204: + description: OK. Project & overrides were removed + 404: + $ref: "#/components/responses/ErrorResponse" + post: + summary: Add the project to the dev server + parameters: + - $ref: "#/components/parameters/projectKey" + - $ref: "#/components/parameters/expand" + requestBody: + content: + application/json: + schema: + type: object + required: + - sourceEnvironmentKey + properties: + sourceEnvironmentKey: + type: string + description: environment to copy flag values from + context: + $ref: "#/components/schemas/Context" + responses: + 201: + $ref: "#/components/responses/Project" + 400: + $ref: "#/components/responses/ErrorResponse" + 409: + $ref: "#/components/responses/ErrorResponse" + /dev/projects/{projectKey}/overrides/{flagKey}: + put: + summary: override flag value with value provided in the body + parameters: + - $ref: "#/components/parameters/projectKey" + - $ref: "#/components/parameters/flagKey" + requestBody: + required: true + description: flag value to override flag with. The json representation of the variation value. + content: + application/json: + schema: + $ref: "#/components/schemas/FlagValue" + responses: + 200: + $ref: "#/components/responses/FlagOverride" + 400: + $ref: "#/components/responses/ErrorResponse" + + delete: + summary: remove override for flag + parameters: + - $ref: "#/components/parameters/projectKey" + - $ref: "#/components/parameters/flagKey" + responses: + 204: + description: OK. override removed + 404: + description: no matching override found +components: + parameters: + flagKey: + name: flagKey + in: path + required: true + schema: + type: string + projectKey: + name: projectKey + in: path + required: true + schema: + type: string + expand: + name: expand + description: Available expand options for this endpoint. + in: query + schema: + type: array + items: + type: string + enum: + - overrides + schemas: + FlagValue: + description: value of a feature flag variation + oneOf: + - type: string + - type: boolean + - type: number + - type: object + x-go-type: ldvalue.Value + x-go-type-import: + path: github.com/launchdarkly/go-sdk-common/v3/ldvalue + Context: + type: object + description: context object to use when evaluating flags in source environment + x-go-type: ldcontext.Context + x-go-type-import: + path: github.com/launchdarkly/go-sdk-common/v3/ldcontext + default: + key: "dev-environment" + kind: "user" + Project: + description: Project + type: object + required: + - sourceEnvironmentKey + - context + - _lastSyncedFromSource + properties: + context: + $ref: "#/components/schemas/Context" + sourceEnvironmentKey: + type: string + description: environment to copy flag values from + flagsState: + type: object + description: flags and their values and version for a given project in the source environment + x-go-type: model.FlagsState + x-go-type-import: + path: github.com/launchdarkly/ldcli/internal/dev_server/model + overrides: + type: object + description: flags and their values and version for a given project in the source environment + x-go-type: model.FlagsState + x-go-type-import: + path: github.com/launchdarkly/ldcli/internal/dev_server/model + _lastSyncedFromSource: + type: integer + x-go-type: int64 + description: unix timestamp for the lat time the flag values were synced from the source environment + responses: + FlagOverride: + description: Flag override + content: + application/json: + schema: + type: object + required: + - override + - value + properties: + value: + $ref: "#/components/schemas/FlagValue" + override: + type: boolean + description: whether or not this is an overridden value or one from the source environment + Project: + description: Project + content: + application/json: + schema: + $ref: "#/components/schemas/Project" + ErrorResponse: + description: Error response object + content: + application/json: + schema: + type: object + required: + - code + - message + properties: + code: + type: string + description: specific error code encountered + message: + type: string + description: description of the error diff --git a/internal/dev_server/api/oapi-codegen-cfg.yaml b/internal/dev_server/api/oapi-codegen-cfg.yaml new file mode 100644 index 00000000..871b6900 --- /dev/null +++ b/internal/dev_server/api/oapi-codegen-cfg.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/deepmap/oapi-codegen/HEAD/configuration-schema.json +package: api +generate: + gorilla-server: true + models: true + strict-server: true +output: server.gen.go diff --git a/internal/dev_server/api/server.gen.go b/internal/dev_server/api/server.gen.go new file mode 100644 index 00000000..5d522be1 --- /dev/null +++ b/internal/dev_server/api/server.gen.go @@ -0,0 +1,1104 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.3.0 DO NOT EDIT. +package api + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/gorilla/mux" + "github.com/launchdarkly/go-sdk-common/v3/ldcontext" + "github.com/launchdarkly/go-sdk-common/v3/ldvalue" + "github.com/launchdarkly/ldcli/internal/dev_server/model" + "github.com/oapi-codegen/runtime" + strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" +) + +// Defines values for GetDevProjectsProjectKeyParamsExpand. +const ( + GetDevProjectsProjectKeyParamsExpandOverrides GetDevProjectsProjectKeyParamsExpand = "overrides" +) + +// Defines values for PatchDevProjectsProjectKeyParamsExpand. +const ( + PatchDevProjectsProjectKeyParamsExpandOverrides PatchDevProjectsProjectKeyParamsExpand = "overrides" +) + +// Defines values for PostDevProjectsProjectKeyParamsExpand. +const ( + PostDevProjectsProjectKeyParamsExpandOverrides PostDevProjectsProjectKeyParamsExpand = "overrides" +) + +// Defines values for PatchDevProjectsProjectKeySyncParamsExpand. +const ( + PatchDevProjectsProjectKeySyncParamsExpandOverrides PatchDevProjectsProjectKeySyncParamsExpand = "overrides" +) + +// Context context object to use when evaluating flags in source environment +type Context = ldcontext.Context + +// FlagValue value of a feature flag variation +type FlagValue = ldvalue.Value + +// Project Project +type Project struct { + // LastSyncedFromSource unix timestamp for the lat time the flag values were synced from the source environment + LastSyncedFromSource int64 `json:"_lastSyncedFromSource"` + + // Context context object to use when evaluating flags in source environment + Context Context `json:"context"` + + // FlagsState flags and their values and version for a given project in the source environment + FlagsState *model.FlagsState `json:"flagsState,omitempty"` + + // Overrides flags and their values and version for a given project in the source environment + Overrides *model.FlagsState `json:"overrides,omitempty"` + + // SourceEnvironmentKey environment to copy flag values from + SourceEnvironmentKey string `json:"sourceEnvironmentKey"` +} + +// Expand defines model for expand. +type Expand = []string + +// FlagKey defines model for flagKey. +type FlagKey = string + +// ProjectKey defines model for projectKey. +type ProjectKey = string + +// ErrorResponse defines model for ErrorResponse. +type ErrorResponse struct { + // Code specific error code encountered + Code string `json:"code"` + + // Message description of the error + Message string `json:"message"` +} + +// FlagOverride defines model for FlagOverride. +type FlagOverride struct { + // Override whether or not this is an overridden value or one from the source environment + Override bool `json:"override"` + + // Value value of a feature flag variation + Value FlagValue `json:"value"` +} + +// GetDevProjectsProjectKeyParams defines parameters for GetDevProjectsProjectKey. +type GetDevProjectsProjectKeyParams struct { + // Expand Available expand options for this endpoint. + Expand *Expand `form:"expand,omitempty" json:"expand,omitempty"` +} + +// GetDevProjectsProjectKeyParamsExpand defines parameters for GetDevProjectsProjectKey. +type GetDevProjectsProjectKeyParamsExpand string + +// PatchDevProjectsProjectKeyJSONBody defines parameters for PatchDevProjectsProjectKey. +type PatchDevProjectsProjectKeyJSONBody struct { + // Context context object to use when evaluating flags in source environment + Context *Context `json:"context,omitempty"` + + // SourceEnvironmentKey environment to copy flag values from + SourceEnvironmentKey *string `json:"sourceEnvironmentKey,omitempty"` +} + +// PatchDevProjectsProjectKeyParams defines parameters for PatchDevProjectsProjectKey. +type PatchDevProjectsProjectKeyParams struct { + // Expand Available expand options for this endpoint. + Expand *Expand `form:"expand,omitempty" json:"expand,omitempty"` +} + +// PatchDevProjectsProjectKeyParamsExpand defines parameters for PatchDevProjectsProjectKey. +type PatchDevProjectsProjectKeyParamsExpand string + +// PostDevProjectsProjectKeyJSONBody defines parameters for PostDevProjectsProjectKey. +type PostDevProjectsProjectKeyJSONBody struct { + // Context context object to use when evaluating flags in source environment + Context *Context `json:"context,omitempty"` + + // SourceEnvironmentKey environment to copy flag values from + SourceEnvironmentKey string `json:"sourceEnvironmentKey"` +} + +// PostDevProjectsProjectKeyParams defines parameters for PostDevProjectsProjectKey. +type PostDevProjectsProjectKeyParams struct { + // Expand Available expand options for this endpoint. + Expand *Expand `form:"expand,omitempty" json:"expand,omitempty"` +} + +// PostDevProjectsProjectKeyParamsExpand defines parameters for PostDevProjectsProjectKey. +type PostDevProjectsProjectKeyParamsExpand string + +// PatchDevProjectsProjectKeySyncParams defines parameters for PatchDevProjectsProjectKeySync. +type PatchDevProjectsProjectKeySyncParams struct { + // Expand Available expand options for this endpoint. + Expand *Expand `form:"expand,omitempty" json:"expand,omitempty"` +} + +// PatchDevProjectsProjectKeySyncParamsExpand defines parameters for PatchDevProjectsProjectKeySync. +type PatchDevProjectsProjectKeySyncParamsExpand string + +// PatchDevProjectsProjectKeyJSONRequestBody defines body for PatchDevProjectsProjectKey for application/json ContentType. +type PatchDevProjectsProjectKeyJSONRequestBody PatchDevProjectsProjectKeyJSONBody + +// PostDevProjectsProjectKeyJSONRequestBody defines body for PostDevProjectsProjectKey for application/json ContentType. +type PostDevProjectsProjectKeyJSONRequestBody PostDevProjectsProjectKeyJSONBody + +// PutDevProjectsProjectKeyOverridesFlagKeyJSONRequestBody defines body for PutDevProjectsProjectKeyOverridesFlagKey for application/json ContentType. +type PutDevProjectsProjectKeyOverridesFlagKeyJSONRequestBody = FlagValue + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // lists all projects that have been configured for the dev server + // (GET /dev/projects) + GetDevProjects(w http.ResponseWriter, r *http.Request) + // remove the specified project from the dev server + // (DELETE /dev/projects/{projectKey}) + DeleteDevProjectsProjectKey(w http.ResponseWriter, r *http.Request, projectKey ProjectKey) + // get the specified project and its configuration for syncing from the LaunchDarkly Service + // (GET /dev/projects/{projectKey}) + GetDevProjectsProjectKey(w http.ResponseWriter, r *http.Request, projectKey ProjectKey, params GetDevProjectsProjectKeyParams) + // updates the project context or sourceEnvironmentKey + // (PATCH /dev/projects/{projectKey}) + PatchDevProjectsProjectKey(w http.ResponseWriter, r *http.Request, projectKey ProjectKey, params PatchDevProjectsProjectKeyParams) + // Add the project to the dev server + // (POST /dev/projects/{projectKey}) + PostDevProjectsProjectKey(w http.ResponseWriter, r *http.Request, projectKey ProjectKey, params PostDevProjectsProjectKeyParams) + // remove override for flag + // (DELETE /dev/projects/{projectKey}/overrides/{flagKey}) + DeleteDevProjectsProjectKeyOverridesFlagKey(w http.ResponseWriter, r *http.Request, projectKey ProjectKey, flagKey FlagKey) + // override flag value with value provided in the body + // (PUT /dev/projects/{projectKey}/overrides/{flagKey}) + PutDevProjectsProjectKeyOverridesFlagKey(w http.ResponseWriter, r *http.Request, projectKey ProjectKey, flagKey FlagKey) + // updates the flag state for the given project and source environment + // (PATCH /dev/projects/{projectKey}/sync) + PatchDevProjectsProjectKeySync(w http.ResponseWriter, r *http.Request, projectKey ProjectKey, params PatchDevProjectsProjectKeySyncParams) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetDevProjects operation middleware +func (siw *ServerInterfaceWrapper) GetDevProjects(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetDevProjects(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// DeleteDevProjectsProjectKey operation middleware +func (siw *ServerInterfaceWrapper) DeleteDevProjectsProjectKey(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "projectKey" ------------- + var projectKey ProjectKey + + err = runtime.BindStyledParameterWithOptions("simple", "projectKey", mux.Vars(r)["projectKey"], &projectKey, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectKey", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteDevProjectsProjectKey(w, r, projectKey) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetDevProjectsProjectKey operation middleware +func (siw *ServerInterfaceWrapper) GetDevProjectsProjectKey(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "projectKey" ------------- + var projectKey ProjectKey + + err = runtime.BindStyledParameterWithOptions("simple", "projectKey", mux.Vars(r)["projectKey"], &projectKey, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectKey", Err: err}) + return + } + + // Parameter object where we will unmarshal all parameters from the context + var params GetDevProjectsProjectKeyParams + + // ------------- Optional query parameter "expand" ------------- + + err = runtime.BindQueryParameter("form", true, false, "expand", r.URL.Query(), ¶ms.Expand) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "expand", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetDevProjectsProjectKey(w, r, projectKey, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// PatchDevProjectsProjectKey operation middleware +func (siw *ServerInterfaceWrapper) PatchDevProjectsProjectKey(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "projectKey" ------------- + var projectKey ProjectKey + + err = runtime.BindStyledParameterWithOptions("simple", "projectKey", mux.Vars(r)["projectKey"], &projectKey, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectKey", Err: err}) + return + } + + // Parameter object where we will unmarshal all parameters from the context + var params PatchDevProjectsProjectKeyParams + + // ------------- Optional query parameter "expand" ------------- + + err = runtime.BindQueryParameter("form", true, false, "expand", r.URL.Query(), ¶ms.Expand) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "expand", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.PatchDevProjectsProjectKey(w, r, projectKey, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// PostDevProjectsProjectKey operation middleware +func (siw *ServerInterfaceWrapper) PostDevProjectsProjectKey(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "projectKey" ------------- + var projectKey ProjectKey + + err = runtime.BindStyledParameterWithOptions("simple", "projectKey", mux.Vars(r)["projectKey"], &projectKey, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectKey", Err: err}) + return + } + + // Parameter object where we will unmarshal all parameters from the context + var params PostDevProjectsProjectKeyParams + + // ------------- Optional query parameter "expand" ------------- + + err = runtime.BindQueryParameter("form", true, false, "expand", r.URL.Query(), ¶ms.Expand) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "expand", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.PostDevProjectsProjectKey(w, r, projectKey, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// DeleteDevProjectsProjectKeyOverridesFlagKey operation middleware +func (siw *ServerInterfaceWrapper) DeleteDevProjectsProjectKeyOverridesFlagKey(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "projectKey" ------------- + var projectKey ProjectKey + + err = runtime.BindStyledParameterWithOptions("simple", "projectKey", mux.Vars(r)["projectKey"], &projectKey, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectKey", Err: err}) + return + } + + // ------------- Path parameter "flagKey" ------------- + var flagKey FlagKey + + err = runtime.BindStyledParameterWithOptions("simple", "flagKey", mux.Vars(r)["flagKey"], &flagKey, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "flagKey", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteDevProjectsProjectKeyOverridesFlagKey(w, r, projectKey, flagKey) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// PutDevProjectsProjectKeyOverridesFlagKey operation middleware +func (siw *ServerInterfaceWrapper) PutDevProjectsProjectKeyOverridesFlagKey(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "projectKey" ------------- + var projectKey ProjectKey + + err = runtime.BindStyledParameterWithOptions("simple", "projectKey", mux.Vars(r)["projectKey"], &projectKey, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectKey", Err: err}) + return + } + + // ------------- Path parameter "flagKey" ------------- + var flagKey FlagKey + + err = runtime.BindStyledParameterWithOptions("simple", "flagKey", mux.Vars(r)["flagKey"], &flagKey, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "flagKey", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.PutDevProjectsProjectKeyOverridesFlagKey(w, r, projectKey, flagKey) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// PatchDevProjectsProjectKeySync operation middleware +func (siw *ServerInterfaceWrapper) PatchDevProjectsProjectKeySync(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "projectKey" ------------- + var projectKey ProjectKey + + err = runtime.BindStyledParameterWithOptions("simple", "projectKey", mux.Vars(r)["projectKey"], &projectKey, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectKey", Err: err}) + return + } + + // Parameter object where we will unmarshal all parameters from the context + var params PatchDevProjectsProjectKeySyncParams + + // ------------- Optional query parameter "expand" ------------- + + err = runtime.BindQueryParameter("form", true, false, "expand", r.URL.Query(), ¶ms.Expand) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "expand", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.PatchDevProjectsProjectKeySync(w, r, projectKey, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{}) +} + +type GorillaServerOptions struct { + BaseURL string + BaseRouter *mux.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r *mux.Router) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r *mux.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, GorillaServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options GorillaServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = mux.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.HandleFunc(options.BaseURL+"/dev/projects", wrapper.GetDevProjects).Methods("GET") + + r.HandleFunc(options.BaseURL+"/dev/projects/{projectKey}", wrapper.DeleteDevProjectsProjectKey).Methods("DELETE") + + r.HandleFunc(options.BaseURL+"/dev/projects/{projectKey}", wrapper.GetDevProjectsProjectKey).Methods("GET") + + r.HandleFunc(options.BaseURL+"/dev/projects/{projectKey}", wrapper.PatchDevProjectsProjectKey).Methods("PATCH") + + r.HandleFunc(options.BaseURL+"/dev/projects/{projectKey}", wrapper.PostDevProjectsProjectKey).Methods("POST") + + r.HandleFunc(options.BaseURL+"/dev/projects/{projectKey}/overrides/{flagKey}", wrapper.DeleteDevProjectsProjectKeyOverridesFlagKey).Methods("DELETE") + + r.HandleFunc(options.BaseURL+"/dev/projects/{projectKey}/overrides/{flagKey}", wrapper.PutDevProjectsProjectKeyOverridesFlagKey).Methods("PUT") + + r.HandleFunc(options.BaseURL+"/dev/projects/{projectKey}/sync", wrapper.PatchDevProjectsProjectKeySync).Methods("PATCH") + + return r +} + +type ErrorResponseJSONResponse struct { + // Code specific error code encountered + Code string `json:"code"` + + // Message description of the error + Message string `json:"message"` +} + +type FlagOverrideJSONResponse struct { + // Override whether or not this is an overridden value or one from the source environment + Override bool `json:"override"` + + // Value value of a feature flag variation + Value FlagValue `json:"value"` +} + +type ProjectJSONResponse Project + +type GetDevProjectsRequestObject struct { +} + +type GetDevProjectsResponseObject interface { + VisitGetDevProjectsResponse(w http.ResponseWriter) error +} + +type GetDevProjects200JSONResponse []string + +func (response GetDevProjects200JSONResponse) VisitGetDevProjectsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteDevProjectsProjectKeyRequestObject struct { + ProjectKey ProjectKey `json:"projectKey"` +} + +type DeleteDevProjectsProjectKeyResponseObject interface { + VisitDeleteDevProjectsProjectKeyResponse(w http.ResponseWriter) error +} + +type DeleteDevProjectsProjectKey204Response struct { +} + +func (response DeleteDevProjectsProjectKey204Response) VisitDeleteDevProjectsProjectKeyResponse(w http.ResponseWriter) error { + w.WriteHeader(204) + return nil +} + +type DeleteDevProjectsProjectKey404JSONResponse struct{ ErrorResponseJSONResponse } + +func (response DeleteDevProjectsProjectKey404JSONResponse) VisitDeleteDevProjectsProjectKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type GetDevProjectsProjectKeyRequestObject struct { + ProjectKey ProjectKey `json:"projectKey"` + Params GetDevProjectsProjectKeyParams +} + +type GetDevProjectsProjectKeyResponseObject interface { + VisitGetDevProjectsProjectKeyResponse(w http.ResponseWriter) error +} + +type GetDevProjectsProjectKey200JSONResponse struct{ ProjectJSONResponse } + +func (response GetDevProjectsProjectKey200JSONResponse) VisitGetDevProjectsProjectKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetDevProjectsProjectKey404Response struct { +} + +func (response GetDevProjectsProjectKey404Response) VisitGetDevProjectsProjectKeyResponse(w http.ResponseWriter) error { + w.WriteHeader(404) + return nil +} + +type PatchDevProjectsProjectKeyRequestObject struct { + ProjectKey ProjectKey `json:"projectKey"` + Params PatchDevProjectsProjectKeyParams + Body *PatchDevProjectsProjectKeyJSONRequestBody +} + +type PatchDevProjectsProjectKeyResponseObject interface { + VisitPatchDevProjectsProjectKeyResponse(w http.ResponseWriter) error +} + +type PatchDevProjectsProjectKey200JSONResponse struct{ ProjectJSONResponse } + +func (response PatchDevProjectsProjectKey200JSONResponse) VisitPatchDevProjectsProjectKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type PatchDevProjectsProjectKey404Response struct { +} + +func (response PatchDevProjectsProjectKey404Response) VisitPatchDevProjectsProjectKeyResponse(w http.ResponseWriter) error { + w.WriteHeader(404) + return nil +} + +type PostDevProjectsProjectKeyRequestObject struct { + ProjectKey ProjectKey `json:"projectKey"` + Params PostDevProjectsProjectKeyParams + Body *PostDevProjectsProjectKeyJSONRequestBody +} + +type PostDevProjectsProjectKeyResponseObject interface { + VisitPostDevProjectsProjectKeyResponse(w http.ResponseWriter) error +} + +type PostDevProjectsProjectKey201JSONResponse struct{ ProjectJSONResponse } + +func (response PostDevProjectsProjectKey201JSONResponse) VisitPostDevProjectsProjectKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(201) + + return json.NewEncoder(w).Encode(response) +} + +type PostDevProjectsProjectKey400JSONResponse struct{ ErrorResponseJSONResponse } + +func (response PostDevProjectsProjectKey400JSONResponse) VisitPostDevProjectsProjectKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type PostDevProjectsProjectKey409JSONResponse struct { + // Code specific error code encountered + Code string `json:"code"` + + // Message description of the error + Message string `json:"message"` +} + +func (response PostDevProjectsProjectKey409JSONResponse) VisitPostDevProjectsProjectKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(409) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteDevProjectsProjectKeyOverridesFlagKeyRequestObject struct { + ProjectKey ProjectKey `json:"projectKey"` + FlagKey FlagKey `json:"flagKey"` +} + +type DeleteDevProjectsProjectKeyOverridesFlagKeyResponseObject interface { + VisitDeleteDevProjectsProjectKeyOverridesFlagKeyResponse(w http.ResponseWriter) error +} + +type DeleteDevProjectsProjectKeyOverridesFlagKey204Response struct { +} + +func (response DeleteDevProjectsProjectKeyOverridesFlagKey204Response) VisitDeleteDevProjectsProjectKeyOverridesFlagKeyResponse(w http.ResponseWriter) error { + w.WriteHeader(204) + return nil +} + +type DeleteDevProjectsProjectKeyOverridesFlagKey404Response struct { +} + +func (response DeleteDevProjectsProjectKeyOverridesFlagKey404Response) VisitDeleteDevProjectsProjectKeyOverridesFlagKeyResponse(w http.ResponseWriter) error { + w.WriteHeader(404) + return nil +} + +type PutDevProjectsProjectKeyOverridesFlagKeyRequestObject struct { + ProjectKey ProjectKey `json:"projectKey"` + FlagKey FlagKey `json:"flagKey"` + Body *PutDevProjectsProjectKeyOverridesFlagKeyJSONRequestBody +} + +type PutDevProjectsProjectKeyOverridesFlagKeyResponseObject interface { + VisitPutDevProjectsProjectKeyOverridesFlagKeyResponse(w http.ResponseWriter) error +} + +type PutDevProjectsProjectKeyOverridesFlagKey200JSONResponse struct{ FlagOverrideJSONResponse } + +func (response PutDevProjectsProjectKeyOverridesFlagKey200JSONResponse) VisitPutDevProjectsProjectKeyOverridesFlagKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type PutDevProjectsProjectKeyOverridesFlagKey400JSONResponse struct{ ErrorResponseJSONResponse } + +func (response PutDevProjectsProjectKeyOverridesFlagKey400JSONResponse) VisitPutDevProjectsProjectKeyOverridesFlagKeyResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type PatchDevProjectsProjectKeySyncRequestObject struct { + ProjectKey ProjectKey `json:"projectKey"` + Params PatchDevProjectsProjectKeySyncParams +} + +type PatchDevProjectsProjectKeySyncResponseObject interface { + VisitPatchDevProjectsProjectKeySyncResponse(w http.ResponseWriter) error +} + +type PatchDevProjectsProjectKeySync200JSONResponse struct{ ProjectJSONResponse } + +func (response PatchDevProjectsProjectKeySync200JSONResponse) VisitPatchDevProjectsProjectKeySyncResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type PatchDevProjectsProjectKeySync404Response struct { +} + +func (response PatchDevProjectsProjectKeySync404Response) VisitPatchDevProjectsProjectKeySyncResponse(w http.ResponseWriter) error { + w.WriteHeader(404) + return nil +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + // lists all projects that have been configured for the dev server + // (GET /dev/projects) + GetDevProjects(ctx context.Context, request GetDevProjectsRequestObject) (GetDevProjectsResponseObject, error) + // remove the specified project from the dev server + // (DELETE /dev/projects/{projectKey}) + DeleteDevProjectsProjectKey(ctx context.Context, request DeleteDevProjectsProjectKeyRequestObject) (DeleteDevProjectsProjectKeyResponseObject, error) + // get the specified project and its configuration for syncing from the LaunchDarkly Service + // (GET /dev/projects/{projectKey}) + GetDevProjectsProjectKey(ctx context.Context, request GetDevProjectsProjectKeyRequestObject) (GetDevProjectsProjectKeyResponseObject, error) + // updates the project context or sourceEnvironmentKey + // (PATCH /dev/projects/{projectKey}) + PatchDevProjectsProjectKey(ctx context.Context, request PatchDevProjectsProjectKeyRequestObject) (PatchDevProjectsProjectKeyResponseObject, error) + // Add the project to the dev server + // (POST /dev/projects/{projectKey}) + PostDevProjectsProjectKey(ctx context.Context, request PostDevProjectsProjectKeyRequestObject) (PostDevProjectsProjectKeyResponseObject, error) + // remove override for flag + // (DELETE /dev/projects/{projectKey}/overrides/{flagKey}) + DeleteDevProjectsProjectKeyOverridesFlagKey(ctx context.Context, request DeleteDevProjectsProjectKeyOverridesFlagKeyRequestObject) (DeleteDevProjectsProjectKeyOverridesFlagKeyResponseObject, error) + // override flag value with value provided in the body + // (PUT /dev/projects/{projectKey}/overrides/{flagKey}) + PutDevProjectsProjectKeyOverridesFlagKey(ctx context.Context, request PutDevProjectsProjectKeyOverridesFlagKeyRequestObject) (PutDevProjectsProjectKeyOverridesFlagKeyResponseObject, error) + // updates the flag state for the given project and source environment + // (PATCH /dev/projects/{projectKey}/sync) + PatchDevProjectsProjectKeySync(ctx context.Context, request PatchDevProjectsProjectKeySyncRequestObject) (PatchDevProjectsProjectKeySyncResponseObject, error) +} + +type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc +type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc + +type StrictHTTPServerOptions struct { + RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions{ + RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + }, + ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusInternalServerError) + }, + }} +} + +func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: options} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc + options StrictHTTPServerOptions +} + +// GetDevProjects operation middleware +func (sh *strictHandler) GetDevProjects(w http.ResponseWriter, r *http.Request) { + var request GetDevProjectsRequestObject + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetDevProjects(ctx, request.(GetDevProjectsRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetDevProjects") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetDevProjectsResponseObject); ok { + if err := validResponse.VisitGetDevProjectsResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// DeleteDevProjectsProjectKey operation middleware +func (sh *strictHandler) DeleteDevProjectsProjectKey(w http.ResponseWriter, r *http.Request, projectKey ProjectKey) { + var request DeleteDevProjectsProjectKeyRequestObject + + request.ProjectKey = projectKey + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.DeleteDevProjectsProjectKey(ctx, request.(DeleteDevProjectsProjectKeyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "DeleteDevProjectsProjectKey") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(DeleteDevProjectsProjectKeyResponseObject); ok { + if err := validResponse.VisitDeleteDevProjectsProjectKeyResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// GetDevProjectsProjectKey operation middleware +func (sh *strictHandler) GetDevProjectsProjectKey(w http.ResponseWriter, r *http.Request, projectKey ProjectKey, params GetDevProjectsProjectKeyParams) { + var request GetDevProjectsProjectKeyRequestObject + + request.ProjectKey = projectKey + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetDevProjectsProjectKey(ctx, request.(GetDevProjectsProjectKeyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetDevProjectsProjectKey") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetDevProjectsProjectKeyResponseObject); ok { + if err := validResponse.VisitGetDevProjectsProjectKeyResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// PatchDevProjectsProjectKey operation middleware +func (sh *strictHandler) PatchDevProjectsProjectKey(w http.ResponseWriter, r *http.Request, projectKey ProjectKey, params PatchDevProjectsProjectKeyParams) { + var request PatchDevProjectsProjectKeyRequestObject + + request.ProjectKey = projectKey + request.Params = params + + var body PatchDevProjectsProjectKeyJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.PatchDevProjectsProjectKey(ctx, request.(PatchDevProjectsProjectKeyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "PatchDevProjectsProjectKey") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(PatchDevProjectsProjectKeyResponseObject); ok { + if err := validResponse.VisitPatchDevProjectsProjectKeyResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// PostDevProjectsProjectKey operation middleware +func (sh *strictHandler) PostDevProjectsProjectKey(w http.ResponseWriter, r *http.Request, projectKey ProjectKey, params PostDevProjectsProjectKeyParams) { + var request PostDevProjectsProjectKeyRequestObject + + request.ProjectKey = projectKey + request.Params = params + + var body PostDevProjectsProjectKeyJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.PostDevProjectsProjectKey(ctx, request.(PostDevProjectsProjectKeyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "PostDevProjectsProjectKey") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(PostDevProjectsProjectKeyResponseObject); ok { + if err := validResponse.VisitPostDevProjectsProjectKeyResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// DeleteDevProjectsProjectKeyOverridesFlagKey operation middleware +func (sh *strictHandler) DeleteDevProjectsProjectKeyOverridesFlagKey(w http.ResponseWriter, r *http.Request, projectKey ProjectKey, flagKey FlagKey) { + var request DeleteDevProjectsProjectKeyOverridesFlagKeyRequestObject + + request.ProjectKey = projectKey + request.FlagKey = flagKey + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.DeleteDevProjectsProjectKeyOverridesFlagKey(ctx, request.(DeleteDevProjectsProjectKeyOverridesFlagKeyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "DeleteDevProjectsProjectKeyOverridesFlagKey") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(DeleteDevProjectsProjectKeyOverridesFlagKeyResponseObject); ok { + if err := validResponse.VisitDeleteDevProjectsProjectKeyOverridesFlagKeyResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// PutDevProjectsProjectKeyOverridesFlagKey operation middleware +func (sh *strictHandler) PutDevProjectsProjectKeyOverridesFlagKey(w http.ResponseWriter, r *http.Request, projectKey ProjectKey, flagKey FlagKey) { + var request PutDevProjectsProjectKeyOverridesFlagKeyRequestObject + + request.ProjectKey = projectKey + request.FlagKey = flagKey + + var body PutDevProjectsProjectKeyOverridesFlagKeyJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.PutDevProjectsProjectKeyOverridesFlagKey(ctx, request.(PutDevProjectsProjectKeyOverridesFlagKeyRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "PutDevProjectsProjectKeyOverridesFlagKey") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(PutDevProjectsProjectKeyOverridesFlagKeyResponseObject); ok { + if err := validResponse.VisitPutDevProjectsProjectKeyOverridesFlagKeyResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// PatchDevProjectsProjectKeySync operation middleware +func (sh *strictHandler) PatchDevProjectsProjectKeySync(w http.ResponseWriter, r *http.Request, projectKey ProjectKey, params PatchDevProjectsProjectKeySyncParams) { + var request PatchDevProjectsProjectKeySyncRequestObject + + request.ProjectKey = projectKey + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.PatchDevProjectsProjectKeySync(ctx, request.(PatchDevProjectsProjectKeySyncRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "PatchDevProjectsProjectKeySync") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(PatchDevProjectsProjectKeySyncResponseObject); ok { + if err := validResponse.VisitPatchDevProjectsProjectKeySyncResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} diff --git a/internal/dev_server/api/server.go b/internal/dev_server/api/server.go new file mode 100644 index 00000000..7ae6bfcf --- /dev/null +++ b/internal/dev_server/api/server.go @@ -0,0 +1,269 @@ +package api + +import ( + "context" + "errors" + + "github.com/launchdarkly/ldcli/internal/dev_server/model" +) + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config oapi-codegen-cfg.yaml api.yaml +type Server struct { +} + +func NewStrictServer() Server { + return Server{} +} + +func (s Server) GetDevProjects(ctx context.Context, request GetDevProjectsRequestObject) (GetDevProjectsResponseObject, error) { + store := model.StoreFromContext(ctx) + projectKeys, err := store.GetDevProjectKeys(ctx) + if err != nil { + return nil, err + } + if projectKeys == nil { + projectKeys = make([]string, 0) // HACK to make the json behavior compatible with go. + } + return GetDevProjects200JSONResponse(projectKeys), nil +} + +func (s Server) DeleteDevProjectsProjectKey(ctx context.Context, request DeleteDevProjectsProjectKeyRequestObject) (DeleteDevProjectsProjectKeyResponseObject, error) { + store := model.StoreFromContext(ctx) + deleted, err := store.DeleteDevProject(ctx, request.ProjectKey) + if err != nil { + return nil, err + } + if !deleted { + return DeleteDevProjectsProjectKey404JSONResponse{ErrorResponseJSONResponse{ + Code: "not_found", + Message: "project not found", + }}, nil + } + return DeleteDevProjectsProjectKey204Response{}, nil +} + +func (s Server) GetDevProjectsProjectKey(ctx context.Context, request GetDevProjectsProjectKeyRequestObject) (GetDevProjectsProjectKeyResponseObject, error) { + store := model.StoreFromContext(ctx) + project, err := store.GetDevProject(ctx, request.ProjectKey) + if err != nil { + return nil, err + } + if project == nil { + return GetDevProjectsProjectKey404Response{}, nil + } + + response := ProjectJSONResponse{ + LastSyncedFromSource: project.LastSyncTime.Unix(), + Context: project.Context, + SourceEnvironmentKey: project.SourceEnvironmentKey, + FlagsState: &project.AllFlagsState, + } + + if request.Params.Expand != nil { + for _, item := range *request.Params.Expand { + if item == "overrides" { + overrides, err := store.GetOverridesForProject(ctx, request.ProjectKey) + if err != nil { + return nil, err + } + respOverrides := make(model.FlagsState) + for _, override := range overrides { + if !override.Active { + continue + } + respOverrides[override.FlagKey] = model.FlagState{ + Value: override.Value, + Version: override.Version, + } + } + response.Overrides = &respOverrides + } + } + + } + + return GetDevProjectsProjectKey200JSONResponse{ + response, + }, nil +} + +func (s Server) PostDevProjectsProjectKey(ctx context.Context, request PostDevProjectsProjectKeyRequestObject) (PostDevProjectsProjectKeyResponseObject, error) { + if request.Body.SourceEnvironmentKey == "" { + return PostDevProjectsProjectKey400JSONResponse{ + ErrorResponseJSONResponse{ + Code: "invalid_request", + Message: "sourceEnvironmentKey is required", + }, + }, nil + } + + store := model.StoreFromContext(ctx) + project, err := model.CreateProject(ctx, request.ProjectKey, request.Body.SourceEnvironmentKey, request.Body.Context) + switch { + case errors.Is(err, model.ErrAlreadyExists): + return PostDevProjectsProjectKey409JSONResponse{ + Code: "conflict", + Message: "project already exists", + }, nil + case err != nil: + return nil, err + } + + response := ProjectJSONResponse{ + LastSyncedFromSource: project.LastSyncTime.Unix(), + Context: project.Context, + SourceEnvironmentKey: project.SourceEnvironmentKey, + FlagsState: &project.AllFlagsState, + } + + if request.Params.Expand != nil { + for _, item := range *request.Params.Expand { + if item == "overrides" { + overrides, err := store.GetOverridesForProject(ctx, request.ProjectKey) + if err != nil { + return nil, err + } + respOverrides := make(model.FlagsState) + for _, override := range overrides { + if !override.Active { + continue + } + respOverrides[override.FlagKey] = model.FlagState{ + Value: override.Value, + Version: override.Version, + } + } + response.Overrides = &respOverrides + } + } + + } + + return PostDevProjectsProjectKey201JSONResponse{ + response, + }, nil +} + +func (s Server) PatchDevProjectsProjectKey(ctx context.Context, request PatchDevProjectsProjectKeyRequestObject) (PatchDevProjectsProjectKeyResponseObject, error) { + store := model.StoreFromContext(ctx) + project, err := model.UpdateProject(ctx, request.ProjectKey, request.Body.Context, request.Body.SourceEnvironmentKey) + if err != nil { + return nil, err + } + if project.Key == "" && project.SourceEnvironmentKey == "" { + return PatchDevProjectsProjectKey404Response{}, nil + } + + response := ProjectJSONResponse{ + LastSyncedFromSource: project.LastSyncTime.Unix(), + Context: project.Context, + SourceEnvironmentKey: project.SourceEnvironmentKey, + FlagsState: &project.AllFlagsState, + } + + if request.Params.Expand != nil { + for _, item := range *request.Params.Expand { + if item == "overrides" { + overrides, err := store.GetOverridesForProject(ctx, request.ProjectKey) + if err != nil { + return nil, err + } + respOverrides := make(model.FlagsState) + for _, override := range overrides { + if !override.Active { + continue + } + respOverrides[override.FlagKey] = model.FlagState{ + Value: override.Value, + Version: override.Version, + } + } + response.Overrides = &respOverrides + } + } + + } + + return PatchDevProjectsProjectKey200JSONResponse{ + response, + }, nil +} + +func (s Server) PatchDevProjectsProjectKeySync(ctx context.Context, request PatchDevProjectsProjectKeySyncRequestObject) (PatchDevProjectsProjectKeySyncResponseObject, error) { + store := model.StoreFromContext(ctx) + project, err := model.SyncProject(ctx, request.ProjectKey) + if err != nil { + return nil, err + } + if project.Key == "" && project.SourceEnvironmentKey == "" { + return PatchDevProjectsProjectKeySync404Response{}, nil + } + + response := ProjectJSONResponse{ + LastSyncedFromSource: project.LastSyncTime.Unix(), + Context: project.Context, + SourceEnvironmentKey: project.SourceEnvironmentKey, + FlagsState: &project.AllFlagsState, + } + + if request.Params.Expand != nil { + for _, item := range *request.Params.Expand { + if item == "overrides" { + overrides, err := store.GetOverridesForProject(ctx, request.ProjectKey) + if err != nil { + return nil, err + } + respOverrides := make(model.FlagsState) + for _, override := range overrides { + if !override.Active { + continue + } + respOverrides[override.FlagKey] = model.FlagState{ + Value: override.Value, + Version: override.Version, + } + } + response.Overrides = &respOverrides + } + } + + } + + return PatchDevProjectsProjectKeySync200JSONResponse{ + response, + }, nil +} + +func (s Server) DeleteDevProjectsProjectKeyOverridesFlagKey(ctx context.Context, request DeleteDevProjectsProjectKeyOverridesFlagKeyRequestObject) (DeleteDevProjectsProjectKeyOverridesFlagKeyResponseObject, error) { + store := model.StoreFromContext(ctx) + err := store.DeactivateOverride(ctx, request.ProjectKey, request.FlagKey) + if err != nil { + if errors.Is(err, model.ErrNotFound) { + return DeleteDevProjectsProjectKeyOverridesFlagKey404Response{}, nil + } + return nil, err + } + return DeleteDevProjectsProjectKeyOverridesFlagKey204Response{}, nil +} + +func (s Server) PutDevProjectsProjectKeyOverridesFlagKey(ctx context.Context, request PutDevProjectsProjectKeyOverridesFlagKeyRequestObject) (PutDevProjectsProjectKeyOverridesFlagKeyResponseObject, error) { + if request.Body == nil { + return nil, errors.New("empty override body") + } + override, err := model.UpsertOverride(ctx, request.ProjectKey, request.FlagKey, *request.Body) + if err != nil { + if errors.As(err, &model.Error{}) { + return PutDevProjectsProjectKeyOverridesFlagKey400JSONResponse{ + ErrorResponseJSONResponse{ + Code: "invalid_request", + Message: err.Error(), + }, + }, nil + } + return nil, err + } + return PutDevProjectsProjectKeyOverridesFlagKey200JSONResponse{FlagOverrideJSONResponse{ + Override: override.Active, + Value: override.Value, + }}, nil +} diff --git a/internal/dev_server/db/docs.go b/internal/dev_server/db/docs.go new file mode 100644 index 00000000..58014bc8 --- /dev/null +++ b/internal/dev_server/db/docs.go @@ -0,0 +1,2 @@ +// Package store provides database storage for package model +package db diff --git a/internal/dev_server/db/sqlite.go b/internal/dev_server/db/sqlite.go new file mode 100644 index 00000000..523b4b9f --- /dev/null +++ b/internal/dev_server/db/sqlite.go @@ -0,0 +1,295 @@ +package db + +import ( + "context" + "database/sql" + "encoding/json" + + _ "github.com/mattn/go-sqlite3" + "github.com/pkg/errors" + + "github.com/launchdarkly/go-sdk-common/v3/ldvalue" + "github.com/launchdarkly/ldcli/internal/dev_server/model" +) + +type Sqlite struct { + database *sql.DB +} + +func (s Sqlite) GetDevProjectKeys(ctx context.Context) ([]string, error) { + rows, err := s.database.Query("select key from projects") + if err != nil { + return nil, err + } + var keys []string + for rows.Next() { + var key string + err = rows.Scan(&key) + if err != nil { + return nil, err + } + keys = append(keys, key) + } + return keys, nil +} + +func (s Sqlite) GetDevProject(ctx context.Context, key string) (*model.Project, error) { + var project model.Project + var contextData string + var flagStateData string + + row := s.database.QueryRowContext(ctx, ` + SELECT key, source_environment_key, context, last_sync_time, flag_state + FROM projects + WHERE key = ? + `, key) + + if err := row.Scan(&project.Key, &project.SourceEnvironmentKey, &contextData, &project.LastSyncTime, &flagStateData); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, errors.Wrapf(model.ErrNotFound, "no project found with key, '%s'", key) + } + return nil, err + } + + // Parse the context JSON string + if err := json.Unmarshal([]byte(contextData), &project.Context); err != nil { + return nil, errors.Wrap(err, "unable to unmarshal context data") + } + + // Parse the flag state JSON string + if err := json.Unmarshal([]byte(flagStateData), &project.AllFlagsState); err != nil { + return nil, errors.Wrap(err, "unable to unmarshal flag state data") + } + + return &project, nil +} + +func (s Sqlite) UpdateProject(ctx context.Context, project model.Project) (bool, error) { + flagsStateJson, err := json.Marshal(project.AllFlagsState) + if err != nil { + return false, errors.Wrap(err, "unable to marshal flags state when updating project") + } + + result, err := s.database.ExecContext(ctx, ` + UPDATE projects + SET flag_state = ?, last_sync_time = ?, context=? + WHERE key = ?; + `, flagsStateJson, project.LastSyncTime, project.Context.JSONString(), project.Key) + if err != nil { + return false, errors.Wrap(err, "unable to execute update project") + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return false, err + } + if rowsAffected == 0 { + return false, nil + } + + return true, nil +} + +func (s Sqlite) DeleteDevProject(ctx context.Context, key string) (bool, error) { + result, err := s.database.Exec("DELETE FROM projects where key=?", key) + if err != nil { + return false, err + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return false, err + } + if rowsAffected == 0 { + return false, nil + } + return true, nil +} + +func (s Sqlite) InsertProject(ctx context.Context, project model.Project) (err error) { + flagsStateJson, err := json.Marshal(project.AllFlagsState) + if err != nil { + return errors.Wrap(err, "unable to marshal flags state when writing project") + } + tx, err := s.database.BeginTx(ctx, nil) + if err != nil { + return + } + defer func() { + if err != nil { + _ = tx.Rollback() + } + }() + + projects, err := tx.QueryContext(ctx, ` +SELECT 1 FROM projects WHERE key = ? +`, project.Key) + if err != nil { + return + } + if projects.Next() { + err = model.ErrAlreadyExists + return + } + err = projects.Close() + if err != nil { + return + } + _, err = tx.Exec(` +INSERT INTO projects (key, source_environment_key, context, last_sync_time, flag_state) +VALUES (?, ?, ?, ?, ?) +`, + project.Key, + project.SourceEnvironmentKey, + project.Context.JSONString(), + project.LastSyncTime, + string(flagsStateJson), + ) + if err != nil { + return + } + err = tx.Commit() + return +} + +func (s Sqlite) GetOverridesForProject(ctx context.Context, projectKey string) (model.Overrides, error) { + rows, err := s.database.QueryContext(ctx, ` + SELECT flag_key, active, value, version + FROM overrides + WHERE project_key = ? + `, projectKey) + + if err != nil { + return nil, err + } + defer rows.Close() + + overrides := make(model.Overrides, 0) + for rows.Next() { + var flagKey string + var active bool + var value string + var version int + + err = rows.Scan(&flagKey, &active, &value, &version) + if err != nil { + return nil, err + } + + var ldValue ldvalue.Value + err = json.Unmarshal([]byte(value), &ldValue) + if err != nil { + return nil, err + } + overrides = append(overrides, model.Override{ + ProjectKey: projectKey, + FlagKey: flagKey, + Value: ldValue, + Active: active, + Version: version, + }) + } + + if err = rows.Err(); err != nil { + return nil, err + } + + return overrides, nil +} + +func (s Sqlite) UpsertOverride(ctx context.Context, override model.Override) (model.Override, error) { + valueJson, err := override.Value.MarshalJSON() + if err != nil { + return model.Override{}, errors.Wrap(err, "unable to marshal override value when writing override") + } + row := s.database.QueryRowContext(ctx, ` + INSERT INTO overrides (project_key, flag_key, value, active) + VALUES (?, ?, ?, ?) + ON CONFLICT(flag_key, project_key) DO UPDATE SET + value=excluded.value, + active=excluded.active, + version=version+1 + RETURNING project_key, flag_key, active, value, version; + `, + override.ProjectKey, + override.FlagKey, + valueJson, + override.Active, + ) + var tempValue []byte + if err := row.Scan(&override.ProjectKey, &override.FlagKey, &override.Active, &tempValue, &override.Version); err != nil { + return model.Override{}, errors.Wrap(err, "unable to upsert override") + } + if err := json.Unmarshal(tempValue, &override.Value); err != nil { + return model.Override{}, errors.Wrap(err, "unable to unmarshal override value") + } + return override, nil +} + +func (s Sqlite) DeactivateOverride(ctx context.Context, projectKey, flagKey string) error { + result, err := s.database.Exec(` + UPDATE overrides set active = false, version = version+1 where project_key = ? and flag_key = ? and active = true + `, + projectKey, + flagKey, + ) + if err != nil { + return err + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return err + } + if rowsAffected == 0 { + return model.ErrNotFound + } + + return nil +} + +func NewSqlite(ctx context.Context, dbPath string) (Sqlite, error) { + store := new(Sqlite) + db, err := sql.Open("sqlite3", dbPath) + if err != nil { + return Sqlite{}, err + } + store.database = db + err = store.runMigrations(ctx) + if err != nil { + return Sqlite{}, err + } + return *store, nil +} + +func (s Sqlite) runMigrations(ctx context.Context) error { + tx, err := s.database.BeginTx(ctx, nil) + if err != nil { + return err + } + _, err = tx.Exec(` + CREATE TABLE IF NOT EXISTS projects ( + key text PRIMARY KEY, + source_environment_key text NOT NULL, + context text NOT NULL, + last_sync_time timestamp NOT NULL, + flag_state TEXT NOT NULL + )`) + if err != nil { + return err + } + + _, err = tx.Exec(` + CREATE TABLE IF NOT EXISTS overrides ( + project_key text NOT NULL, + flag_key text NOT NULL, + value text NOT NULL, + active boolean NOT NULL default TRUE, + version integer NOT NULL default 1, + UNIQUE (project_key, flag_key) ON CONFLICT REPLACE + )`) + if err != nil { + return err + } + return tx.Commit() +} diff --git a/internal/dev_server/db/sqlite_test.go b/internal/dev_server/db/sqlite_test.go new file mode 100644 index 00000000..acbed71f --- /dev/null +++ b/internal/dev_server/db/sqlite_test.go @@ -0,0 +1,223 @@ +package db_test + +import ( + "context" + "os" + "testing" + "time" + + "github.com/launchdarkly/go-sdk-common/v3/ldcontext" + "github.com/launchdarkly/go-sdk-common/v3/ldvalue" + "github.com/launchdarkly/ldcli/internal/dev_server/db" + "github.com/launchdarkly/ldcli/internal/dev_server/model" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDBFunctions(t *testing.T) { + ctx := context.Background() + dbName := "test.db" + + store, err := db.NewSqlite(ctx, dbName) + require.NoError(t, err) + + defer func() { + require.NoError(t, os.Remove(dbName)) + }() + + ldContext := ldcontext.New(t.Name()) + now := time.Now() + + projects := []model.Project{ + { + Key: "main-test-proj", + SourceEnvironmentKey: "env-1", + Context: ldContext, + LastSyncTime: now, + AllFlagsState: model.FlagsState{ + "flag-1": model.FlagState{Value: ldvalue.Bool(true), Version: 2}, + "flag-2": model.FlagState{Value: ldvalue.String("cool"), Version: 2}, + }, + }, + { + Key: "proj-to-delete", + SourceEnvironmentKey: "env-2", + Context: ldContext, + LastSyncTime: now, + AllFlagsState: model.FlagsState{ + "flag-1": model.FlagState{Value: ldvalue.Int(123), Version: 2}, + "flag-2": model.FlagState{Value: ldvalue.Float64(99.99), Version: 2}, + }, + }, + } + actualProjectKeys := make(map[string]bool, len(projects)) + + for _, proj := range projects { + err := store.InsertProject(ctx, proj) + require.NoError(t, err) + actualProjectKeys[proj.Key] = true + } + + t.Run("InsertProject returns ErrAlreadyExists if the project already exists", func(t *testing.T) { + err := store.InsertProject(ctx, projects[0]) + assert.Equal(t, model.ErrAlreadyExists, err) + }) + + t.Run("GetDevProjectKeys returns keys in projects", func(t *testing.T) { + keys, err := store.GetDevProjectKeys(ctx) + assert.NoError(t, err) + assert.Len(t, keys, len(projects)) + + for _, key := range keys { + _, ok := actualProjectKeys[key] + assert.True(t, ok) + } + }) + + t.Run("GetDevProject returns ErrNotFound for fake project keys", func(t *testing.T) { + p, err := store.GetDevProject(ctx, "THIS-DOES-NOT-EXIST") + assert.Nil(t, p) + assert.ErrorIs(t, err, model.ErrNotFound) + }) + + t.Run("GetDevProject returns project", func(t *testing.T) { + expected := projects[0] + p, err := store.GetDevProject(ctx, expected.Key) + + assert.NoError(t, err) + assert.NotNil(t, p) + assert.Equal(t, expected.Key, p.Key) + assert.Equal(t, expected.AllFlagsState, p.AllFlagsState) + assert.Equal(t, expected.SourceEnvironmentKey, p.SourceEnvironmentKey) + assert.Equal(t, expected.Context, p.Context) + assert.True(t, expected.LastSyncTime.Equal(p.LastSyncTime)) + }) + + t.Run("UpdateProject updates flag state, sync time, context but not source environment key", func(t *testing.T) { + projects[0].Context = ldcontext.New(t.Name() + "blah") + projects[0].AllFlagsState = model.FlagsState{ + "flag-1": model.FlagState{Value: ldvalue.Bool(false), Version: 3}, + "flag-2": model.FlagState{Value: ldvalue.String("cool beeans"), Version: 3}, + } + projects[0].LastSyncTime = time.Now().Add(time.Hour) + oldSourceEnvKey := projects[0].SourceEnvironmentKey + projects[0].SourceEnvironmentKey = "new-env" + + updated, err := store.UpdateProject(ctx, projects[0]) + assert.NoError(t, err) + assert.True(t, updated) + + newProj, err := store.GetDevProject(ctx, projects[0].Key) + assert.NoError(t, err) + assert.NotNil(t, newProj) + assert.Equal(t, projects[0].Key, newProj.Key) + assert.Equal(t, projects[0].AllFlagsState, newProj.AllFlagsState) + assert.Equal(t, oldSourceEnvKey, newProj.SourceEnvironmentKey) + assert.Equal(t, projects[0].Context, newProj.Context) + assert.True(t, projects[0].LastSyncTime.Equal(newProj.LastSyncTime)) + }) + + t.Run("UpdateProject returns false if project does not exist", func(t *testing.T) { + updated, err := store.UpdateProject(ctx, model.Project{Key: "nope"}) + assert.NoError(t, err) + assert.False(t, updated) + }) + + t.Run("DeleteProject returns false if project does not exist", func(t *testing.T) { + deleted, err := store.DeleteDevProject(ctx, "nope") + assert.NoError(t, err) + assert.False(t, deleted) + }) + + t.Run("DeleteProject succeeds if project exists", func(t *testing.T) { + deleted, err := store.DeleteDevProject(ctx, projects[1].Key) + assert.NoError(t, err) + assert.True(t, deleted) + }) + + flagKeys := []string{"flag-1", "flag-2"} + + overrides := map[string]model.Override{ + flagKeys[0]: { + ProjectKey: projects[0].Key, + FlagKey: flagKeys[0], + Value: ldvalue.Bool(true), + Active: true, + Version: 1, + }, + flagKeys[1]: { + ProjectKey: projects[0].Key, + FlagKey: flagKeys[1], + Value: ldvalue.Int(100), + Active: true, + Version: 1, + }, + } + + // test inserts + for _, o := range overrides { + _, err := store.UpsertOverride(ctx, o) + require.NoError(t, err) + } + + overridesResult, err := store.GetOverridesForProject(ctx, projects[0].Key) + require.NoError(t, err) + require.Len(t, overridesResult, 2) + + for _, r := range overridesResult { + originalOverride, ok := overrides[r.FlagKey] + require.True(t, ok) + require.Equal(t, originalOverride, r) + } + + t.Run("UpsertOverride updates when override exists", func(t *testing.T) { + updated := overrides[flagKeys[1]] + updated.Value = ldvalue.Int(101) + + _, err := store.UpsertOverride(ctx, updated) + assert.NoError(t, err) + + overridesResult, err := store.GetOverridesForProject(ctx, projects[0].Key) + assert.NoError(t, err) + assert.Len(t, overridesResult, 2) + + found := false // prevent test from erroneously succeeding because override not in array + for _, r := range overridesResult { + if r.FlagKey != flagKeys[1] { + continue + } + + found = true + assert.Equal(t, updated.Value, r.Value) + } + + assert.True(t, found) + }) + + t.Run("DeactivateOverride returns error when override not found", func(t *testing.T) { + err := store.DeactivateOverride(ctx, projects[0].Key, "nope") + assert.ErrorIs(t, err, model.ErrNotFound) + }) + + t.Run("DeactivateOverride sets the override inactive", func(t *testing.T) { + toDelete := overrides[flagKeys[0]] + err := store.DeactivateOverride(ctx, toDelete.ProjectKey, toDelete.FlagKey) + assert.NoError(t, err) + + result, err := store.GetOverridesForProject(ctx, toDelete.ProjectKey) + assert.NoError(t, err) + assert.Len(t, result, 2) + + found := false // prevent test from erroneously succeeding because override not in array + for _, r := range result { + if r.FlagKey != toDelete.FlagKey { + continue + } + + found = true + assert.False(t, r.Active) + } + + assert.True(t, found) + }) +} diff --git a/internal/dev_server/dev_server.go b/internal/dev_server/dev_server.go new file mode 100644 index 00000000..223689e0 --- /dev/null +++ b/internal/dev_server/dev_server.go @@ -0,0 +1,93 @@ +package dev_server + +import ( + "context" + "fmt" + "log" + "net/http" + "os" + + "github.com/adrg/xdg" + "github.com/gorilla/handlers" + "github.com/gorilla/mux" + "github.com/launchdarkly/ldcli/internal/client" + "github.com/launchdarkly/ldcli/internal/dev_server/adapters" + "github.com/launchdarkly/ldcli/internal/dev_server/api" + "github.com/launchdarkly/ldcli/internal/dev_server/db" + "github.com/launchdarkly/ldcli/internal/dev_server/model" + "github.com/launchdarkly/ldcli/internal/dev_server/sdk" + "github.com/launchdarkly/ldcli/internal/dev_server/ui" +) + +type Client interface { + RunServer(ctx context.Context, serverParams ServerParams) +} + +type ServerParams struct { + AccessToken string + BaseURI string + DevStreamURI string + Port string +} + +type LDClient struct { + cliVersion string +} + +var _ Client = LDClient{} + +func NewClient(cliVersion string) LDClient { + return LDClient{cliVersion: cliVersion} +} + +func (c LDClient) RunServer(ctx context.Context, serverParams ServerParams) { + ldClient := client.New(serverParams.AccessToken, serverParams.BaseURI, c.cliVersion) + dbPath := getDBPath() + log.Printf("Using database at %s", dbPath) + sqlStore, err := db.NewSqlite(ctx, getDBPath()) + if err != nil { + log.Fatal(err) + } + ss := api.NewStrictServer() + apiServer := api.NewStrictHandlerWithOptions(ss, nil, api.StrictHTTPServerOptions{ + RequestErrorHandlerFunc: RequestErrorHandler, + ResponseErrorHandlerFunc: ResponseErrorHandler, + }) + r := mux.NewRouter() + r.Use(adapters.Middleware(*ldClient, serverParams.DevStreamURI)) + r.Use(model.StoreMiddleware(sqlStore)) + r.Use(model.ObserversMiddleware(model.NewObservers())) + r.Handle("/", http.RedirectHandler("/ui/", http.StatusFound)) + r.Handle("/ui", http.RedirectHandler("/ui/", http.StatusMovedPermanently)) + r.PathPrefix("/ui/").Handler(http.StripPrefix("/ui/", ui.AssetHandler)) + sdk.BindRoutes(r) + handler := api.HandlerFromMux(apiServer, r) + handler = handlers.CombinedLoggingHandler(os.Stdout, handler) + handler = handlers.RecoveryHandler(handlers.PrintRecoveryStack(true))(handler) + + addr := fmt.Sprintf("0.0.0.0:%s", serverParams.Port) + log.Printf("Server running on %s", addr) + log.Printf("Access the UI for toggling overrides at http://localhost:%s/ui or by running `ldcli dev-server ui`", serverParams.Port) + server := http.Server{ + Addr: addr, + Handler: handler, + } + log.Fatal(server.ListenAndServe()) +} + +func ResponseErrorHandler(w http.ResponseWriter, r *http.Request, err error) { + log.Printf("Error while serving response: %+v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) +} +func RequestErrorHandler(w http.ResponseWriter, r *http.Request, err error) { + log.Printf("Error while serving request: %+v", err) + http.Error(w, err.Error(), http.StatusBadRequest) +} + +func getDBPath() string { + dbFilePath, err := xdg.StateFile("ldcli/dev_server.db") + if err != nil { + log.Fatalf("Unable to create state directory: %s", err) + } + return dbFilePath +} diff --git a/internal/dev_server/model/docs.go b/internal/dev_server/model/docs.go new file mode 100644 index 00000000..8d689924 --- /dev/null +++ b/internal/dev_server/model/docs.go @@ -0,0 +1,2 @@ +// Package model provides dev projects and flag override data models for the api handler to use. It exists so that it can also notify observers to support streaming updates +package model diff --git a/internal/dev_server/model/error.go b/internal/dev_server/model/error.go new file mode 100644 index 00000000..55d6d463 --- /dev/null +++ b/internal/dev_server/model/error.go @@ -0,0 +1,23 @@ +package model + +import "github.com/pkg/errors" + +type Error struct { + err error + message string +} + +func (e Error) Error() string { + return e.message +} + +func (e Error) Unwrap() error { + return e.err +} + +func NewError(message string) error { + return errors.WithStack(Error{ + err: errors.New(message), + message: message, + }) +} diff --git a/internal/dev_server/model/events.go b/internal/dev_server/model/events.go new file mode 100644 index 00000000..1701322a --- /dev/null +++ b/internal/dev_server/model/events.go @@ -0,0 +1,14 @@ +package model + +// Event for individual flag overrides +type UpsertOverrideEvent struct { + FlagKey string + ProjectKey string + FlagState FlagState +} + +// Event for full project sync +type SyncEvent struct { + ProjectKey string + AllFlagsState FlagsState +} diff --git a/internal/dev_server/model/flags_state.go b/internal/dev_server/model/flags_state.go new file mode 100644 index 00000000..08788c6e --- /dev/null +++ b/internal/dev_server/model/flags_state.go @@ -0,0 +1,30 @@ +package model + +import ( + "github.com/launchdarkly/go-sdk-common/v3/ldvalue" + "github.com/launchdarkly/go-server-sdk/v7/interfaces/flagstate" +) + +type FlagState struct { + Value ldvalue.Value `json:"value"` + Version int `json:"version"` +} + +type FlagsState map[string]FlagState + +func FromAllFlags(sdkFlags flagstate.AllFlags) FlagsState { + flags := sdkFlags.ToValuesMap() + flagsState := make(FlagsState, len(flags)) + for key, value := range flags { + sdkFlag, ok := sdkFlags.GetFlag(key) + if !ok { + // panic because we're iterating over the same set of keys + panic("flag '" + key + "' not found") + } + flagsState[key] = FlagState{ + Value: value, + Version: sdkFlag.Version, + } + } + return flagsState +} diff --git a/internal/dev_server/model/flags_state_test.go b/internal/dev_server/model/flags_state_test.go new file mode 100644 index 00000000..e79d6c9e --- /dev/null +++ b/internal/dev_server/model/flags_state_test.go @@ -0,0 +1,45 @@ +package model_test + +import ( + "testing" + + "github.com/launchdarkly/go-sdk-common/v3/ldvalue" + "github.com/launchdarkly/go-server-sdk/v7/interfaces/flagstate" + "github.com/launchdarkly/ldcli/internal/dev_server/model" + "github.com/stretchr/testify/assert" +) + +func TestFromAllFlags(t *testing.T) { + sdkFlags := flagstate.NewAllFlagsBuilder(). + AddFlag("boolFlag", flagstate.FlagState{Value: ldvalue.Bool(true), Version: 1}). + AddFlag("stringFlag", flagstate.FlagState{Value: ldvalue.String("cool"), Version: 1}). + AddFlag("intFlag", flagstate.FlagState{Value: ldvalue.Int(123), Version: 1}). + AddFlag("doubleFlag", flagstate.FlagState{Value: ldvalue.Float64(99.99), Version: 1}). + AddFlag("jsonFlag", flagstate.FlagState{Value: ldvalue.CopyArbitraryValue(map[string]any{"cat": "hat"}), Version: 1}). + Build() + + flagState := model.FromAllFlags(sdkFlags) + expectedVersion := 1 + + for key, state := range flagState { + var expectedVal ldvalue.Value + + switch key { + case "boolFlag": + expectedVal = ldvalue.Bool(true) + case "stringFlag": + expectedVal = ldvalue.String("cool") + case "intFlag": + expectedVal = ldvalue.Int(123) + case "doubleFlag": + expectedVal = ldvalue.Float64(99.99) + case "jsonFlag": + expectedVal = ldvalue.CopyArbitraryValue(map[string]any{"cat": "hat"}) + default: + assert.Failf(t, "unknown flag, %s", key) + } + + assert.Equal(t, expectedVersion, state.Version) + assert.True(t, expectedVal.Equal(state.Value)) + } +} diff --git a/internal/dev_server/model/mocks/observer.go b/internal/dev_server/model/mocks/observer.go new file mode 100644 index 00000000..5c4b96bd --- /dev/null +++ b/internal/dev_server/model/mocks/observer.go @@ -0,0 +1,51 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/launchdarkly/ldcli/internal/dev_server/model (interfaces: Observer) +// +// Generated by this command: +// +// mockgen -destination mocks/observer.go -package mocks . Observer +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockObserver is a mock of Observer interface. +type MockObserver struct { + ctrl *gomock.Controller + recorder *MockObserverMockRecorder +} + +// MockObserverMockRecorder is the mock recorder for MockObserver. +type MockObserverMockRecorder struct { + mock *MockObserver +} + +// NewMockObserver creates a new mock instance. +func NewMockObserver(ctrl *gomock.Controller) *MockObserver { + mock := &MockObserver{ctrl: ctrl} + mock.recorder = &MockObserverMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockObserver) EXPECT() *MockObserverMockRecorder { + return m.recorder +} + +// Handle mocks base method. +func (m *MockObserver) Handle(arg0 any) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Handle", arg0) +} + +// Handle indicates an expected call of Handle. +func (mr *MockObserverMockRecorder) Handle(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Handle", reflect.TypeOf((*MockObserver)(nil).Handle), arg0) +} diff --git a/internal/dev_server/model/mocks/store.go b/internal/dev_server/model/mocks/store.go new file mode 100644 index 00000000..2ea32011 --- /dev/null +++ b/internal/dev_server/model/mocks/store.go @@ -0,0 +1,159 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/launchdarkly/ldcli/internal/dev_server/model (interfaces: Store) +// +// Generated by this command: +// +// mockgen -destination mocks/store.go -package mocks . Store +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + model "github.com/launchdarkly/ldcli/internal/dev_server/model" + gomock "go.uber.org/mock/gomock" +) + +// MockStore is a mock of Store interface. +type MockStore struct { + ctrl *gomock.Controller + recorder *MockStoreMockRecorder +} + +// MockStoreMockRecorder is the mock recorder for MockStore. +type MockStoreMockRecorder struct { + mock *MockStore +} + +// NewMockStore creates a new mock instance. +func NewMockStore(ctrl *gomock.Controller) *MockStore { + mock := &MockStore{ctrl: ctrl} + mock.recorder = &MockStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStore) EXPECT() *MockStoreMockRecorder { + return m.recorder +} + +// DeactivateOverride mocks base method. +func (m *MockStore) DeactivateOverride(arg0 context.Context, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeactivateOverride", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeactivateOverride indicates an expected call of DeactivateOverride. +func (mr *MockStoreMockRecorder) DeactivateOverride(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeactivateOverride", reflect.TypeOf((*MockStore)(nil).DeactivateOverride), arg0, arg1, arg2) +} + +// DeleteDevProject mocks base method. +func (m *MockStore) DeleteDevProject(arg0 context.Context, arg1 string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteDevProject", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteDevProject indicates an expected call of DeleteDevProject. +func (mr *MockStoreMockRecorder) DeleteDevProject(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDevProject", reflect.TypeOf((*MockStore)(nil).DeleteDevProject), arg0, arg1) +} + +// GetDevProject mocks base method. +func (m *MockStore) GetDevProject(arg0 context.Context, arg1 string) (*model.Project, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDevProject", arg0, arg1) + ret0, _ := ret[0].(*model.Project) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetDevProject indicates an expected call of GetDevProject. +func (mr *MockStoreMockRecorder) GetDevProject(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDevProject", reflect.TypeOf((*MockStore)(nil).GetDevProject), arg0, arg1) +} + +// GetDevProjectKeys mocks base method. +func (m *MockStore) GetDevProjectKeys(arg0 context.Context) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDevProjectKeys", arg0) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetDevProjectKeys indicates an expected call of GetDevProjectKeys. +func (mr *MockStoreMockRecorder) GetDevProjectKeys(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDevProjectKeys", reflect.TypeOf((*MockStore)(nil).GetDevProjectKeys), arg0) +} + +// GetOverridesForProject mocks base method. +func (m *MockStore) GetOverridesForProject(arg0 context.Context, arg1 string) (model.Overrides, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOverridesForProject", arg0, arg1) + ret0, _ := ret[0].(model.Overrides) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOverridesForProject indicates an expected call of GetOverridesForProject. +func (mr *MockStoreMockRecorder) GetOverridesForProject(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOverridesForProject", reflect.TypeOf((*MockStore)(nil).GetOverridesForProject), arg0, arg1) +} + +// InsertProject mocks base method. +func (m *MockStore) InsertProject(arg0 context.Context, arg1 model.Project) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InsertProject", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// InsertProject indicates an expected call of InsertProject. +func (mr *MockStoreMockRecorder) InsertProject(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertProject", reflect.TypeOf((*MockStore)(nil).InsertProject), arg0, arg1) +} + +// UpdateProject mocks base method. +func (m *MockStore) UpdateProject(arg0 context.Context, arg1 model.Project) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateProject", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateProject indicates an expected call of UpdateProject. +func (mr *MockStoreMockRecorder) UpdateProject(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateProject", reflect.TypeOf((*MockStore)(nil).UpdateProject), arg0, arg1) +} + +// UpsertOverride mocks base method. +func (m *MockStore) UpsertOverride(arg0 context.Context, arg1 model.Override) (model.Override, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpsertOverride", arg0, arg1) + ret0, _ := ret[0].(model.Override) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpsertOverride indicates an expected call of UpsertOverride. +func (mr *MockStoreMockRecorder) UpsertOverride(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertOverride", reflect.TypeOf((*MockStore)(nil).UpsertOverride), arg0, arg1) +} diff --git a/internal/dev_server/model/observer.go b/internal/dev_server/model/observer.go new file mode 100644 index 00000000..e66c6290 --- /dev/null +++ b/internal/dev_server/model/observer.go @@ -0,0 +1,45 @@ +package model + +import ( + "log" + "sync" + + "github.com/google/uuid" +) + +//go:generate go run go.uber.org/mock/mockgen -destination mocks/observer.go -package mocks . Observer + +type Observer interface { + Handle(interface{}) +} + +type Observers struct { + observers sync.Map +} + +func NewObservers() *Observers { + observers := new(Observers) + observers.observers = sync.Map{} + return observers +} + +func (o *Observers) DeregisterObserver(observerId uuid.UUID) bool { + log.Printf("DeregisterObserver: observerId %+v", observerId) + _, exists := o.observers.LoadAndDelete(observerId) + return exists +} + +func (o *Observers) RegisterObserver(observer Observer) uuid.UUID { + id := uuid.New() + log.Printf("RegisterObserver: observer %+v, id %s", observer, id) + o.observers.Store(id, observer) + return id +} + +func (o *Observers) Notify(event interface{}) { + log.Printf("Notify: event %+v to observers", event) + o.observers.Range(func(_, observer any) bool { + observer.(Observer).Handle(event) + return true + }) +} diff --git a/internal/dev_server/model/observer_middleware.go b/internal/dev_server/model/observer_middleware.go new file mode 100644 index 00000000..ffa3d337 --- /dev/null +++ b/internal/dev_server/model/observer_middleware.go @@ -0,0 +1,25 @@ +package model + +import ( + "context" + "net/http" +) + +const observersKey = ctxKey("model.observers") + +func SetObserversOnContext(ctx context.Context, observers *Observers) context.Context { + return context.WithValue(ctx, observersKey, observers) +} +func GetObserversFromContext(ctx context.Context) *Observers { + return ctx.Value(observersKey).(*Observers) +} +func ObserversMiddleware(observers *Observers) func(handler http.Handler) http.Handler { + return func(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + ctx = SetObserversOnContext(ctx, observers) + r = r.WithContext(ctx) + handler.ServeHTTP(w, r) + }) + } +} diff --git a/internal/dev_server/model/observer_test.go b/internal/dev_server/model/observer_test.go new file mode 100644 index 00000000..7a5cef91 --- /dev/null +++ b/internal/dev_server/model/observer_test.go @@ -0,0 +1,60 @@ +package model_test + +import ( + "sync" + "testing" + + "github.com/google/uuid" + "github.com/launchdarkly/ldcli/internal/dev_server/model" + "github.com/stretchr/testify/assert" +) + +type testObserver struct { + handle func(interface{}) +} + +func (o testObserver) Handle(event interface{}) { o.handle(event) } + +func TestObservers(t *testing.T) { + t.Run("Register then notify yields notification", func(t *testing.T) { + observers := model.NewObservers() + observerCalled := false + observer := testObserver{handle: func(i interface{}) { + observerCalled = true + }} + observers.RegisterObserver(observer) + observers.Notify("lol") + assert.True(t, observerCalled, "observer should be called") + }) + t.Run("Register, deregister then notify yields NO notification", func(t *testing.T) { + observers := model.NewObservers() + observer := testObserver{handle: func(i interface{}) { + assert.Fail(t, "should not be called") + }} + id := observers.RegisterObserver(observer) + ok := observers.DeregisterObserver(id) + assert.True(t, ok, "observer should be deregistered") + observers.Notify("lol") + }) + t.Run("deregistering from multiple go routines should not panic", func(t *testing.T) { + observers := model.NewObservers() + observer := testObserver{handle: func(i interface{}) { + assert.Fail(t, "should not be called") + }} + ids := make([]uuid.UUID, 100) + for i := 0; i < 100; i++ { + i := i + ids[i] = observers.RegisterObserver(observer) + } + wg := sync.WaitGroup{} + for _, id := range ids { + id := id + wg.Add(1) + go func() { + observers.DeregisterObserver(id) + wg.Done() + }() + } + wg.Wait() + }) +} diff --git a/internal/dev_server/model/override.go b/internal/dev_server/model/override.go new file mode 100644 index 00000000..f2eb4918 --- /dev/null +++ b/internal/dev_server/model/override.go @@ -0,0 +1,82 @@ +package model + +import ( + "context" + + "github.com/launchdarkly/go-sdk-common/v3/ldvalue" +) + +type Override struct { + ProjectKey string + FlagKey string + Value ldvalue.Value + Active bool + Version int +} + +func UpsertOverride(ctx context.Context, projectKey, flagKey string, value ldvalue.Value) (Override, error) { + // TODO: validate if the flag type matches + + store := StoreFromContext(ctx) + + project, err := store.GetDevProject(ctx, projectKey) + if err != nil || project == nil { + return Override{}, NewError("project does not exist within dev server") + } + + var flagExists bool + for flag := range project.AllFlagsState { + if flagKey == flag { + flagExists = true + break + } + } + if !flagExists { + return Override{}, NewError("flag does not exist within dev project") + } + + override := Override{ + ProjectKey: projectKey, + FlagKey: flagKey, + Value: value, + Active: true, + Version: 1, + } + + override, err = store.UpsertOverride(ctx, override) + if err != nil { + return Override{}, err + } + + flagState := override.Apply(project.AllFlagsState[flagKey]) + GetObserversFromContext(ctx).Notify(UpsertOverrideEvent{ + FlagKey: flagKey, + ProjectKey: projectKey, + FlagState: flagState, + }) + + return override, nil +} + +func (o Override) Apply(state FlagState) FlagState { + flagVersion := state.Version + o.Version + flagValue := state.Value + if o.Active { + flagValue = o.Value + } + return FlagState{ + Value: flagValue, + Version: flagVersion, + } +} + +type Overrides []Override + +func (o Overrides) GetFlag(key string) (Override, bool) { + for _, override := range o { + if override.FlagKey == key { + return override, true + } + } + return Override{}, false +} diff --git a/internal/dev_server/model/override_test.go b/internal/dev_server/model/override_test.go new file mode 100644 index 00000000..6b47b563 --- /dev/null +++ b/internal/dev_server/model/override_test.go @@ -0,0 +1,121 @@ +package model_test + +import ( + "context" + "errors" + "testing" + + "github.com/launchdarkly/go-sdk-common/v3/ldvalue" + "github.com/launchdarkly/ldcli/internal/dev_server/model" + "github.com/launchdarkly/ldcli/internal/dev_server/model/mocks" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" +) + +func TestUpsertOverride(t *testing.T) { + ctx := context.Background() + mockController := gomock.NewController(t) + store := mocks.NewMockStore(mockController) + projKey := "proj" + flagKey := "flg" + ldValue := ldvalue.Bool(true) + override := model.Override{ + ProjectKey: projKey, + FlagKey: flagKey, + Value: ldValue, + Active: true, + Version: 1, + } + + project := &model.Project{ + Key: projKey, + AllFlagsState: model.FlagsState{flagKey: model.FlagState{Value: ldvalue.Bool(false), Version: 1}}, + } + + ctx = model.ContextWithStore(ctx, store) + + observers := model.NewObservers() + observer := mocks.NewMockObserver(mockController) + + observers.RegisterObserver(observer) + ctx = model.SetObserversOnContext(ctx, observers) + + t.Run("store unable to get project, returns error", func(t *testing.T) { + store.EXPECT().GetDevProject(gomock.Any(), projKey).Return(nil, errors.New("test 2")) + + _, err := model.UpsertOverride(ctx, projKey, flagKey, ldValue) + assert.Error(t, err) + assert.Contains(t, err.Error(), "project does not exist within dev server") + }) + + t.Run("Returns error if flag does not exist in project", func(t *testing.T) { + badProj := model.Project{ + Key: projKey, + AllFlagsState: model.FlagsState{}, + } + store.EXPECT().GetDevProject(gomock.Any(), projKey).Return(&badProj, nil) + + _, err := model.UpsertOverride(ctx, projKey, flagKey, ldValue) + assert.Error(t, err) + assert.Contains(t, err.Error(), "flag does not exist within dev project") + }) + + t.Run("store fails to upsert, returns error", func(t *testing.T) { + store.EXPECT().GetDevProject(gomock.Any(), projKey).Return(project, nil) + store.EXPECT().UpsertOverride(gomock.Any(), gomock.Any()).Return(model.Override{}, errors.New("testy test")) + + _, err := model.UpsertOverride(ctx, projKey, flagKey, ldValue) + assert.Error(t, err) + assert.Equal(t, "testy test", err.Error()) + }) + + t.Run("override is applied, observers are notified", func(t *testing.T) { + store.EXPECT().GetDevProject(gomock.Any(), projKey).Return(project, nil) + store.EXPECT().UpsertOverride(gomock.Any(), override).Return(override, nil) + observer. + EXPECT(). + Handle(model.UpsertOverrideEvent{ + FlagKey: flagKey, + ProjectKey: projKey, + FlagState: model.FlagState{Value: ldvalue.Bool(true), Version: 2}, + }) + + o, err := model.UpsertOverride(ctx, projKey, flagKey, ldValue) + assert.Nil(t, err) + assert.Equal(t, override, o) + }) +} + +func TestOverrideApply(t *testing.T) { + projKey := "proj" + flagKey := "flg" + ldValue := ldvalue.Bool(true) + oldState := model.FlagState{Value: ldvalue.Bool(false), Version: 1} + + t.Run("if override is inactive, increment version", func(t *testing.T) { + override := model.Override{ + ProjectKey: projKey, + FlagKey: flagKey, + Value: ldValue, + Version: 1, + } + + state := override.Apply(oldState) + assert.False(t, state.Value.BoolValue()) + assert.Equal(t, 2, state.Version) + }) + + t.Run("if override is active, increment version AND update value", func(t *testing.T) { + override := model.Override{ + ProjectKey: projKey, + FlagKey: flagKey, + Value: ldValue, + Active: true, + Version: 1, + } + + state := override.Apply(oldState) + assert.True(t, state.Value.BoolValue()) + assert.Equal(t, 2, state.Version) + }) +} diff --git a/internal/dev_server/model/project.go b/internal/dev_server/model/project.go new file mode 100644 index 00000000..6d2677cb --- /dev/null +++ b/internal/dev_server/model/project.go @@ -0,0 +1,149 @@ +package model + +import ( + "context" + "time" + + "github.com/launchdarkly/go-sdk-common/v3/ldcontext" + "github.com/launchdarkly/ldcli/internal/dev_server/adapters" + "github.com/pkg/errors" +) + +type Project struct { + Key string + SourceEnvironmentKey string + Context ldcontext.Context + LastSyncTime time.Time + AllFlagsState FlagsState +} + +// CreateProject creates a project and adds it to the database. +func CreateProject(ctx context.Context, projectKey, sourceEnvironmentKey string, ldCtx *ldcontext.Context) (Project, error) { + project := Project{ + Key: projectKey, + SourceEnvironmentKey: sourceEnvironmentKey, + } + + if ldCtx == nil { + project.Context = ldcontext.NewBuilder("user").Key("dev-environment").Build() + } else { + project.Context = *ldCtx + } + + flagsState, err := project.FetchFlagState(ctx) + if err != nil { + return Project{}, err + } + + project.AllFlagsState = flagsState + project.LastSyncTime = time.Now() + + store := StoreFromContext(ctx) + err = store.InsertProject(ctx, project) + if err != nil { + return Project{}, err + } + return project, nil +} + +func UpdateProject(ctx context.Context, projectKey string, context *ldcontext.Context, sourceEnvironmentKey *string) (Project, error) { + store := StoreFromContext(ctx) + project, err := store.GetDevProject(ctx, projectKey) + if err != nil { + return Project{}, err + } + if context != nil { + project.Context = *context + } + + if sourceEnvironmentKey != nil { + project.SourceEnvironmentKey = *sourceEnvironmentKey + } + + if context != nil || sourceEnvironmentKey != nil { + flagsState, err := project.FetchFlagState(ctx) + if err != nil { + return Project{}, err + } + project.AllFlagsState = flagsState + project.LastSyncTime = time.Now() + } + + updated, err := store.UpdateProject(ctx, *project) + if err != nil { + return Project{}, err + } + if !updated { + return Project{}, errors.New("Project not updated") + } + + return *project, nil +} + +func SyncProject(ctx context.Context, projectKey string) (Project, error) { + store := StoreFromContext(ctx) + project, err := store.GetDevProject(ctx, projectKey) + if err != nil { + return Project{}, err + } + flagsState, err := project.FetchFlagState(ctx) + if err != nil { + return Project{}, err + } + + project.AllFlagsState = flagsState + project.LastSyncTime = time.Now() + + updated, err := store.UpdateProject(ctx, *project) + if err != nil { + return Project{}, err + } + if !updated { + return Project{}, errors.New("Project not updated") + } + + allFlagsWithOverrides, err := project.GetFlagStateWithOverridesForProject(ctx) + if err != nil { + return Project{}, errors.Wrapf(err, "unable to get overrides for project, %s", projectKey) + } + + GetObserversFromContext(ctx).Notify(SyncEvent{ + ProjectKey: project.Key, + AllFlagsState: allFlagsWithOverrides, + }) + return *project, nil +} + +func (p Project) GetFlagStateWithOverridesForProject(ctx context.Context) (FlagsState, error) { + store := StoreFromContext(ctx) + overrides, err := store.GetOverridesForProject(ctx, p.Key) + if err != nil { + return FlagsState{}, errors.Wrapf(err, "unable to fetch overrides for project %s", p.Key) + } + withOverrides := make(FlagsState, len(p.AllFlagsState)) + for flagKey, flagState := range p.AllFlagsState { + if override, ok := overrides.GetFlag(flagKey); ok { + flagState = override.Apply(flagState) + } + withOverrides[flagKey] = flagState + } + return withOverrides, nil +} + +func (p Project) FetchFlagState(ctx context.Context) (FlagsState, error) { + apiAdapter := adapters.GetApi(ctx) + sdkKey, err := apiAdapter.GetSdkKey(ctx, p.Key, p.SourceEnvironmentKey) + flagsState := make(FlagsState) + if err != nil { + return flagsState, err + } + + sdkAdapter := adapters.GetSdk(ctx) + sdkFlags, err := sdkAdapter.GetAllFlagsState(ctx, p.Context, sdkKey) + if err != nil { + return flagsState, err + } + + flagsState = FromAllFlags(sdkFlags) + return flagsState, nil +} diff --git a/internal/dev_server/model/project_test.go b/internal/dev_server/model/project_test.go new file mode 100644 index 00000000..9add089c --- /dev/null +++ b/internal/dev_server/model/project_test.go @@ -0,0 +1,287 @@ +package model_test + +import ( + "context" + "errors" + "testing" + + "github.com/launchdarkly/go-sdk-common/v3/ldcontext" + "github.com/launchdarkly/go-sdk-common/v3/ldvalue" + "github.com/launchdarkly/go-server-sdk/v7/interfaces/flagstate" + adapters_mocks "github.com/launchdarkly/ldcli/internal/dev_server/adapters/mocks" + "github.com/launchdarkly/ldcli/internal/dev_server/model" + "github.com/launchdarkly/ldcli/internal/dev_server/model/mocks" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" +) + +func TestCreateProject(t *testing.T) { + ctx := context.Background() + mockController := gomock.NewController(t) + ctx, api, sdk := adapters_mocks.WithMockApiAndSdk(ctx, mockController) + store := mocks.NewMockStore(mockController) + ctx = model.ContextWithStore(ctx, store) + projKey := "proj" + sourceEnvKey := "env" + sdkKey := "thing" + + allFlags := flagstate.NewAllFlagsBuilder(). + AddFlag("boolFlag", flagstate.FlagState{Value: ldvalue.Bool(true)}). + Build() + + t.Run("Returns error if it cant fetch flag state", func(t *testing.T) { + api.EXPECT().GetSdkKey(gomock.Any(), projKey, sourceEnvKey).Return("", errors.New("fetch flag state fails")) + _, err := model.CreateProject(ctx, projKey, sourceEnvKey, nil) + assert.NotNil(t, err) + assert.Equal(t, "fetch flag state fails", err.Error()) + }) + + t.Run("Returns error if it fails to insert the project", func(t *testing.T) { + api.EXPECT().GetSdkKey(gomock.Any(), projKey, sourceEnvKey).Return(sdkKey, nil) + sdk.EXPECT().GetAllFlagsState(gomock.Any(), gomock.Any(), sdkKey).Return(allFlags, nil) + store.EXPECT().InsertProject(gomock.Any(), gomock.Any()).Return(errors.New("insert fails")) + + _, err := model.CreateProject(ctx, projKey, sourceEnvKey, nil) + assert.NotNil(t, err) + assert.Equal(t, "insert fails", err.Error()) + }) + + t.Run("Successfully creates project", func(t *testing.T) { + api.EXPECT().GetSdkKey(gomock.Any(), projKey, sourceEnvKey).Return(sdkKey, nil) + sdk.EXPECT().GetAllFlagsState(gomock.Any(), gomock.Any(), sdkKey).Return(allFlags, nil) + store.EXPECT().InsertProject(gomock.Any(), gomock.Any()).Return(nil) + + p, err := model.CreateProject(ctx, projKey, sourceEnvKey, nil) + assert.Nil(t, err) + + expectedProj := model.Project{ + Key: projKey, + SourceEnvironmentKey: sourceEnvKey, + Context: ldcontext.NewBuilder("user").Key("dev-environment").Build(), + AllFlagsState: model.FromAllFlags(allFlags), + } + + assert.Equal(t, expectedProj.Key, p.Key) + assert.Equal(t, expectedProj.SourceEnvironmentKey, p.SourceEnvironmentKey) + assert.Equal(t, expectedProj.Context, p.Context) + assert.Equal(t, expectedProj.AllFlagsState, p.AllFlagsState) + }) +} + +func TestUpdateProject(t *testing.T) { + mockController := gomock.NewController(t) + store := mocks.NewMockStore(mockController) + ctx := model.ContextWithStore(context.Background(), store) + ctx, api, sdk := adapters_mocks.WithMockApiAndSdk(ctx, mockController) + ldCtx := ldcontext.New(t.Name()) + newSrcEnv := "newEnv" + + proj := model.Project{ + Key: "projKey", + SourceEnvironmentKey: "srcEnvKey", + Context: ldcontext.New(t.Name()), + } + + t.Run("Returns error if GetDevProject fails", func(t *testing.T) { + store.EXPECT().GetDevProject(gomock.Any(), proj.Key).Return(&model.Project{}, errors.New("GetDevProject fails")) + _, err := model.UpdateProject(ctx, proj.Key, nil, nil) + assert.NotNil(t, err) + assert.Equal(t, "GetDevProject fails", err.Error()) + }) + + t.Run("Passing in context triggers FetchFlagState, returns error if the fetch fails", func(t *testing.T) { + store.EXPECT().GetDevProject(gomock.Any(), proj.Key).Return(&proj, nil) + api.EXPECT().GetSdkKey(gomock.Any(), proj.Key, proj.SourceEnvironmentKey).Return("", errors.New("FetchFlagState fails")) + + _, err := model.UpdateProject(ctx, proj.Key, &ldCtx, nil) + assert.NotNil(t, err) + assert.Equal(t, "FetchFlagState fails", err.Error()) + }) + + t.Run("Passing in sourceEnvironmentKey triggers FetchFlagState, returns error if UpdateProject fails", func(t *testing.T) { + store.EXPECT().GetDevProject(gomock.Any(), proj.Key).Return(&proj, nil) + api.EXPECT().GetSdkKey(gomock.Any(), proj.Key, newSrcEnv).Return("sdkKey", nil) + sdk.EXPECT().GetAllFlagsState(gomock.Any(), gomock.Any(), "sdkKey").Return(flagstate.AllFlags{}, nil) + store.EXPECT().UpdateProject(gomock.Any(), gomock.Any()).Return(false, errors.New("UpdateProject fails")) + + _, err := model.UpdateProject(ctx, proj.Key, nil, &newSrcEnv) + assert.NotNil(t, err) + assert.Equal(t, "UpdateProject fails", err.Error()) + }) + + t.Run("Returns error if project was not actually updated", func(t *testing.T) { + store.EXPECT().GetDevProject(gomock.Any(), proj.Key).Return(&proj, nil) + store.EXPECT().UpdateProject(gomock.Any(), gomock.Any()).Return(false, nil) + + _, err := model.UpdateProject(ctx, proj.Key, nil, nil) + assert.NotNil(t, err) + assert.Equal(t, "Project not updated", err.Error()) + }) + + t.Run("Return successfully", func(t *testing.T) { + store.EXPECT().GetDevProject(gomock.Any(), proj.Key).Return(&proj, nil) + store.EXPECT().UpdateProject(gomock.Any(), gomock.Any()).Return(true, nil) + + project, err := model.UpdateProject(ctx, proj.Key, nil, nil) + assert.Nil(t, err) + assert.Equal(t, proj, project) + }) +} + +func TestSyncProject(t *testing.T) { + mockController := gomock.NewController(t) + store := mocks.NewMockStore(mockController) + ctx := model.ContextWithStore(context.Background(), store) + ctx, api, sdk := adapters_mocks.WithMockApiAndSdk(ctx, mockController) + + observer := mocks.NewMockObserver(mockController) + observers := model.NewObservers() + observers.RegisterObserver(observer) + ctx = model.SetObserversOnContext(ctx, observers) + + proj := model.Project{ + Key: "projKey", + SourceEnvironmentKey: "srcEnvKey", + Context: ldcontext.New(t.Name()), + } + + t.Run("Returns error if GetDevProject fails", func(t *testing.T) { + store.EXPECT().GetDevProject(gomock.Any(), proj.Key).Return(&model.Project{}, errors.New("GetDevProject fails")) + _, err := model.SyncProject(ctx, proj.Key) + assert.NotNil(t, err) + assert.Equal(t, "GetDevProject fails", err.Error()) + }) + + t.Run("returns error if FetchFlagState fails", func(t *testing.T) { + store.EXPECT().GetDevProject(gomock.Any(), proj.Key).Return(&proj, nil) + api.EXPECT().GetSdkKey(gomock.Any(), proj.Key, proj.SourceEnvironmentKey).Return("", errors.New("FetchFlagState fails")) + + _, err := model.SyncProject(ctx, proj.Key) + assert.NotNil(t, err) + assert.Equal(t, "FetchFlagState fails", err.Error()) + }) + + t.Run("returns error if UpdateProject fails", func(t *testing.T) { + store.EXPECT().GetDevProject(gomock.Any(), proj.Key).Return(&proj, nil) + api.EXPECT().GetSdkKey(gomock.Any(), proj.Key, proj.SourceEnvironmentKey).Return("sdkKey", nil) + sdk.EXPECT().GetAllFlagsState(gomock.Any(), gomock.Any(), "sdkKey").Return(flagstate.AllFlags{}, nil) + store.EXPECT().UpdateProject(gomock.Any(), gomock.Any()).Return(false, errors.New("UpdateProject fails")) + + _, err := model.SyncProject(ctx, proj.Key) + assert.NotNil(t, err) + assert.Equal(t, "UpdateProject fails", err.Error()) + }) + + t.Run("Returns error if project was not actually updated", func(t *testing.T) { + store.EXPECT().GetDevProject(gomock.Any(), proj.Key).Return(&proj, nil) + api.EXPECT().GetSdkKey(gomock.Any(), proj.Key, proj.SourceEnvironmentKey).Return("sdkKey", nil) + sdk.EXPECT().GetAllFlagsState(gomock.Any(), gomock.Any(), "sdkKey").Return(flagstate.AllFlags{}, nil) + store.EXPECT().UpdateProject(gomock.Any(), gomock.Any()).Return(false, nil) + + _, err := model.SyncProject(ctx, proj.Key) + assert.NotNil(t, err) + assert.Equal(t, "Project not updated", err.Error()) + }) + + t.Run("Return successfully", func(t *testing.T) { + store.EXPECT().GetDevProject(gomock.Any(), proj.Key).Return(&proj, nil) + api.EXPECT().GetSdkKey(gomock.Any(), proj.Key, proj.SourceEnvironmentKey).Return("sdkKey", nil) + sdk.EXPECT().GetAllFlagsState(gomock.Any(), gomock.Any(), "sdkKey").Return(flagstate.AllFlags{}, nil) + store.EXPECT().UpdateProject(gomock.Any(), gomock.Any()).Return(true, nil) + store.EXPECT().GetOverridesForProject(gomock.Any(), proj.Key).Return(model.Overrides{}, nil) + observer. + EXPECT(). + Handle(model.SyncEvent{ + ProjectKey: proj.Key, + AllFlagsState: model.FlagsState{}, + }) + + project, err := model.SyncProject(ctx, proj.Key) + assert.Nil(t, err) + assert.Equal(t, proj, project) + }) +} + +func TestGetFlagStateWithOverridesForProject(t *testing.T) { + mockController := gomock.NewController(t) + store := mocks.NewMockStore(mockController) + ctx := model.ContextWithStore(context.Background(), store) + flagKey := "flg" + proj := model.Project{ + Key: "projKey", + AllFlagsState: model.FlagsState{flagKey: model.FlagState{Value: ldvalue.Bool(false), Version: 1}}, + } + + t.Run("Returns error if store fetch fails", func(t *testing.T) { + store.EXPECT().GetOverridesForProject(gomock.Any(), proj.Key).Return(model.Overrides{}, errors.New("fetch fails")) + + _, err := proj.GetFlagStateWithOverridesForProject(ctx) + assert.NotNil(t, err) + assert.Equal(t, "unable to fetch overrides for project projKey: fetch fails", err.Error()) + }) + + t.Run("Returns flag state with overrides successfully", func(t *testing.T) { + overrides := model.Overrides{ + { + ProjectKey: proj.Key, + FlagKey: flagKey, + Value: ldvalue.Bool(true), + Active: true, + Version: 1, + }, + } + + store.EXPECT().GetOverridesForProject(gomock.Any(), proj.Key).Return(overrides, nil) + + withOverrides, err := proj.GetFlagStateWithOverridesForProject(ctx) + assert.Nil(t, err) + + assert.Len(t, withOverrides, 1) + + overriddenFlag, exists := withOverrides[flagKey] + assert.True(t, exists) + assert.True(t, overriddenFlag.Value.BoolValue()) + assert.Equal(t, 2, overriddenFlag.Version) + }) +} + +func TestFetchFlagState(t *testing.T) { + ctx := context.Background() + mockController := gomock.NewController(t) + ctx, api, sdk := adapters_mocks.WithMockApiAndSdk(ctx, mockController) + allFlags := flagstate.NewAllFlagsBuilder(). + AddFlag("boolFlag", flagstate.FlagState{Value: ldvalue.Bool(true)}). + Build() + + proj := model.Project{ + Key: "projKey", + SourceEnvironmentKey: "srcEnvKey", + Context: ldcontext.New(t.Name()), + } + + t.Run("Returns error if fails to fetch sdk key", func(t *testing.T) { + api.EXPECT().GetSdkKey(gomock.Any(), proj.Key, proj.SourceEnvironmentKey).Return("", errors.New("sdk key fail")) + + _, err := proj.FetchFlagState(ctx) + assert.NotNil(t, err) + assert.Equal(t, "sdk key fail", err.Error()) + }) + + t.Run("Returns error if fails to fetch flags state", func(t *testing.T) { + api.EXPECT().GetSdkKey(gomock.Any(), proj.Key, proj.SourceEnvironmentKey).Return("key", nil) + sdk.EXPECT().GetAllFlagsState(gomock.Any(), gomock.Any(), "key").Return(flagstate.AllFlags{}, errors.New("fetch fail")) + + _, err := proj.FetchFlagState(ctx) + assert.NotNil(t, err) + assert.Equal(t, "fetch fail", err.Error()) + }) + + t.Run("Successfully fetches flag state", func(t *testing.T) { + api.EXPECT().GetSdkKey(gomock.Any(), proj.Key, proj.SourceEnvironmentKey).Return("key", nil) + sdk.EXPECT().GetAllFlagsState(gomock.Any(), gomock.Any(), "key").Return(allFlags, nil) + + flagState, err := proj.FetchFlagState(ctx) + assert.Nil(t, err) + assert.Equal(t, model.FromAllFlags(allFlags), flagState) + }) +} diff --git a/internal/dev_server/model/store.go b/internal/dev_server/model/store.go new file mode 100644 index 00000000..2686a1bb --- /dev/null +++ b/internal/dev_server/model/store.go @@ -0,0 +1,50 @@ +package model + +import ( + "context" + "net/http" + + "github.com/gorilla/mux" + "github.com/pkg/errors" +) + +type ctxKey string + +const ctxKeyStore = ctxKey("model.Store") + +//go:generate go run go.uber.org/mock/mockgen -destination mocks/store.go -package mocks . Store + +type Store interface { + DeactivateOverride(ctx context.Context, projectKey, flagKey string) error + GetDevProjectKeys(ctx context.Context) ([]string, error) + // GetDevProject fetches the project based on the projectKey. If it doesn't exist, ErrNotFound is returned + GetDevProject(ctx context.Context, projectKey string) (*Project, error) + UpdateProject(ctx context.Context, project Project) (bool, error) + DeleteDevProject(ctx context.Context, projectKey string) (bool, error) + // InsertProject inserts the project. If it already exists, ErrAlreadyExists is returned + InsertProject(ctx context.Context, project Project) error + UpsertOverride(ctx context.Context, override Override) (Override, error) + GetOverridesForProject(ctx context.Context, projectKey string) (Overrides, error) +} + +func ContextWithStore(ctx context.Context, store Store) context.Context { + return context.WithValue(ctx, ctxKeyStore, store) +} + +func StoreFromContext(ctx context.Context) Store { + return ctx.Value(ctxKeyStore).(Store) +} + +func StoreMiddleware(store Store) mux.MiddlewareFunc { + return func(handler http.Handler) http.Handler { + return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + ctx := request.Context() + ctx = ContextWithStore(ctx, store) + request = request.WithContext(ctx) + handler.ServeHTTP(writer, request) + }) + } +} + +var ErrNotFound = errors.New("not found") +var ErrAlreadyExists = errors.New("already exists") diff --git a/internal/dev_server/sdk/constant_response.go b/internal/dev_server/sdk/constant_response.go new file mode 100644 index 00000000..1b9d00da --- /dev/null +++ b/internal/dev_server/sdk/constant_response.go @@ -0,0 +1,13 @@ +package sdk + +import "net/http" + +func ConstantResponseHandler(statusCode int, response string) http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + writer.Header().Set("Content-Type", "application/json") + writer.WriteHeader(statusCode) + if len(response) > 0 { + _, _ = writer.Write([]byte(response)) + } + } +} diff --git a/internal/dev_server/sdk/cors.go b/internal/dev_server/sdk/cors.go new file mode 100644 index 00000000..100336c1 --- /dev/null +++ b/internal/dev_server/sdk/cors.go @@ -0,0 +1,27 @@ +package sdk + +import "net/http" + +func CorsHeaders(handler http.Handler) http.Handler { + return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + writer.Header().Set("Access-Control-Allow-Origin", "*") + writer.Header().Set("Access-Control-Allow-Methods", "GET,OPTIONS") + writer.Header().Set("Access-Control-Allow-Credentials", "true") + writer.Header().Set("Access-Control-Allow-Headers", "Cache-Control,Content-Type,Content-Length,Accept-Encoding,X-LaunchDarkly-User-Agent,X-LaunchDarkly-Payload-ID,X-LaunchDarkly-Wrapper,X-LaunchDarkly-Event-Schema,X-LaunchDarkly-Tags") + writer.Header().Set("Access-Control-Expose-Headers:", "Date") + writer.Header().Set("Access-Control-Max-Age", "300") + handler.ServeHTTP(writer, request) + }) +} + +func EventsCorsHeaders(handler http.Handler) http.Handler { + return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + writer.Header().Set("Access-Control-Allow-Origin", "*") + writer.Header().Set("Access-Control-Allow-Methods", "POST,OPTIONS") + writer.Header().Set("Access-Control-Allow-Credentials", "true") + writer.Header().Set("Access-Control-Allow-Headers", "Accept,Content-Type,Content-Length,Accept-Encoding,X-LaunchDarkly-Event-Schema,X-LaunchDarkly-User-Agent,X-LaunchDarkly-Payload-ID,X-LaunchDarkly-Wrapper,X-LaunchDarkly-Tags") + writer.Header().Set("Access-Control-Expose-Headers:", "Date") + writer.Header().Set("Access-Control-Max-Age", "300") + handler.ServeHTTP(writer, request) + }) +} diff --git a/internal/dev_server/sdk/docs.go b/internal/dev_server/sdk/docs.go new file mode 100644 index 00000000..915adc6c --- /dev/null +++ b/internal/dev_server/sdk/docs.go @@ -0,0 +1,34 @@ +// Package sdk provides API handlers for [the endpoints provided by ld-relay](https://github.com/launchdarkly/ld-relay/blob/v8/docs/endpoints.md). +// +// Not all routes are supported because some routes are for exclusively for SDKs that have reached end of life. +// +// ✅ /all GET stream. SSE stream for all data +// ✅ /bulk POST events. Receives analytics events from SDKs +// ✅ /diagnostic POST events. Receives diagnostic data from SDKs +// ❌ /flags GET stream. SSE stream for flag data (older SDKs) +// ✅ /sdk/flags GET sdk. Polling endpoint for PHP SDK +// ✅ /sdk/flags/{flagKey} GET sdk. Polling endpoint for PHP SDK +// ✅ /sdk/segments/{segmentKey} GET sdk. Polling endpoint for PHP SDK (Segments are not used in dev server, so this always 404s) +// ✅ /meval/{contextBase64} GET clientstream. SSE stream of "ping" and other events +// ✅ /meval REPORT clientstream. Same as above, but request body is the evaluation context JSON object (not in base64) +// ✅ /mobile POST events. For receiving events from mobile SDKs +// ✅ /mobile/events POST events. Same as above +// ✅ /mobile/events/bulk POST events. Same as above +// ✅ /mobile/events/diagnostic POST events. Same as above +// ❌ /mping GET clientstream. SSE stream for older SDKs that issues "ping" events when flags have changed +// ✅ /msdk/evalx/contexts/{contextBase64} GET clientsdk. Polling endpoint, returns flag evaluation results for an evaluation context +// ✅ /msdk/evalx/context REPORT clientsdk. Same as above but request body is the evaluation context JSON object (not in base64) +// ✅ /msdk/evalx/users/{contextBase64} GET clientsdk. Alternate name for /msdk/evalx/contexts/{contextBase64} used by older SDKs +// ✅ /msdk/evalx/user REPORT clientsdk. Alternate name for /msdk/evalx/context used by older SDKs +// ❌ /a/{envId}.gif?d=*events* GET events. Alternative analytics event mechanism used if browser does not allow CORS +// ✅ /eval/{envId}/{contextBase64} GET clientstream. SSE stream of "ping" and other events for JS and other client-side SDK listeners +// ✅ /eval/{envId} REPORT clientstream. Same as above but request body is the evaluation context JSON object (not in base64) +// ✅ /events/bulk/{envId} POST events. Receives analytics events from SDKs +// ✅ /events/diagnostic/{envId} POST events. Receives diagnostic data from SDKs +// ❌ /ping/{envId} GET clientstream. SSE stream for older SDKs that issues "ping" events when flags have changed +// ✅ /sdk/evalx/{envId}/contexts/{contextBase64} GET clientsdk. Polling endpoint, returns flag evaluation results and additional metadata +// ✅ /sdk/evalx/{envId}/contexts REPORT clientsdk. Same as above but request body is the evaluation context JSON object (not in base64) +// ✅ /sdk/evalx/{envId}/users/{contextBase64} GET clientsdk. Alternate name for /sdk/evalx/{envId}/contexts/{contextBase64} used by older SDKs +// ✅ /sdk/evalx/{envId}/users REPORT clientsdk. Alternate name for /sdk/evalx/{envId}/contexts used by older SDKs +// ✅ /sdk/goals/{envId} GET clientsdk. Provides goals data used by JS SDK +package sdk diff --git a/internal/dev_server/sdk/get_client_flags.go b/internal/dev_server/sdk/get_client_flags.go new file mode 100644 index 00000000..acbc14a6 --- /dev/null +++ b/internal/dev_server/sdk/get_client_flags.go @@ -0,0 +1,28 @@ +package sdk + +import ( + "encoding/json" + "net/http" + + "github.com/pkg/errors" +) + +func GetClientFlags(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + allFlags, err := GetAllFlagsFromContext(ctx) + if err != nil { + WriteError(ctx, w, errors.Wrap(err, "failed to get flag state")) + return + } + jsonBody, err := json.Marshal(allFlags) + if err != nil { + WriteError(ctx, w, errors.Wrap(err, "failed to marshal flag state")) + return + } + w.Header().Set("Content-Type", "application/json") + _, err = w.Write(jsonBody) + if err != nil { + WriteError(ctx, w, errors.Wrap(err, "unable to write response")) + return + } +} diff --git a/internal/dev_server/sdk/get_server_flags.go b/internal/dev_server/sdk/get_server_flags.go new file mode 100644 index 00000000..87aa056c --- /dev/null +++ b/internal/dev_server/sdk/get_server_flags.go @@ -0,0 +1,38 @@ +package sdk + +import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + "github.com/pkg/errors" +) + +func GetServerFlags(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + allFlags, err := GetAllFlagsFromContext(ctx) + if err != nil { + WriteError(ctx, w, errors.Wrap(err, "failed to get flag state")) + return + } + var body interface{} + if flagKey, ok := mux.Vars(r)["flagKey"]; ok { + body, ok = ServerFlagsFromFlagsState(allFlags)[flagKey] + if !ok { + http.Error(w, "flag not found", http.StatusNotFound) + } + } else { + body = ServerAllPayloadFromFlagsState(allFlags) + } + jsonBody, err := json.Marshal(body) + if err != nil { + WriteError(ctx, w, errors.Wrap(err, "failed to marshal flag state")) + return + } + w.Header().Set("Content-Type", "application/json") + _, err = w.Write(jsonBody) + if err != nil { + WriteError(ctx, w, errors.Wrap(err, "unable to write response")) + return + } +} diff --git a/internal/dev_server/sdk/go_sdk_test.go b/internal/dev_server/sdk/go_sdk_test.go new file mode 100644 index 00000000..b6a32370 --- /dev/null +++ b/internal/dev_server/sdk/go_sdk_test.go @@ -0,0 +1,161 @@ +package sdk + +import ( + "context" + "fmt" + "net/http/httptest" + "os" + "testing" + "time" + + "github.com/gorilla/mux" + "github.com/launchdarkly/go-sdk-common/v3/ldcontext" + "github.com/launchdarkly/go-sdk-common/v3/ldvalue" + ldclient "github.com/launchdarkly/go-server-sdk/v7" + "github.com/launchdarkly/go-server-sdk/v7/interfaces" + "github.com/launchdarkly/go-server-sdk/v7/interfaces/flagstate" + "github.com/launchdarkly/ldcli/internal/dev_server/adapters/mocks" + "github.com/launchdarkly/ldcli/internal/dev_server/db" + "github.com/launchdarkly/ldcli/internal/dev_server/model" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +// TestSdkRoutesViaGoSDK is an integration test. It hooks up a real go SDK to our SDK routes and makes changes to the +// model using the model methods directly. It also uses a real sqlite store. Goal of these tests are to ensure that the +// happy path works end to end (almost -- API handlers are omitted, but they are thin wrappers over the model). +func TestSDKRoutesViaGoSDK(t *testing.T) { + const projectKey = "test-project" + const environmentKey = "test-environment" + const testSdkKey = "1234" + + // Wire up model dependencies to context + ctx := context.Background() + store, err := db.NewSqlite(ctx, "test.db") + require.NoError(t, err) + defer func() { + require.NoError(t, os.Remove("test.db")) + }() + ctx = model.ContextWithStore(ctx, store) + observers := model.NewObservers() + ctx = model.SetObserversOnContext(ctx, observers) + // Mock the external LD APIs + mockController := gomock.NewController(t) + ctx, api, sdk := mocks.WithMockApiAndSdk(ctx, mockController) + + api.EXPECT().GetSdkKey(gomock.Any(), projectKey, environmentKey).Return(testSdkKey, nil).AnyTimes() + + // Wire up sdk routes in test server + router := mux.NewRouter() + router.Use(model.StoreMiddleware(store)) + router.Use(model.ObserversMiddleware(observers)) + BindRoutes(router) + require.NoError(t, err) + testServer := httptest.NewServer(router) + + // Initialize project with all kinds of flags + allFlags := flagstate.NewAllFlagsBuilder(). + AddFlag("boolFlag", flagstate.FlagState{Value: ldvalue.Bool(true)}). + AddFlag("stringFlag", flagstate.FlagState{Value: ldvalue.String("cool")}). + AddFlag("intFlag", flagstate.FlagState{Value: ldvalue.Int(123)}). + AddFlag("doubleFlag", flagstate.FlagState{Value: ldvalue.Float64(99.99)}). + AddFlag("jsonFlag", flagstate.FlagState{Value: ldvalue.CopyArbitraryValue(map[string]any{"cat": "hat"})}). + Build() + + sdk.EXPECT().GetAllFlagsState(gomock.Any(), gomock.Any(), testSdkKey).Return(allFlags, nil) + _, err = model.CreateProject(ctx, projectKey, environmentKey, nil) + require.NoError(t, err) + + // Configure go SDK to use test server + ldConfig := ldclient.Config{} + ldConfig.ServiceEndpoints.Streaming = testServer.URL + ldConfig.ServiceEndpoints.Events = testServer.URL + ldConfig.ServiceEndpoints.Polling = testServer.URL + ld, err := ldclient.MakeCustomClient(projectKey, ldConfig, time.Second) + require.NoError(t, err) + + ldContext := ldcontext.New(t.Name()) + + t.Run("bool flag is true in fresh environment", func(t *testing.T) { + val, err := ld.BoolVariation("boolFlag", ldContext, false) + require.NoError(t, err) + assert.True(t, val, "boolean variation is expected value") + }) + + t.Run("string flag is cool in fresh environment", func(t *testing.T) { + val, err := ld.StringVariation("stringFlag", ldContext, "bad") + require.NoError(t, err) + assert.Equal(t, "cool", val) + }) + + t.Run("int flag is 123 in fresh environment", func(t *testing.T) { + val, err := ld.IntVariation("intFlag", ldContext, 0) + require.NoError(t, err) + assert.Equal(t, 123, val) + }) + + t.Run("doubleFlag is 4 9s in fresh environment", func(t *testing.T) { + val, err := ld.Float64Variation("doubleFlag", ldContext, 0) + require.NoError(t, err) + assert.Equal(t, 99.99, val) + }) + + t.Run("jsonFlag is cat :hat in fresh environment", func(t *testing.T) { + val, err := ld.JSONVariation("jsonFlag", ldContext, ldvalue.Null()) + require.NoError(t, err) + assert.Equal(t, map[string]any{"cat": "hat"}, val.AsArbitraryValue()) + }) + + // Mock scenario: we re-sync and the SDK returns new values and higher version numbers + updatedFlags := flagstate.NewAllFlagsBuilder(). + AddFlag("boolFlag", flagstate.FlagState{Value: ldvalue.Bool(false), Version: 2}). + AddFlag("stringFlag", flagstate.FlagState{Value: ldvalue.String("pool"), Version: 2}). + AddFlag("intFlag", flagstate.FlagState{Value: ldvalue.Int(789), Version: 2}). + AddFlag("doubleFlag", flagstate.FlagState{Value: ldvalue.Float64(101.01), Version: 2}). + AddFlag("jsonFlag", flagstate.FlagState{Value: ldvalue.CopyArbitraryValue(map[string]any{"cat": "bababooey"}), Version: 2}). + Build() + valuesMap := updatedFlags.ToValuesMap() + + sdk.EXPECT().GetAllFlagsState(gomock.Any(), gomock.Any(), testSdkKey).Return(updatedFlags, nil) + + // This test is testing the "put" payload in a roundabout way by verifying each of the flags are in there. + t.Run("Sync sends full flag payload for project", func(t *testing.T) { + trackers := make(map[string]<-chan interfaces.FlagValueChangeEvent, len(valuesMap)) + + for flagKey := range valuesMap { + flagUpdateChan := ld.GetFlagTracker().AddFlagValueChangeListener(flagKey, ldContext, ldvalue.String("uh-oh")) + defer ld.GetFlagTracker().RemoveFlagValueChangeListener(flagUpdateChan) + trackers[flagKey] = flagUpdateChan + } + + _, err := model.SyncProject(ctx, projectKey) + require.NoError(t, err) + + for flagKey, value := range valuesMap { + updateTracker, ok := trackers[flagKey] + require.True(t, ok) + + update := <-updateTracker + assert.Equal(t, value.AsArbitraryValue(), update.NewValue.AsArbitraryValue()) + } + }) + + updates := map[string]ldvalue.Value{ + "boolFlag": ldvalue.Bool(true), + "stringFlag": ldvalue.String("drool"), + "intFlag": ldvalue.Int(456), + "doubleFlag": ldvalue.Float64(88.88), + "jsonFlag": ldvalue.CopyArbitraryValue(map[string]any{"tortoise": "shell"}), + } + for flagKey, value := range updates { + t.Run(fmt.Sprintf("%s is %v after override", flagKey, value), func(t *testing.T) { + flagUpdateChan := ld.GetFlagTracker().AddFlagValueChangeListener(flagKey, ldContext, ldvalue.String("uh-oh")) + defer ld.GetFlagTracker().RemoveFlagValueChangeListener(flagUpdateChan) + _, err := model.UpsertOverride(ctx, projectKey, flagKey, value) + require.NoError(t, err) + flagUpdate := <-flagUpdateChan + assert.Equal(t, value.AsArbitraryValue(), flagUpdate.NewValue.AsArbitraryValue()) + }) + } +} diff --git a/internal/dev_server/sdk/project_key_middleware.go b/internal/dev_server/sdk/project_key_middleware.go new file mode 100644 index 00000000..8fdb5675 --- /dev/null +++ b/internal/dev_server/sdk/project_key_middleware.go @@ -0,0 +1,49 @@ +package sdk + +import ( + "context" + "net/http" + + "github.com/gorilla/mux" +) + +type ctxKey string + +const projectKeyContextKey = ctxKey("projectKey") + +func SetProjectKeyOnContext(ctx context.Context, projectKey string) context.Context { + return context.WithValue(ctx, projectKeyContextKey, projectKey) +} +func GetProjectKeyFromContext(ctx context.Context) string { + return ctx.Value(projectKeyContextKey).(string) +} + +func GetProjectKeyFromEnvIdParameter(pathParameter string) func(handler http.Handler) http.Handler { + return func(handler http.Handler) http.Handler { + return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + projectKey, ok := mux.Vars(request)[pathParameter] + if !ok { + http.Error(writer, "project key not on path", http.StatusNotFound) + return + } + ctx := request.Context() + ctx = SetProjectKeyOnContext(ctx, projectKey) + request = request.WithContext(ctx) + handler.ServeHTTP(writer, request) + }) + } +} + +func GetProjectKeyFromAuthorizationHeader(handler http.Handler) http.Handler { + return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + ctx := request.Context() + projectKey := request.Header.Get("Authorization") + if projectKey == "" { + http.Error(writer, "project key not on Authorization header", http.StatusUnauthorized) + return + } + ctx = SetProjectKeyOnContext(ctx, projectKey) + request = request.WithContext(ctx) + handler.ServeHTTP(writer, request) + }) +} diff --git a/internal/dev_server/sdk/routes.go b/internal/dev_server/sdk/routes.go new file mode 100644 index 00000000..29bc1bdb --- /dev/null +++ b/internal/dev_server/sdk/routes.go @@ -0,0 +1,50 @@ +package sdk + +import ( + "net/http" + + "github.com/gorilla/mux" +) + +var DevNull = ConstantResponseHandler(http.StatusAccepted, "") + +func BindRoutes(router *mux.Router) { + // events + router.HandleFunc("/bulk", DevNull) + router.HandleFunc("/diagnostic", DevNull) + router.Handle("/events/bulk/{envId}", EventsCorsHeaders(DevNull)) + router.Handle("/events/diagnostic/{envId}", EventsCorsHeaders(DevNull)) + router.HandleFunc("/mobile", DevNull) + router.HandleFunc("/mobile/events", DevNull) + router.HandleFunc("/mobile/events/bulk", DevNull) + router.HandleFunc("/mobile/events/diagnostic", DevNull) + + router.Handle("/all", GetProjectKeyFromAuthorizationHeader(http.HandlerFunc(StreamServerAllPayload))) + + router.PathPrefix("/sdk/flags"). + Methods(http.MethodGet). + Handler(GetProjectKeyFromAuthorizationHeader(http.HandlerFunc(GetServerFlags))) + + router.PathPrefix("/meval").Handler(GetProjectKeyFromAuthorizationHeader(http.HandlerFunc(StreamClientFlags))) + router.PathPrefix("/msdk/evalx").Handler(GetProjectKeyFromAuthorizationHeader(http.HandlerFunc(GetClientFlags))) + + evalRouter := router.PathPrefix("/eval").Subrouter() + evalRouter.Use(CorsHeaders) + evalRouter.Methods(http.MethodOptions).HandlerFunc(ConstantResponseHandler(http.StatusOK, "")) + evalRouter.Use(GetProjectKeyFromEnvIdParameter("envId")) + evalRouter.PathPrefix("/{envId}"). + Methods(http.MethodGet, "REPORT"). + HandlerFunc(StreamClientFlags) + + goalsRouter := router.Path("/sdk/goals/{envId}").Subrouter() + goalsRouter.Use(CorsHeaders) + goalsRouter.Use(GetProjectKeyFromEnvIdParameter("envId")) + goalsRouter.Methods(http.MethodOptions).HandlerFunc(ConstantResponseHandler(http.StatusOK, "")) + goalsRouter.Methods(http.MethodGet).HandlerFunc(ConstantResponseHandler(http.StatusOK, "[]")) + + evalXRouter := router.PathPrefix("/sdk/evalx/{envId}").Subrouter() + evalXRouter.Use(CorsHeaders) + evalXRouter.Use(GetProjectKeyFromEnvIdParameter("envId")) + evalXRouter.Methods(http.MethodOptions).HandlerFunc(ConstantResponseHandler(http.StatusOK, "")) + evalXRouter.Methods(http.MethodGet, "REPORT").HandlerFunc(GetClientFlags) +} diff --git a/internal/dev_server/sdk/server_flags.go b/internal/dev_server/sdk/server_flags.go new file mode 100644 index 00000000..e359acde --- /dev/null +++ b/internal/dev_server/sdk/server_flags.go @@ -0,0 +1,80 @@ +package sdk + +import ( + "github.com/launchdarkly/go-sdk-common/v3/ldvalue" + "github.com/launchdarkly/ldcli/internal/dev_server/model" +) + +type fallthroughRule struct { + Variation int `json:"variation"` +} + +type clientSideAvailability struct { + UsingMobileKey bool `json:"usingMobileKey"` + UsingEnvironmentId bool `json:"usingEnvironmentId"` +} + +type ServerFlag struct { + Key string `json:"key"` + On bool `json:"on"` + Prerequisites []string `json:"prerequisites"` // this isn't the real model for this, but this will always be empty for us + Targets []string `json:"targets"` // this isn't the real model for this, but this will always be empty for us + Rules []string `json:"rules"` // this isn't the real model for this, but this will always be empty for us + Fallthrough fallthroughRule `json:"fallthrough"` + OffVariation int `json:"offVariation"` + Variations []ldvalue.Value `json:"variations"` + ClientSideAvailability clientSideAvailability `json:"clientSideAvailability"` + ClientSide bool `json:"clientSide"` + Salt string `json:"salt"` + TrackEvents bool `json:"trackEvents"` + TrackEventsFallthrough bool `json:"trackEventsFallthrough"` + DebugEventsUntilDate int `json:"debugEventsUntilDate"` + Version int `json:"version"` + Deleted bool `json:"deleted"` +} + +type ServerFlags map[string]ServerFlag + +type data struct { + Flags ServerFlags `json:"flags"` +} +type ServerAllPayload struct { + Path string `json:"path"` + Data data `json:"data"` +} + +func ServerAllPayloadFromFlagsState(state model.FlagsState) ServerAllPayload { + return ServerAllPayload{ + Path: "", + Data: data{ServerFlagsFromFlagsState(state)}, + } +} + +func ServerFlagsFromFlagsState(flagsState model.FlagsState) ServerFlags { + serverFlags := make(map[string]ServerFlag, len(flagsState)) + for flagKey, state := range flagsState { + serverFlags[flagKey] = serverFlagFromFlagState(flagKey, state) + } + return serverFlags +} + +func serverFlagFromFlagState(key string, state model.FlagState) ServerFlag { + return ServerFlag{ + Key: key, + On: true, + Prerequisites: make([]string, 0), + Targets: make([]string, 0), + Rules: make([]string, 0), + Fallthrough: fallthroughRule{Variation: 0}, + OffVariation: 0, + Variations: []ldvalue.Value{state.Value}, + ClientSideAvailability: clientSideAvailability{true, true}, + ClientSide: true, + Salt: "", + TrackEvents: false, + TrackEventsFallthrough: false, + DebugEventsUntilDate: 0, + Version: state.Version, + Deleted: false, + } +} diff --git a/internal/dev_server/sdk/store_facade.go b/internal/dev_server/sdk/store_facade.go new file mode 100644 index 00000000..08c629ef --- /dev/null +++ b/internal/dev_server/sdk/store_facade.go @@ -0,0 +1,42 @@ +package sdk + +import ( + "context" + "fmt" + "log" + "net/http" + + "github.com/launchdarkly/ldcli/internal/dev_server/model" + "github.com/pkg/errors" +) + +// WriteError writes out a given error if it's known or panics if it isn't. +// Two assumptions it's making +// - a panic handling middleware is in use +// - This is in the context of flag delivery which has pretty consistent semantics for what's an error across handlers. +func WriteError(ctx context.Context, w http.ResponseWriter, err error) { + switch { + case errors.Is(err, model.ErrNotFound): + projectKey := GetProjectKeyFromContext(ctx) + message := fmt.Sprintf("project, %s, not found", projectKey) + log.Println(message) + log.Printf("To add your project to the dev server, call `ldcli dev-server add-project --project %s --source {SOURCE_ENV_KEY}", projectKey) + http.Error(w, message, http.StatusNotFound) + case err != nil: + panic(err) + } +} + +func GetAllFlagsFromContext(ctx context.Context) (model.FlagsState, error) { + store := model.StoreFromContext(ctx) + projectKey := GetProjectKeyFromContext(ctx) + project, err := store.GetDevProject(ctx, projectKey) + if err != nil { + return model.FlagsState{}, errors.Wrap(err, "unable to get dev project") + } + allFlags, err := project.GetFlagStateWithOverridesForProject(ctx) + if err != nil { + return model.FlagsState{}, errors.Wrap(err, "unable to get flags for project") + } + return allFlags, nil +} diff --git a/internal/dev_server/sdk/stream_client_flags.go b/internal/dev_server/sdk/stream_client_flags.go new file mode 100644 index 00000000..4eac5e91 --- /dev/null +++ b/internal/dev_server/sdk/stream_client_flags.go @@ -0,0 +1,87 @@ +package sdk + +import ( + "encoding/json" + "log" + "net/http" + + "github.com/launchdarkly/go-sdk-common/v3/ldvalue" + "github.com/launchdarkly/ldcli/internal/dev_server/model" + "github.com/pkg/errors" +) + +func StreamClientFlags(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + allFlags, err := GetAllFlagsFromContext(ctx) + if err != nil { + WriteError(ctx, w, errors.Wrap(err, "failed to get flag state")) + return + } + jsonBody, err := json.Marshal(allFlags) + if err != nil { + WriteError(ctx, w, errors.Wrap(err, "failed to marshal flag state")) + return + } + updateChan, doneChan := OpenStream( + w, + r.Context().Done(), + Message{Event: TYPE_PUT, Data: jsonBody}, + ) + defer close(updateChan) + projectKey := GetProjectKeyFromContext(ctx) + observer := clientFlagsObserver{updateChan, projectKey} + observers := model.GetObserversFromContext(ctx) + observerId := observers.RegisterObserver(observer) + defer func() { + ok := observers.DeregisterObserver(observerId) + if !ok { + log.Printf("unable to remove observer") + } + }() + err = <-doneChan + if err != nil { + WriteError(ctx, w, errors.Wrap(err, "stream failure")) + return + } +} + +type clientFlagsObserver struct { + updateChan chan<- Message + projectKey string +} + +func (c clientFlagsObserver) Handle(event interface{}) { + log.Printf("clientFlagsObserver: handling flag state event: %v", event) + switch event := event.(type) { + case model.UpsertOverrideEvent: + err := SendMessage(c.updateChan, TYPE_PATCH, clientFlag{ + Key: event.FlagKey, + Version: event.FlagState.Version, + Value: event.FlagState.Value, + }) + if err != nil { + panic(errors.Wrap(err, "failed to marshal flag state in observer")) + } + case model.SyncEvent: + clientFlags := clientFlags{} + for flagKey, flagState := range event.AllFlagsState { + clientFlags[flagKey] = clientFlag{ + Version: flagState.Version, + Value: flagState.Value, + } + } + + err := SendMessage(c.updateChan, TYPE_PUT, clientFlags) + if err != nil { + panic(errors.Wrap(err, "failed to marshal flag state in observer")) + } + } +} + +type clientFlag struct { + Key string `json:"key,omitempty"` + Version int `json:"version"` + Value ldvalue.Value `json:"value"` +} + +type clientFlags map[string]clientFlag diff --git a/internal/dev_server/sdk/stream_server_flags.go b/internal/dev_server/sdk/stream_server_flags.go new file mode 100644 index 00000000..1d3c0a4f --- /dev/null +++ b/internal/dev_server/sdk/stream_server_flags.go @@ -0,0 +1,84 @@ +package sdk + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + + "github.com/launchdarkly/ldcli/internal/dev_server/model" + "github.com/pkg/errors" +) + +func StreamServerAllPayload(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + projectKey := GetProjectKeyFromContext(ctx) + allFlags, err := GetAllFlagsFromContext(ctx) + if err != nil { + WriteError(ctx, w, errors.Wrap(err, "failed to get flag state")) + return + } + serverFlags := ServerAllPayloadFromFlagsState(allFlags) + jsonBody, err := json.Marshal(serverFlags) + if err != nil { + WriteError(ctx, w, errors.Wrap(err, "failed to marshal flag state")) + return + } + updateChan, doneChan := OpenStream( + w, + r.Context().Done(), + Message{Event: TYPE_PUT, Data: jsonBody}, + ) + defer close(updateChan) + observer := serverFlagsObserver{updateChan, projectKey} + observers := model.GetObserversFromContext(ctx) + observerId := observers.RegisterObserver(observer) + defer func() { + ok := observers.DeregisterObserver(observerId) + if !ok { + log.Printf("unable to remove observer") + } + }() + err = <-doneChan + if err != nil { + WriteError(ctx, w, errors.Wrap(err, "stream failure")) + return + } +} + +type serverFlagsObserver struct { + updateChan chan<- Message + projectKey string +} + +func (c serverFlagsObserver) Handle(event interface{}) { + log.Printf("serverFlagsObserver: handling flag state event: %v", event) + switch event := event.(type) { + case model.UpsertOverrideEvent: + if event.ProjectKey != c.projectKey { + return + } + + err := SendMessage(c.updateChan, TYPE_PATCH, serverSidePatchData{ + Path: fmt.Sprintf("/flags/%s", event.FlagKey), + Data: serverFlagFromFlagState(event.FlagKey, event.FlagState), + }) + if err != nil { + panic(errors.Wrap(err, "failed to marshal flag state in observer")) + } + case model.SyncEvent: + if event.ProjectKey != c.projectKey { + return + } + + err := SendMessage(c.updateChan, TYPE_PUT, ServerAllPayloadFromFlagsState(event.AllFlagsState)) + if err != nil { + panic(errors.Wrap(err, "failed to marshal flag state in observer")) + } + } +} + +type serverSidePatchData struct { + Path string `json:"path"` + Data ServerFlag `json:"data"` +} diff --git a/internal/dev_server/sdk/streaming.go b/internal/dev_server/sdk/streaming.go new file mode 100644 index 00000000..04f650c8 --- /dev/null +++ b/internal/dev_server/sdk/streaming.go @@ -0,0 +1,95 @@ +package sdk + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +type MessageType string + +const ( + TYPE_PUT MessageType = "put" + TYPE_PATCH MessageType = "patch" +) + +type Message struct { + Event MessageType + Data []byte +} + +func (m Message) ToPayload() []byte { + payload := []byte(fmt.Sprintf("event:%s\ndata:", m.Event)) + payload = append(payload, m.Data...) + payload = append(payload, "\n\n"...) + return payload +} + +// OpenStream sends data to a response using the initial payload and subsequently via the returned write only channel +func OpenStream(w http.ResponseWriter, done <-chan struct{}, initialMessage Message) (chan<- Message, <-chan error) { + errChan := make(chan error) + updateChan := make(chan Message, 10) + go func() { + var err error + defer func() { + errChan <- err + close(errChan) + }() + err = func() error { + flusher, ok := w.(http.Flusher) + if !ok { + return errors.New("expected http.ResponseWriter to be an http.Flusher") + } + + w.Header().Set("Content-Type", "text/event-stream") + _, err = w.Write(initialMessage.ToPayload()) + if err != nil { + return errors.Wrap(err, "unable to write response") + } + flusher.Flush() + ticker := time.NewTicker(time.Minute) + loop: + for { + select { + case <-ticker.C: + _, err = w.Write([]byte(":\n\n")) + if err != nil { + return errors.Wrap(err, "unable to write response") + } + flusher.Flush() + case msg := <-updateChan: + _, err = w.Write(msg.ToPayload()) + if err != nil { + return errors.Wrap(err, "unable to write response") + } + flusher.Flush() + case <-done: + break loop + } + } + return nil + }() + }() + return updateChan, errChan +} + +func SendMessage( + updateChan chan<- Message, + msgType MessageType, + data interface{}, +) error { + payload, err := json.Marshal(data) + if err != nil { + return err + } + + updateChan <- Message{ + Event: msgType, + Data: payload, + } + + return nil +} diff --git a/internal/dev_server/ui/.gitignore b/internal/dev_server/ui/.gitignore new file mode 100644 index 00000000..af18ca95 --- /dev/null +++ b/internal/dev_server/ui/.gitignore @@ -0,0 +1 @@ +!dist/ diff --git a/internal/dev_server/ui/.npmrc b/internal/dev_server/ui/.npmrc new file mode 100644 index 00000000..cffe8cde --- /dev/null +++ b/internal/dev_server/ui/.npmrc @@ -0,0 +1 @@ +save-exact=true diff --git a/internal/dev_server/ui/.prettierrc b/internal/dev_server/ui/.prettierrc new file mode 100644 index 00000000..dc75c8a8 --- /dev/null +++ b/internal/dev_server/ui/.prettierrc @@ -0,0 +1,6 @@ +{ + "trailingComma": "all", + "tabWidth": 2, + "semi": true, + "singleQuote": true +} diff --git a/internal/dev_server/ui/README.md b/internal/dev_server/ui/README.md new file mode 100644 index 00000000..fd7a33ea --- /dev/null +++ b/internal/dev_server/ui/README.md @@ -0,0 +1,19 @@ +# Dev Server UI + +The dev server UI is a very small react app that is used to view the flags & flag variations that the dev server will serve. + +**NOTE: be sure to run all commands in the `internal/ui` directory** + +## Prerequisites + +This uses `vite` for builds & `npm` to manage dependencies. `npm i` to install dependencies. + +## Build + +Because the UI is modified far less frequently than the rest of the CLI, we build the UI locally so that we don't need the JS toolchain in CI. + +To update the UI that is served by the dev sever, run `npm run build` and rebuild the go server. Be sure to check in the update in `dist` so that your changes are incorporated into the go build + +## Run + +When developing, you can use `npm run dev` to spin up a development version of the UI. Be sure to also run the dev server to provide the APIs. diff --git a/internal/dev_server/ui/asset_handler.go b/internal/dev_server/ui/asset_handler.go new file mode 100644 index 00000000..e3248b96 --- /dev/null +++ b/internal/dev_server/ui/asset_handler.go @@ -0,0 +1,19 @@ +package ui + +import ( + "embed" + "io/fs" + "log" + "net/http" +) + +//go:embed dist/index.html +var content embed.FS + +var AssetHandler = func() http.Handler { + dist, err := fs.Sub(content, "dist") + if err != nil { + log.Fatalf("unable to open dist: %+v", err) + } + return http.FileServerFS(dist) +}() diff --git a/internal/dev_server/ui/dist/index.html b/internal/dev_server/ui/dist/index.html new file mode 100644 index 00000000..f97092cf --- /dev/null +++ b/internal/dev_server/ui/dist/index.html @@ -0,0 +1,65 @@ + + + + + + + LaunchDevly + + + + +
+ + diff --git a/internal/dev_server/ui/index.html b/internal/dev_server/ui/index.html new file mode 100644 index 00000000..00caf5ca --- /dev/null +++ b/internal/dev_server/ui/index.html @@ -0,0 +1,13 @@ + + + + + + + LaunchDevly + + +
+ + + diff --git a/internal/dev_server/ui/package-lock.json b/internal/dev_server/ui/package-lock.json new file mode 100644 index 00000000..de1937d9 --- /dev/null +++ b/internal/dev_server/ui/package-lock.json @@ -0,0 +1,7026 @@ +{ + "name": "ldcli-dev-server-ui", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ldcli-dev-server-ui", + "version": "0.0.0", + "dependencies": { + "@launchpad-ui/components": "0.2.12", + "@launchpad-ui/core": "0.49.22", + "@launchpad-ui/icons": "0.18.4", + "@launchpad-ui/tokens": "0.9.12", + "launchdarkly-js-client-sdk": "3.4.0", + "react": "18.3.1", + "react-dom": "18.3.1" + }, + "devDependencies": { + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", + "@typescript-eslint/eslint-plugin": "7.13.1", + "@typescript-eslint/parser": "7.13.1", + "@vitejs/plugin-react": "4.3.1", + "eslint": "8.57.0", + "eslint-plugin-react-hooks": "4.6.2", + "eslint-plugin-react-refresh": "0.4.7", + "prettier": "3.3.2", + "typescript": "5.2.2", + "vite": "5.3.1", + "vite-plugin-singlefile": "2.0.2", + "vite-plugin-svgr": "^4.2.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", + "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", + "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helpers": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", + "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", + "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", + "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", + "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", + "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==", + "peer": true + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", + "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "dependencies": { + "@floating-ui/utils": "^0.2.1" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.1.tgz", + "integrity": "sha512-iA8qE43/H5iGozC3W0YSnVSW42Vh522yyM1gj+BqRwVsTNOyr231PsXDaV04yT39PsO0QL2QpbI/M0ZaLUQgRQ==", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.1" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.3.tgz", + "integrity": "sha512-XGndio0l5/Gvd6CLIABvsav9HHezgDFFhDfHk1bvLfr9ni8dojqLSvBbotJEjmIwNHL7vK4QzBJTdBRoB+c1ww==" + }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz", + "integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==", + "dependencies": { + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz", + "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.7.8", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.8.tgz", + "integrity": "sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/icu-skeleton-parser": "1.8.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.2.tgz", + "integrity": "sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@internationalized/date": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.5.4.tgz", + "integrity": "sha512-qoVJVro+O0rBaw+8HPjUB1iH8Ihf8oziEnqMnvhJUSuVIrHOuZ6eNLHNvzXJKUvAtaDiqMnRlg8Z2mgh09BlUw==", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@internationalized/message/-/message-3.1.4.tgz", + "integrity": "sha512-Dygi9hH1s7V9nha07pggCkvmRfDd3q2lWnMGvrJyrOwYMe1yj4D2T9BoH9I6MGR7xz0biQrtLPsqUkqXzIrBOw==", + "dependencies": { + "@swc/helpers": "^0.5.0", + "intl-messageformat": "^10.1.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.5.3.tgz", + "integrity": "sha512-rd1wA3ebzlp0Mehj5YTuTI50AQEx80gWFyHcQu+u91/5NgdwBecO8BH6ipPfE+lmQ9d63vpB3H9SHoIUiupllw==", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/string": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.3.tgz", + "integrity": "sha512-9kpfLoA8HegiWTeCbR2livhdVeKobCnVv8tlJ6M2jF+4tcMqDo94ezwlnrUANBWPgd8U7OXIHCk2Ov2qhk4KXw==", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@launchpad-ui/alert": { + "version": "0.9.20", + "resolved": "https://registry.npmjs.org/@launchpad-ui/alert/-/alert-0.9.20.tgz", + "integrity": "sha512-zKBAis4Mzxtj8+dsQ/wrVmhkLjWZ/eiN9iVTBYmfI8iqlK9ITM4+m4YzBfQHkMowML+tE405eI6gPYWYz/d7OQ==", + "dependencies": { + "@launchpad-ui/button": "~0.12.17", + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/tokens": "~0.9.12", + "@react-stately/utils": "3.10.1", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/avatar": { + "version": "0.6.30", + "resolved": "https://registry.npmjs.org/@launchpad-ui/avatar/-/avatar-0.6.30.tgz", + "integrity": "sha512-b0IoCn0Jgq6DpxUI5lbiriyzxSKvFTd2b6/cx3mzD0y/6lgFBz3LaFNj6FXhb6b+qMchK2DkSfYKV+ErsqWa/w==", + "dependencies": { + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/tokens": "~0.9.12", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/banner": { + "version": "0.10.30", + "resolved": "https://registry.npmjs.org/@launchpad-ui/banner/-/banner-0.10.30.tgz", + "integrity": "sha512-b77fZA6es+lLMCoqKHidb3ayh3r9FsUo9Rq3ZZm4D4Wk8qxi2lwORU/5+R7rGWq5kTfaBRCNiDGbGWOGcQCZng==", + "dependencies": { + "@launchpad-ui/button": "~0.12.17", + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/tokens": "~0.9.12", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/box": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@launchpad-ui/box/-/box-0.1.13.tgz", + "integrity": "sha512-uoErz95ETafuMmAeSPVCMovT7gB7qyDWOkQB+kA8fQwtIl7/JxuawcsRl00cgVkyNv2mhs7o4PwIPhfAT3KDwg==", + "dependencies": { + "@launchpad-ui/tokens": "~0.9.12", + "@launchpad-ui/vars": "~0.2.19", + "@radix-ui/react-slot": "1.0.2", + "@vanilla-extract/dynamic": "2.1.0", + "rainbow-sprinkles": "0.17.0" + }, + "peerDependencies": { + "@vanilla-extract/css": "^1.14.0", + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/button": { + "version": "0.12.17", + "resolved": "https://registry.npmjs.org/@launchpad-ui/button/-/button-0.12.17.tgz", + "integrity": "sha512-6XT0wXZxZKnfowU/H+hrlxjFCoVCKKMqaQsnViABubphLQvo8KXX8xskzwA1GXGGBEh2U5pykkfPlmFCEC5vzw==", + "dependencies": { + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/tokens": "~0.9.12", + "@radix-ui/react-slot": "1.0.2", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/card": { + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/@launchpad-ui/card/-/card-0.2.35.tgz", + "integrity": "sha512-tMwD7ObfhS6YjbrboBep35nTzHogUeuNTI/27OtfdwmF0SRvuKmA/PaXAE1B5SSyepZmJMHKnzMn2suWfWarlA==", + "dependencies": { + "@launchpad-ui/form": "~0.11.20", + "@launchpad-ui/tokens": "~0.9.12", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/chip": { + "version": "0.9.30", + "resolved": "https://registry.npmjs.org/@launchpad-ui/chip/-/chip-0.9.30.tgz", + "integrity": "sha512-+n/yYq3FU1JnFsKs5cYg0tsEBl7oU2a19iiiiTvDBXmcEV762plOwo/Vsh+9nbh5kbjUpwLypNgpsQqhz5wgCg==", + "dependencies": { + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/tokens": "~0.9.12", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/clipboard": { + "version": "0.11.35", + "resolved": "https://registry.npmjs.org/@launchpad-ui/clipboard/-/clipboard-0.11.35.tgz", + "integrity": "sha512-ZXuRcFC3u00jbTAWhRmW6/rlcJYOXTXMCUtx/ytOpFdr7cD80gRuUFl/OyNgXJOOEnXP3ZT8TR4IHZWmlcrI9w==", + "dependencies": { + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/tokens": "~0.9.12", + "@launchpad-ui/tooltip": "~0.9.12", + "@radix-ui/react-slot": "1.0.2", + "@react-aria/live-announcer": "3.3.4", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/collapsible": { + "version": "0.1.61", + "resolved": "https://registry.npmjs.org/@launchpad-ui/collapsible/-/collapsible-0.1.61.tgz", + "integrity": "sha512-13jplhIlUUxgaCeLCBAM/pkCYCNLEW1TaBwMHEVgelhcCu08JDU9VmSzONnJDa5KdzOWv7Ob64868R8gwwb+CA==", + "dependencies": { + "@launchpad-ui/button": "~0.12.17", + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/tokens": "~0.9.12", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/columns": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@launchpad-ui/columns/-/columns-0.1.22.tgz", + "integrity": "sha512-HURsO+6Xk6QwnnLfFedXwAn38sdZ8xxFToXmbawtD07+uSnVDJULwDVAJytMD+NlXqFCsAYhjBZoBjrwUaRBCw==", + "dependencies": { + "@launchpad-ui/tokens": "~0.9.12", + "@launchpad-ui/types": "~0.1.1", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/components": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@launchpad-ui/components/-/components-0.2.12.tgz", + "integrity": "sha512-bmhVFxcvmy+2F0dr04Qpfkp7mMNOp5AP/v7tftvTLLhfaya8Vh+75wCvsvgphoFQq0JSbf0KVfXOMQuxF5yGsQ==", + "dependencies": { + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/tokens": "~0.9.12", + "@react-aria/interactions": "3.21.3", + "@react-aria/toast": "3.0.0-beta.12", + "@react-aria/utils": "3.24.1", + "@react-stately/toast": "3.0.0-beta.4", + "@react-types/shared": "3.23.1", + "class-variance-authority": "0.7.0", + "react-aria": "3.33.1", + "react-aria-components": "1.2.1", + "react-router-dom": "6.16.0", + "react-stately": "3.31.1" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/core": { + "version": "0.49.22", + "resolved": "https://registry.npmjs.org/@launchpad-ui/core/-/core-0.49.22.tgz", + "integrity": "sha512-pEUVUMm6rlONqim6OqycA24zOzh19rGL0pk7EoHZfGfy9j4TCJIOnm6JjuPwDzEV1MsdUz7yXbNmSsePogFPqw==", + "dependencies": { + "@launchpad-ui/alert": "~0.9.20", + "@launchpad-ui/avatar": "~0.6.30", + "@launchpad-ui/banner": "~0.10.30", + "@launchpad-ui/box": "~0.1.13", + "@launchpad-ui/button": "~0.12.17", + "@launchpad-ui/card": "~0.2.35", + "@launchpad-ui/chip": "~0.9.30", + "@launchpad-ui/clipboard": "~0.11.35", + "@launchpad-ui/collapsible": "~0.1.61", + "@launchpad-ui/columns": "~0.1.22", + "@launchpad-ui/counter": "~0.4.16", + "@launchpad-ui/data-table": "~0.2.21", + "@launchpad-ui/drawer": "~0.5.35", + "@launchpad-ui/dropdown": "~0.6.109", + "@launchpad-ui/filter": "~0.7.20", + "@launchpad-ui/focus-trap": "~0.1.20", + "@launchpad-ui/form": "~0.11.20", + "@launchpad-ui/inline": "~0.1.22", + "@launchpad-ui/inline-edit": "~0.3.20", + "@launchpad-ui/markdown": "~0.5.18", + "@launchpad-ui/menu": "~0.13.20", + "@launchpad-ui/modal": "~0.17.35", + "@launchpad-ui/navigation": "~0.12.35", + "@launchpad-ui/overlay": "~0.3.30", + "@launchpad-ui/pagination": "~0.4.35", + "@launchpad-ui/popover": "~0.11.23", + "@launchpad-ui/portal": "~0.1.5", + "@launchpad-ui/progress": "~0.5.47", + "@launchpad-ui/progress-bubbles": "~0.7.23", + "@launchpad-ui/select": "~0.4.35", + "@launchpad-ui/slider": "~0.5.16", + "@launchpad-ui/snackbar": "~0.5.18", + "@launchpad-ui/split-button": "~0.10.20", + "@launchpad-ui/stack": "~0.1.22", + "@launchpad-ui/tab-list": "~0.5.22", + "@launchpad-ui/table": "~0.6.17", + "@launchpad-ui/tag": "~0.3.35", + "@launchpad-ui/toast": "~0.3.31", + "@launchpad-ui/toggle": "~0.7.22", + "@launchpad-ui/tooltip": "~0.9.12", + "@launchpad-ui/types": "~0.1.1" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/counter": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@launchpad-ui/counter/-/counter-0.4.16.tgz", + "integrity": "sha512-LH2uN4AV+cAHdVJuS2D3Rg/IJP706cw1nWXr0VPPW8ihW1fa1U/klUmmiSXKtvtjmcbdLyGB6Y7dKKithyAoUw==", + "dependencies": { + "@launchpad-ui/tokens": "~0.9.12", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/data-table": { + "version": "0.2.21", + "resolved": "https://registry.npmjs.org/@launchpad-ui/data-table/-/data-table-0.2.21.tgz", + "integrity": "sha512-GEqR07JKgC18O2YYwyxKl7SNLgkI6KCbcnGa0eHP/6YAZSAZFwPJ289JEfyKZTLKEosgbplQV1SGNglKOWaLRQ==", + "dependencies": { + "@launchpad-ui/tokens": "~0.9.12", + "@launchpad-ui/vars": "~0.2.19", + "@react-aria/checkbox": "3.14.3", + "@react-aria/focus": "3.17.1", + "@react-aria/table": "3.14.1", + "@react-aria/utils": "3.24.1", + "@react-aria/visually-hidden": "3.8.12", + "@react-stately/table": "3.11.8", + "@react-stately/toggle": "3.7.4", + "@react-types/grid": "3.2.6", + "@vanilla-extract/recipes": "^0.5.0", + "classix": "2.1.17" + }, + "peerDependencies": { + "@react-stately/table": "3.11.8", + "@vanilla-extract/css": "^1.14.0", + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/drawer": { + "version": "0.5.35", + "resolved": "https://registry.npmjs.org/@launchpad-ui/drawer/-/drawer-0.5.35.tgz", + "integrity": "sha512-IlKpK38YHIJJPJSZSyX7W5GoixukOzYk0BRoYcG18GoYS5orf8w4yCBWx93xh56/s71ZyMaJSlYOmv0eh3WUEg==", + "dependencies": { + "@launchpad-ui/button": "~0.12.17", + "@launchpad-ui/focus-trap": "~0.1.20", + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/portal": "~0.1.5", + "@launchpad-ui/progress": "~0.5.47", + "@launchpad-ui/tokens": "~0.9.12", + "@react-aria/interactions": "3.21.3", + "@react-aria/overlays": "3.22.1", + "classix": "2.1.17", + "framer-motion": "11.2.4" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/dropdown": { + "version": "0.6.109", + "resolved": "https://registry.npmjs.org/@launchpad-ui/dropdown/-/dropdown-0.6.109.tgz", + "integrity": "sha512-vKwDHSniDZCyK4NZo7Q8GhxK56HexSnFb7dc5075K6zgIDNkVMyY9WiUbD/PYkOIVWuKWn+2F+4tla6fIHQ5dA==", + "dependencies": { + "@launchpad-ui/button": "~0.12.17", + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/popover": "~0.11.23", + "@launchpad-ui/tokens": "~0.9.12", + "@react-aria/utils": "3.24.1", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/filter": { + "version": "0.7.20", + "resolved": "https://registry.npmjs.org/@launchpad-ui/filter/-/filter-0.7.20.tgz", + "integrity": "sha512-8+KcL5xZKH7sYsyj8Tz2pr2QtMA1AjDF5bv/JkIKWgFLXsUdzvXk2JoTKDRq3wI72T3A9UGiYI4zxX7+5VpFfw==", + "dependencies": { + "@launchpad-ui/button": "~0.12.17", + "@launchpad-ui/dropdown": "~0.6.109", + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/menu": "~0.13.20", + "@launchpad-ui/tokens": "~0.9.12", + "@launchpad-ui/tooltip": "~0.9.12", + "@react-aria/visually-hidden": "3.8.12", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/focus-trap": { + "version": "0.1.20", + "resolved": "https://registry.npmjs.org/@launchpad-ui/focus-trap/-/focus-trap-0.1.20.tgz", + "integrity": "sha512-n/nHfKaj5G8rxw9ok10llFff4QmyUPUhMlISBuh7ZuMW0GeBD5EqLiE64IyXDCSAFJqUt6nvx+peN9tHyqatsQ==", + "dependencies": { + "@react-aria/focus": "3.17.1" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/form": { + "version": "0.11.20", + "resolved": "https://registry.npmjs.org/@launchpad-ui/form/-/form-0.11.20.tgz", + "integrity": "sha512-X6Gx2p3wWC8wdZMYV7VM8K0Q9BMRi5jP8/RDR2GP9NyQ+4It6XXwL2vTmlaOQhdiJfILDlaSQqTYxYZNF342Pw==", + "dependencies": { + "@launchpad-ui/button": "~0.12.17", + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/tokens": "~0.9.12", + "@launchpad-ui/tooltip": "~0.9.12", + "@react-aria/button": "3.9.5", + "@react-aria/i18n": "3.11.1", + "@react-aria/numberfield": "3.11.3", + "@react-aria/visually-hidden": "3.8.12", + "@react-stately/numberfield": "3.9.3", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/icons": { + "version": "0.18.4", + "resolved": "https://registry.npmjs.org/@launchpad-ui/icons/-/icons-0.18.4.tgz", + "integrity": "sha512-hcHUJFcxxPRs8PIWXZHAQGmezecEW+SUAoJ/FGAjtZFxse+iHD/IxMyzNIvNiWOqTFjew7Jt16U9veHvJfth4A==", + "dependencies": { + "@launchpad-ui/tokens": "~0.9.12", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/inline": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@launchpad-ui/inline/-/inline-0.1.22.tgz", + "integrity": "sha512-v0gdWJzgQhWbLpo45Gw9DiT0bK5OEmb6pf6zpIu1mavvfpgqRoI724DIKcgXJQ7zoP+Hna3YtCY8HZqGMfqpEw==", + "dependencies": { + "@launchpad-ui/tokens": "~0.9.12", + "@launchpad-ui/types": "~0.1.1", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/inline-edit": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@launchpad-ui/inline-edit/-/inline-edit-0.3.20.tgz", + "integrity": "sha512-TGyld7YP3I9xs/t4kR6xof/2QGHxX/jDJ/SnhbCYeiHW94AADT7MKwO60tcZElTBVRai6oSo4C2v6kqzgl7AQg==", + "dependencies": { + "@launchpad-ui/button": "~0.12.17", + "@launchpad-ui/form": "~0.11.20", + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/tokens": "~0.9.12", + "@launchpad-ui/vars": "~0.2.19", + "@react-aria/button": "3.9.5", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/utils": "3.24.1", + "@vanilla-extract/recipes": "^0.5.0", + "classix": "2.1.17" + }, + "peerDependencies": { + "@vanilla-extract/css": "^1.14.0", + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/markdown": { + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/@launchpad-ui/markdown/-/markdown-0.5.18.tgz", + "integrity": "sha512-RT3Aeb1+7zlYMg+llp0aFv0ZGrm94maZ6KwQ2Tvl1jMpByqOJdOm6qkohOr713rNO2zykoZG2slCFICw9qbuhw==", + "dependencies": { + "@launchpad-ui/tokens": "~0.9.12", + "classix": "2.1.17", + "isomorphic-dompurify": "^2.12.0", + "marked": "^13.0.0" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/menu": { + "version": "0.13.20", + "resolved": "https://registry.npmjs.org/@launchpad-ui/menu/-/menu-0.13.20.tgz", + "integrity": "sha512-TmJQvN+6aEiCo96AAF7BJ1sprF8bNLMizyjr9ENOs9qVoe0zp37QmBVkSCq136Ccrohp5hCd7+X3ITxBuNbPeQ==", + "dependencies": { + "@launchpad-ui/form": "~0.11.20", + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/popover": "~0.11.23", + "@launchpad-ui/tokens": "~0.9.12", + "@launchpad-ui/tooltip": "~0.9.12", + "@radix-ui/react-slot": "1.0.2", + "@react-aria/focus": "3.17.1", + "@react-aria/separator": "3.3.13", + "classix": "2.1.17", + "react-virtual": "2.10.4" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/modal": { + "version": "0.17.35", + "resolved": "https://registry.npmjs.org/@launchpad-ui/modal/-/modal-0.17.35.tgz", + "integrity": "sha512-XAcvaHN4TSPL+OSiTRlwEOK7F7fpiudRPPOtri77bRqW6RRqAHlrHJknYUTbxmj6ZixD9zQncFURG2Gb9jLw2A==", + "dependencies": { + "@launchpad-ui/button": "~0.12.17", + "@launchpad-ui/focus-trap": "~0.1.20", + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/portal": "~0.1.5", + "@launchpad-ui/tokens": "~0.9.12", + "@react-aria/interactions": "3.21.3", + "@react-aria/overlays": "3.22.1", + "classix": "2.1.17", + "framer-motion": "11.2.4" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/navigation": { + "version": "0.12.35", + "resolved": "https://registry.npmjs.org/@launchpad-ui/navigation/-/navigation-0.12.35.tgz", + "integrity": "sha512-on4tScWncd+YMqKJAGhkaO06tgaNEhvAbDolBw8Kiiimwur5dN16BjGcWjEmfNMV5sd9wl8bZGBmQXoigBDV1g==", + "dependencies": { + "@launchpad-ui/chip": "~0.9.30", + "@launchpad-ui/dropdown": "~0.6.109", + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/menu": "~0.13.20", + "@launchpad-ui/popover": "~0.11.23", + "@launchpad-ui/tokens": "~0.9.12", + "@launchpad-ui/tooltip": "~0.9.12", + "@react-aria/utils": "3.24.1", + "@react-stately/list": "3.10.5", + "@react-types/shared": "3.23.1", + "classix": "2.1.17", + "react-router-dom": "6.16.0" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/overlay": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@launchpad-ui/overlay/-/overlay-0.3.30.tgz", + "integrity": "sha512-KLQez0pA0HaF0Anse8MRsh+T6ruIswXC0ltQyFQS9USnOLTCqtjSYqXdgLx9O1ecbGidQT1CPHvEIRsORUxEvg==", + "dependencies": { + "@launchpad-ui/portal": "~0.1.5" + }, + "peerDependencies": { + "react": "18.2.0", + "react-dom": "18.2.0" + } + }, + "node_modules/@launchpad-ui/pagination": { + "version": "0.4.35", + "resolved": "https://registry.npmjs.org/@launchpad-ui/pagination/-/pagination-0.4.35.tgz", + "integrity": "sha512-F+VQDXJ4pZnp99ZbJ6A0SrJX+WJ4RJHHqihZcX9vB+NVl3XZgjYE2+W/UWM/dK9KDMmsokGv9B+qV1FDNyKF+w==", + "dependencies": { + "@launchpad-ui/button": "~0.12.17", + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/progress": "~0.5.47", + "@launchpad-ui/tokens": "~0.9.12", + "@react-aria/visually-hidden": "3.8.12", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/popover": { + "version": "0.11.23", + "resolved": "https://registry.npmjs.org/@launchpad-ui/popover/-/popover-0.11.23.tgz", + "integrity": "sha512-x6B+/cZm5NcJXwLMSBXK+xeg/Yfk12AjgNmhU3IDFC9XjDVaNNKIheQe/oFiz2/29FLkhYmU/numSzDojxexfg==", + "dependencies": { + "@floating-ui/core": "1.6.0", + "@floating-ui/dom": "1.6.1", + "@launchpad-ui/focus-trap": "~0.1.20", + "@launchpad-ui/overlay": "~0.3.30", + "@launchpad-ui/tokens": "~0.9.12", + "classix": "2.1.17", + "framer-motion": "11.2.4" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/portal": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@launchpad-ui/portal/-/portal-0.1.5.tgz", + "integrity": "sha512-swNZFu99gVz0BdOS95EFdZVNU7KIgrxiZtTFNNJrKLMp4QzsvhJt6mB8cPArPXM5UH8kHLVTDXCMc9oZg66m+A==", + "peerDependencies": { + "react": "18.2.0", + "react-dom": "18.2.0" + } + }, + "node_modules/@launchpad-ui/progress": { + "version": "0.5.47", + "resolved": "https://registry.npmjs.org/@launchpad-ui/progress/-/progress-0.5.47.tgz", + "integrity": "sha512-Ai1q0lQ2zOSdvn7bWePSzWy6aGn7xArkkq+osp0PFAf3gn+E2uw0VHWYUnf68XEWbDjZL60L+LxHv56AHBGd9w==", + "dependencies": { + "@launchpad-ui/tokens": "~0.9.12", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/progress-bubbles": { + "version": "0.7.23", + "resolved": "https://registry.npmjs.org/@launchpad-ui/progress-bubbles/-/progress-bubbles-0.7.23.tgz", + "integrity": "sha512-vIBJBGmUFEBLusHzJmqPH+iT3MWZnoCPLGEsrE7WmwYQ2sQWJgAejKJAai3ulU1LOZ3TzL7JTUjiBQlczMERYw==", + "dependencies": { + "@launchpad-ui/popover": "~0.11.23", + "@launchpad-ui/tokens": "~0.9.12", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/select": { + "version": "0.4.35", + "resolved": "https://registry.npmjs.org/@launchpad-ui/select/-/select-0.4.35.tgz", + "integrity": "sha512-U0VypPwXHghj/Ajew/Y+QpZjEXLdvotWcXrPqLrOTNE+QBNgkDqB34P29NUTd7MqBUlN/VzeNiX10RnVHlFY5A==", + "dependencies": { + "@launchpad-ui/button": "~0.12.17", + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/modal": "~0.17.35", + "@launchpad-ui/popover": "~0.11.23", + "@launchpad-ui/tokens": "~0.9.12", + "@launchpad-ui/tooltip": "~0.9.12", + "@react-aria/button": "3.9.5", + "@react-aria/combobox": "3.9.1", + "@react-aria/focus": "3.17.1", + "@react-aria/gridlist": "3.8.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/label": "3.7.8", + "@react-aria/listbox": "3.12.1", + "@react-aria/menu": "3.14.1", + "@react-aria/overlays": "3.22.1", + "@react-aria/select": "3.14.5", + "@react-aria/selection": "3.18.1", + "@react-aria/separator": "3.3.13", + "@react-aria/textfield": "3.14.5", + "@react-aria/utils": "3.24.1", + "@react-aria/visually-hidden": "3.8.12", + "@react-stately/collections": "3.10.7", + "@react-stately/combobox": "3.8.4", + "@react-stately/data": "3.11.4", + "@react-stately/list": "3.10.5", + "@react-stately/menu": "3.7.1", + "@react-stately/overlays": "3.6.7", + "@react-stately/select": "3.6.4", + "@react-stately/selection": "3.15.1", + "@react-stately/utils": "3.10.1", + "@react-types/button": "3.9.4", + "@react-types/combobox": "3.11.1", + "@react-types/overlays": "3.8.7", + "@react-types/select": "3.9.4", + "@react-types/shared": "3.23.1", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/slider": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/@launchpad-ui/slider/-/slider-0.5.16.tgz", + "integrity": "sha512-Qt5349CLr7M6ESEWwhesiVW4YHTHE37YhiFoGuIAJubw3xd44WcCSSM+54Y+pQT8P1ZxnZoQ+p9Nvrc7clGlGg==", + "dependencies": { + "@launchpad-ui/tokens": "~0.9.12", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/snackbar": { + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/@launchpad-ui/snackbar/-/snackbar-0.5.18.tgz", + "integrity": "sha512-Od+G4NCSw5mBqwTAaAOzzcpdgQjU8Id96tXCictGxRyJKb+oH3GExpcM0EAgDlPH1zBktIICcr8GN4YgsWZuxg==", + "dependencies": { + "@launchpad-ui/button": "~0.12.17", + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/tokens": "~0.9.12", + "classix": "2.1.17", + "framer-motion": "11.2.4" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/split-button": { + "version": "0.10.20", + "resolved": "https://registry.npmjs.org/@launchpad-ui/split-button/-/split-button-0.10.20.tgz", + "integrity": "sha512-ZILhoKCaLfA1ZDF5fzL5N5nyBMk3BTD4FVU0p4RgJM9q8zYUU4HJBZ3Tasna407YxrsCZQTFV/jJry5KBJ7c6A==", + "dependencies": { + "@launchpad-ui/button": "~0.12.17", + "@launchpad-ui/dropdown": "~0.6.109", + "@launchpad-ui/popover": "~0.11.23", + "@launchpad-ui/tokens": "~0.9.12", + "@launchpad-ui/tooltip": "~0.9.12", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/stack": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@launchpad-ui/stack/-/stack-0.1.22.tgz", + "integrity": "sha512-3oQWEe+wmhiF6vy6ZgGaVNIcbiwSP4yJurhGSEYMNbxwK7XhnjZyziUucBBg+55z8M9nHYP7mS1t50k2hjSKKw==", + "dependencies": { + "@launchpad-ui/tokens": "~0.9.12", + "@launchpad-ui/types": "~0.1.1", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/tab-list": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@launchpad-ui/tab-list/-/tab-list-0.5.22.tgz", + "integrity": "sha512-k/8SpOOuqpNSGsIblwB8S1IaJfQUSBkqjz4osl5m9AE4+E4DGg+U0+QY8ssm/PjPMEaaOh/dn1SE5KufWo94Lw==", + "dependencies": { + "@launchpad-ui/tokens": "~0.9.12", + "@react-aria/tabs": "3.9.1", + "@react-stately/tabs": "3.6.6", + "@react-types/shared": "3.23.1", + "@react-types/tabs": "3.3.7", + "classix": "2.1.17" + }, + "peerDependencies": { + "@react-stately/collections": "3.10.7", + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/table": { + "version": "0.6.17", + "resolved": "https://registry.npmjs.org/@launchpad-ui/table/-/table-0.6.17.tgz", + "integrity": "sha512-rOcHHgLMVGFds8aa69UDOd3mcibvVZfAIvJ8F1AYUNVc6Ynb7uUzGkjEjySbaDakbQ0zcyjYMozVcZ4WBlDVAg==", + "dependencies": { + "@launchpad-ui/tokens": "~0.9.12", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/tag": { + "version": "0.3.35", + "resolved": "https://registry.npmjs.org/@launchpad-ui/tag/-/tag-0.3.35.tgz", + "integrity": "sha512-RRwiaF158Wcu4b+T6Aaefzr5Yn7SK4Mg7j0bPZ7XoboClzbzranxaTm2IwC3YOPyf1GTmJ8+3bT6dRPrgBxsQw==", + "dependencies": { + "@launchpad-ui/button": "~0.12.17", + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/tokens": "~0.9.12", + "@launchpad-ui/tooltip": "~0.9.12", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/selection": "3.18.1", + "@react-aria/tag": "3.4.1", + "@react-aria/utils": "3.24.1", + "@react-stately/list": "3.10.5", + "@react-types/shared": "3.23.1", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/toast": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@launchpad-ui/toast/-/toast-0.3.31.tgz", + "integrity": "sha512-TdFFK2sw0xqTD1olN/zw6gh09q3s92zi0v98ub3wISfZtibGf4EPjMIcx0bFbdYC13kBMoemoVRbpew2cb5Pvw==", + "dependencies": { + "@launchpad-ui/icons": "~0.18.4", + "@launchpad-ui/tokens": "~0.9.12", + "classix": "2.1.17", + "framer-motion": "11.2.4" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/toggle": { + "version": "0.7.22", + "resolved": "https://registry.npmjs.org/@launchpad-ui/toggle/-/toggle-0.7.22.tgz", + "integrity": "sha512-X3FXxdM1a32x1qbcCLQT3C6IdBwIkt07SfRjlbYql1socANcCmWOdRD1Qd0JWjARpoDMDediGZtJZ6EsLUxroQ==", + "dependencies": { + "@launchpad-ui/tokens": "~0.9.12", + "@react-aria/focus": "3.17.1", + "@react-aria/switch": "3.6.4", + "@react-stately/toggle": "3.7.4", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/tokens": { + "version": "0.9.12", + "resolved": "https://registry.npmjs.org/@launchpad-ui/tokens/-/tokens-0.9.12.tgz", + "integrity": "sha512-FEDBt0s+b0hlwkhSU/TiY5YXseLQYGKeIgqZUngmUiPTgaSvttlBm4BSVtoly3JK00ihNGZsvEtZVoUgp+zoKA==" + }, + "node_modules/@launchpad-ui/tooltip": { + "version": "0.9.12", + "resolved": "https://registry.npmjs.org/@launchpad-ui/tooltip/-/tooltip-0.9.12.tgz", + "integrity": "sha512-qsEnQAXOEpPyyoMEN0fa1D8mgKYPMd09VGQCg65RS0KNNbK/TjewbvtKi07AZdzol1hTzArnpCaM7/INPRLBoA==", + "dependencies": { + "@launchpad-ui/popover": "~0.11.23", + "@launchpad-ui/tokens": "~0.9.12", + "classix": "2.1.17" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + } + }, + "node_modules/@launchpad-ui/types": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@launchpad-ui/types/-/types-0.1.1.tgz", + "integrity": "sha512-0mHnqVNIHNwrRe4uw5DacWp1N+WNFt6PgVs/5xAuO6tlH5WXHq1WqEg3H4rKi46sXwsepEaijdhKQC3xlajcmA==" + }, + "node_modules/@launchpad-ui/vars": { + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/@launchpad-ui/vars/-/vars-0.2.19.tgz", + "integrity": "sha512-fvSRN/mQ+DpuaLpXO4usLaqfaqb0u0M6ryMLIMpqK9kdv5gTjbha3GWOv0MVz5PKDBWFkXdgNtvvkWzYjlgZQA==", + "dependencies": { + "@launchpad-ui/tokens": "~0.9.12" + }, + "peerDependencies": { + "@vanilla-extract/css": "^1.14.0" + }, + "peerDependenciesMeta": { + "@vanilla-extract/css": { + "optional": true + } + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", + "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@reach/observe-rect": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@reach/observe-rect/-/observe-rect-1.2.0.tgz", + "integrity": "sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==" + }, + "node_modules/@react-aria/breadcrumbs": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.13.tgz", + "integrity": "sha512-G1Gqf/P6kVdfs94ovwP18fTWuIxadIQgHsXS08JEVcFVYMjb9YjqnEBaohUxD1tq2WldMbYw53ahQblT4NTG+g==", + "dependencies": { + "@react-aria/i18n": "^3.11.1", + "@react-aria/link": "^3.7.1", + "@react-aria/utils": "^3.24.1", + "@react-types/breadcrumbs": "^3.7.5", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/button": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/@react-aria/button/-/button-3.9.5.tgz", + "integrity": "sha512-dgcYR6j8WDOMLKuVrtxzx4jIC05cVKDzc+HnPO8lNkBAOfjcuN5tkGRtIjLtqjMvpZHhQT5aDbgFpIaZzxgFIg==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/utils": "^3.24.1", + "@react-stately/toggle": "^3.7.4", + "@react-types/button": "^3.9.4", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/calendar": { + "version": "3.5.8", + "resolved": "https://registry.npmjs.org/@react-aria/calendar/-/calendar-3.5.8.tgz", + "integrity": "sha512-Whlp4CeAA5/ZkzrAHUv73kgIRYjw088eYGSc+cvSOCxfrc/2XkBm9rNrnSBv0DvhJ8AG0Fjz3vYakTmF3BgZBw==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/live-announcer": "^3.3.4", + "@react-aria/utils": "^3.24.1", + "@react-stately/calendar": "^3.5.1", + "@react-types/button": "^3.9.4", + "@react-types/calendar": "^3.4.6", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/checkbox": { + "version": "3.14.3", + "resolved": "https://registry.npmjs.org/@react-aria/checkbox/-/checkbox-3.14.3.tgz", + "integrity": "sha512-EtBJL6iu0gvrw3A4R7UeVLR6diaVk/mh4kFBc7c8hQjpEJweRr4hmJT3hrNg3MBcTWLxFiMEXPGgWEwXDBygtA==", + "dependencies": { + "@react-aria/form": "^3.0.5", + "@react-aria/interactions": "^3.21.3", + "@react-aria/label": "^3.7.8", + "@react-aria/toggle": "^3.10.4", + "@react-aria/utils": "^3.24.1", + "@react-stately/checkbox": "^3.6.5", + "@react-stately/form": "^3.0.3", + "@react-stately/toggle": "^3.7.4", + "@react-types/checkbox": "^3.8.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/color": { + "version": "3.0.0-beta.33", + "resolved": "https://registry.npmjs.org/@react-aria/color/-/color-3.0.0-beta.33.tgz", + "integrity": "sha512-nhqnIHYm5p6MbuF3cC6lnqzG7MjwBsBd0DtpO+ByFYO+zxpMMbeC5R+1SFxvapR4uqmAzTotbtiUCGsG+SUaIg==", + "dependencies": { + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/numberfield": "^3.11.3", + "@react-aria/slider": "^3.7.8", + "@react-aria/spinbutton": "^3.6.5", + "@react-aria/textfield": "^3.14.5", + "@react-aria/utils": "^3.24.1", + "@react-aria/visually-hidden": "^3.8.12", + "@react-stately/color": "^3.6.1", + "@react-stately/form": "^3.0.3", + "@react-types/color": "3.0.0-beta.25", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/combobox": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-aria/combobox/-/combobox-3.9.1.tgz", + "integrity": "sha512-SpK92dCmT8qn8aEcUAihRQrBb5LZUhwIbDExFII8PvUvEFy/PoQHXIo3j1V29WkutDBDpMvBv/6XRCHGXPqrhQ==", + "dependencies": { + "@react-aria/i18n": "^3.11.1", + "@react-aria/listbox": "^3.12.1", + "@react-aria/live-announcer": "^3.3.4", + "@react-aria/menu": "^3.14.1", + "@react-aria/overlays": "^3.22.1", + "@react-aria/selection": "^3.18.1", + "@react-aria/textfield": "^3.14.5", + "@react-aria/utils": "^3.24.1", + "@react-stately/collections": "^3.10.7", + "@react-stately/combobox": "^3.8.4", + "@react-stately/form": "^3.0.3", + "@react-types/button": "^3.9.4", + "@react-types/combobox": "^3.11.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/datepicker": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@react-aria/datepicker/-/datepicker-3.10.1.tgz", + "integrity": "sha512-4HZL593nrNMa1GjBmWEN/OTvNS6d3/16G1YJWlqiUlv11ADulSbqBIjMmkgwrJVFcjrgqtXFy+yyrTA/oq94Zw==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@internationalized/number": "^3.5.3", + "@internationalized/string": "^3.2.3", + "@react-aria/focus": "^3.17.1", + "@react-aria/form": "^3.0.5", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/label": "^3.7.8", + "@react-aria/spinbutton": "^3.6.5", + "@react-aria/utils": "^3.24.1", + "@react-stately/datepicker": "^3.9.4", + "@react-stately/form": "^3.0.3", + "@react-types/button": "^3.9.4", + "@react-types/calendar": "^3.4.6", + "@react-types/datepicker": "^3.7.4", + "@react-types/dialog": "^3.5.10", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/dialog": { + "version": "3.5.14", + "resolved": "https://registry.npmjs.org/@react-aria/dialog/-/dialog-3.5.14.tgz", + "integrity": "sha512-oqDCjQ8hxe3GStf48XWBf2CliEnxlR9GgSYPHJPUc69WBj68D9rVcCW3kogJnLAnwIyf3FnzbX4wSjvUa88sAQ==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/overlays": "^3.22.1", + "@react-aria/utils": "^3.24.1", + "@react-types/dialog": "^3.5.10", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/dnd": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@react-aria/dnd/-/dnd-3.6.1.tgz", + "integrity": "sha512-6WnujUTD+cIYZVF/B+uXdHyJ+WSpbYa8jH282epvY4FUAq1qLmen12/HHcoj/5dswKQe8X6EM3OhkQM89d9vFw==", + "dependencies": { + "@internationalized/string": "^3.2.3", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/live-announcer": "^3.3.4", + "@react-aria/overlays": "^3.22.1", + "@react-aria/utils": "^3.24.1", + "@react-stately/dnd": "^3.3.1", + "@react-types/button": "^3.9.4", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/focus": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.17.1.tgz", + "integrity": "sha512-FLTySoSNqX++u0nWZJPPN5etXY0WBxaIe/YuL/GTEeuqUIuC/2bJSaw5hlsM6T2yjy6Y/VAxBcKSdAFUlU6njQ==", + "dependencies": { + "@react-aria/interactions": "^3.21.3", + "@react-aria/utils": "^3.24.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/form": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@react-aria/form/-/form-3.0.5.tgz", + "integrity": "sha512-n290jRwrrRXO3fS82MyWR+OKN7yznVesy5Q10IclSTVYHHI3VI53xtAPr/WzNjJR1um8aLhOcDNFKwnNIUUCsQ==", + "dependencies": { + "@react-aria/interactions": "^3.21.3", + "@react-aria/utils": "^3.24.1", + "@react-stately/form": "^3.0.3", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/grid": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-aria/grid/-/grid-3.9.1.tgz", + "integrity": "sha512-fGEZqAEaS8mqzV/II3N4ndoNWegIcbh+L3PmKbXdpKKUP8VgMs/WY5rYl5WAF0f5RoFwXqx3ibDLeR9tKj/bOg==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/live-announcer": "^3.3.4", + "@react-aria/selection": "^3.18.1", + "@react-aria/utils": "^3.24.1", + "@react-stately/collections": "^3.10.7", + "@react-stately/grid": "^3.8.7", + "@react-stately/selection": "^3.15.1", + "@react-stately/virtualizer": "^3.7.1", + "@react-types/checkbox": "^3.8.1", + "@react-types/grid": "^3.2.6", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/gridlist": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@react-aria/gridlist/-/gridlist-3.8.1.tgz", + "integrity": "sha512-vVPkkA+Ct0NDcpnNm/tnYaBumg0fP9pXxsPLqL1rxvsTyj1PaIpFTZ4corabPTbTDExZwUSTS3LG1n+o1OvBtQ==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/grid": "^3.9.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/selection": "^3.18.1", + "@react-aria/utils": "^3.24.1", + "@react-stately/collections": "^3.10.7", + "@react-stately/list": "^3.10.5", + "@react-stately/tree": "^3.8.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/i18n": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.11.1.tgz", + "integrity": "sha512-vuiBHw1kZruNMYeKkTGGnmPyMnM5T+gT8bz97H1FqIq1hQ6OPzmtBZ6W6l6OIMjeHI5oJo4utTwfZl495GALFQ==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@internationalized/message": "^3.1.4", + "@internationalized/number": "^3.5.3", + "@internationalized/string": "^3.2.3", + "@react-aria/ssr": "^3.9.4", + "@react-aria/utils": "^3.24.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.21.3", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.21.3.tgz", + "integrity": "sha512-BWIuf4qCs5FreDJ9AguawLVS0lV9UU+sK4CCnbCNNmYqOWY+1+gRXCsnOM32K+oMESBxilAjdHW5n1hsMqYMpA==", + "dependencies": { + "@react-aria/ssr": "^3.9.4", + "@react-aria/utils": "^3.24.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/label": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/@react-aria/label/-/label-3.7.8.tgz", + "integrity": "sha512-MzgTm5+suPA3KX7Ug6ZBK2NX9cin/RFLsv1BdafJ6CZpmUSpWnGE/yQfYUB7csN7j31OsZrD3/P56eShYWAQfg==", + "dependencies": { + "@react-aria/utils": "^3.24.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/landmark": { + "version": "3.0.0-beta.12", + "resolved": "https://registry.npmjs.org/@react-aria/landmark/-/landmark-3.0.0-beta.12.tgz", + "integrity": "sha512-xCAYw0cxn115ZaTXyGXALW2Jebm56s7oX/R0/REubiHwkuDSRxRnYXbaaHxGXNsJWex5LGIZbUYaumGjGrzOnw==", + "dependencies": { + "@react-aria/utils": "^3.24.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/link": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@react-aria/link/-/link-3.7.1.tgz", + "integrity": "sha512-a4IaV50P3fXc7DQvEIPYkJJv26JknFbRzFT5MJOMgtzuhyJoQdILEUK6XHYjcSSNCA7uLgzpojArVk5Hz3lCpw==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/utils": "^3.24.1", + "@react-types/link": "^3.5.5", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/listbox": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@react-aria/listbox/-/listbox-3.12.1.tgz", + "integrity": "sha512-7JiUp0NGykbv/HgSpmTY1wqhuf/RmjFxs1HZcNaTv8A+DlzgJYc7yQqFjP3ZA/z5RvJFuuIxggIYmgIFjaRYdA==", + "dependencies": { + "@react-aria/interactions": "^3.21.3", + "@react-aria/label": "^3.7.8", + "@react-aria/selection": "^3.18.1", + "@react-aria/utils": "^3.24.1", + "@react-stately/collections": "^3.10.7", + "@react-stately/list": "^3.10.5", + "@react-types/listbox": "^3.4.9", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/live-announcer": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@react-aria/live-announcer/-/live-announcer-3.3.4.tgz", + "integrity": "sha512-w8lxs35QrRrn6pBNzVfyGOeqWdxeVKf9U6bXIVwhq7rrTqRULL8jqy8RJIMfIs1s8G5FpwWYjyBOjl2g5Cu1iA==", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-aria/menu": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/@react-aria/menu/-/menu-3.14.1.tgz", + "integrity": "sha512-BYliRb38uAzq05UOFcD5XkjA5foQoXRbcH3ZufBsc4kvh79BcP1PMW6KsXKGJ7dC/PJWUwCui6QL1kUg8PqMHA==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/overlays": "^3.22.1", + "@react-aria/selection": "^3.18.1", + "@react-aria/utils": "^3.24.1", + "@react-stately/collections": "^3.10.7", + "@react-stately/menu": "^3.7.1", + "@react-stately/tree": "^3.8.1", + "@react-types/button": "^3.9.4", + "@react-types/menu": "^3.9.9", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/meter": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/@react-aria/meter/-/meter-3.4.13.tgz", + "integrity": "sha512-oG6KvHQM3ri93XkYQkgEaMKSMO9KNDVpcW1MUqFfqyUXHFBRZRrJB4BTXMZ4nyjheFVQjVboU51fRwoLjOzThg==", + "dependencies": { + "@react-aria/progress": "^3.4.13", + "@react-types/meter": "^3.4.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/numberfield": { + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/@react-aria/numberfield/-/numberfield-3.11.3.tgz", + "integrity": "sha512-QQ9ZTzBbRI8d9ksaBWm6YVXbgv+5zzUsdxVxwzJVXLznvivoORB8rpdFJzUEWVCo25lzoBxluCEPYtLOxP1B0w==", + "dependencies": { + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/spinbutton": "^3.6.5", + "@react-aria/textfield": "^3.14.5", + "@react-aria/utils": "^3.24.1", + "@react-stately/form": "^3.0.3", + "@react-stately/numberfield": "^3.9.3", + "@react-types/button": "^3.9.4", + "@react-types/numberfield": "^3.8.3", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/overlays": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.22.1.tgz", + "integrity": "sha512-GHiFMWO4EQ6+j6b5QCnNoOYiyx1Gk8ZiwLzzglCI4q1NY5AG2EAmfU4Z1+Gtrf2S5Y0zHbumC7rs9GnPoGLUYg==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/ssr": "^3.9.4", + "@react-aria/utils": "^3.24.1", + "@react-aria/visually-hidden": "^3.8.12", + "@react-stately/overlays": "^3.6.7", + "@react-types/button": "^3.9.4", + "@react-types/overlays": "^3.8.7", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/progress": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/@react-aria/progress/-/progress-3.4.13.tgz", + "integrity": "sha512-YBV9bOO5JzKvG8QCI0IAA00o6FczMgIDiK8Q9p5gKorFMatFUdRayxlbIPoYHMi+PguLil0jHgC7eOyaUcrZ0g==", + "dependencies": { + "@react-aria/i18n": "^3.11.1", + "@react-aria/label": "^3.7.8", + "@react-aria/utils": "^3.24.1", + "@react-types/progress": "^3.5.4", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/radio": { + "version": "3.10.4", + "resolved": "https://registry.npmjs.org/@react-aria/radio/-/radio-3.10.4.tgz", + "integrity": "sha512-3fmoMcQtCpgjTwJReFjnvIE/C7zOZeCeWUn4JKDqz9s1ILYsC3Rk5zZ4q66tFn6v+IQnecrKT52wH6+hlVLwTA==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/form": "^3.0.5", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/label": "^3.7.8", + "@react-aria/utils": "^3.24.1", + "@react-stately/radio": "^3.10.4", + "@react-types/radio": "^3.8.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/searchfield": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/@react-aria/searchfield/-/searchfield-3.7.5.tgz", + "integrity": "sha512-h1sMUOWjhevaKKUHab/luHbM6yiyeN57L4RxZU0IIc9Ww0h5Rp2GUuKZA3pcdPiExHje0aijcImL3wBHEbKAzw==", + "dependencies": { + "@react-aria/i18n": "^3.11.1", + "@react-aria/textfield": "^3.14.5", + "@react-aria/utils": "^3.24.1", + "@react-stately/searchfield": "^3.5.3", + "@react-types/button": "^3.9.4", + "@react-types/searchfield": "^3.5.5", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/select": { + "version": "3.14.5", + "resolved": "https://registry.npmjs.org/@react-aria/select/-/select-3.14.5.tgz", + "integrity": "sha512-s8jixBuTUNdKWRHe2tIJqp55ORHeUObGMw1s7PQRRVrrHPdNSYseAOI9B2W7qpl3hKhvjJg40UW+45mcb1WKbw==", + "dependencies": { + "@react-aria/form": "^3.0.5", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/label": "^3.7.8", + "@react-aria/listbox": "^3.12.1", + "@react-aria/menu": "^3.14.1", + "@react-aria/selection": "^3.18.1", + "@react-aria/utils": "^3.24.1", + "@react-aria/visually-hidden": "^3.8.12", + "@react-stately/select": "^3.6.4", + "@react-types/button": "^3.9.4", + "@react-types/select": "^3.9.4", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/selection": { + "version": "3.18.1", + "resolved": "https://registry.npmjs.org/@react-aria/selection/-/selection-3.18.1.tgz", + "integrity": "sha512-GSqN2jX6lh7v+ldqhVjAXDcrWS3N4IsKXxO6L6Ygsye86Q9q9Mq9twWDWWu5IjHD6LoVZLUBCMO+ENGbOkyqeQ==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/utils": "^3.24.1", + "@react-stately/selection": "^3.15.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/separator": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@react-aria/separator/-/separator-3.3.13.tgz", + "integrity": "sha512-hofA6JCPnAOqSE9vxnq7Dkazr7Kb2A0I5sR16fOG7ddjYRc/YEY5Nv7MWfKUGU0kNFHkgNjsDAILERtLechzeA==", + "dependencies": { + "@react-aria/utils": "^3.24.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/slider": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/@react-aria/slider/-/slider-3.7.8.tgz", + "integrity": "sha512-MYvPcM0K8jxEJJicUK2+WxUkBIM/mquBxOTOSSIL3CszA80nXIGVnLlCUnQV3LOUzpWtabbWaZokSPtGgOgQOw==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/label": "^3.7.8", + "@react-aria/utils": "^3.24.1", + "@react-stately/slider": "^3.5.4", + "@react-types/shared": "^3.23.1", + "@react-types/slider": "^3.7.3", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/spinbutton": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@react-aria/spinbutton/-/spinbutton-3.6.5.tgz", + "integrity": "sha512-0aACBarF/Xr/7ixzjVBTQ0NBwwwsoGkf5v6AVFVMTC0uYMXHTALvRs+ULHjHMa5e/cX/aPlEvaVT7jfSs+Xy9Q==", + "dependencies": { + "@react-aria/i18n": "^3.11.1", + "@react-aria/live-announcer": "^3.3.4", + "@react-aria/utils": "^3.24.1", + "@react-types/button": "^3.9.4", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.4.tgz", + "integrity": "sha512-4jmAigVq409qcJvQyuorsmBR4+9r3+JEC60wC+Y0MZV0HCtTmm8D9guYXlJMdx0SSkgj0hHAyFm/HvPNFofCoQ==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/switch": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/@react-aria/switch/-/switch-3.6.4.tgz", + "integrity": "sha512-2nVqz4ZuJyof47IpGSt3oZRmp+EdS8wzeDYgf42WHQXrx4uEOk1mdLJ20+NnsYhj/2NHZsvXVrjBeKMjlMs+0w==", + "dependencies": { + "@react-aria/toggle": "^3.10.4", + "@react-stately/toggle": "^3.7.4", + "@react-types/switch": "^3.5.3", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/table": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/@react-aria/table/-/table-3.14.1.tgz", + "integrity": "sha512-WaPgQe4zQF5OaluO5rm+Y2nEoFR63vsLd4BT4yjK1uaFhKhDY2Zk+1SCVQvBLLKS4WK9dhP05nrNzT0vp/ZPOw==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/grid": "^3.9.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/live-announcer": "^3.3.4", + "@react-aria/utils": "^3.24.1", + "@react-aria/visually-hidden": "^3.8.12", + "@react-stately/collections": "^3.10.7", + "@react-stately/flags": "^3.0.3", + "@react-stately/table": "^3.11.8", + "@react-stately/virtualizer": "^3.7.1", + "@react-types/checkbox": "^3.8.1", + "@react-types/grid": "^3.2.6", + "@react-types/shared": "^3.23.1", + "@react-types/table": "^3.9.5", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/tabs": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-aria/tabs/-/tabs-3.9.1.tgz", + "integrity": "sha512-S5v/0sRcOaSXaJYZuuy1ZVzYc7JD4sDyseG1133GjyuNjJOFHgoWMb+b4uxNIJbZxnLgynn/ZDBZSO+qU+fIxw==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/selection": "^3.18.1", + "@react-aria/utils": "^3.24.1", + "@react-stately/tabs": "^3.6.6", + "@react-types/shared": "^3.23.1", + "@react-types/tabs": "^3.3.7", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/tag": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@react-aria/tag/-/tag-3.4.1.tgz", + "integrity": "sha512-gcIGPYZ2OBwMT4IHnlczEezKlxr0KRPL/mSfm2Q91GE027ZGOJnqusH9az6DX1qxrQx8x3vRdqYT2KmuefkrBQ==", + "dependencies": { + "@react-aria/gridlist": "^3.8.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/label": "^3.7.8", + "@react-aria/selection": "^3.18.1", + "@react-aria/utils": "^3.24.1", + "@react-stately/list": "^3.10.5", + "@react-types/button": "^3.9.4", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/textfield": { + "version": "3.14.5", + "resolved": "https://registry.npmjs.org/@react-aria/textfield/-/textfield-3.14.5.tgz", + "integrity": "sha512-hj7H+66BjB1iTKKaFXwSZBZg88YT+wZboEXZ0DNdQB2ytzoz/g045wBItUuNi4ZjXI3P+0AOZznVMYadWBAmiA==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/form": "^3.0.5", + "@react-aria/label": "^3.7.8", + "@react-aria/utils": "^3.24.1", + "@react-stately/form": "^3.0.3", + "@react-stately/utils": "^3.10.1", + "@react-types/shared": "^3.23.1", + "@react-types/textfield": "^3.9.3", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/toast": { + "version": "3.0.0-beta.12", + "resolved": "https://registry.npmjs.org/@react-aria/toast/-/toast-3.0.0-beta.12.tgz", + "integrity": "sha512-cNchqcvK+WJ+CTMzXBk8GzlsqEUUtbyLxz4BUgKHzysac50uhpbjdYbb+5cKetEaU6/wJ75VECk35wiLYkc16w==", + "dependencies": { + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/landmark": "3.0.0-beta.12", + "@react-aria/utils": "^3.24.1", + "@react-stately/toast": "3.0.0-beta.4", + "@react-types/button": "^3.9.4", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/toggle": { + "version": "3.10.4", + "resolved": "https://registry.npmjs.org/@react-aria/toggle/-/toggle-3.10.4.tgz", + "integrity": "sha512-bRk+CdB8QzrSyGNjENXiTWxfzYKRw753iwQXsEAU7agPCUdB8cZJyrhbaUoD0rwczzTp2zDbZ9rRbUPdsBE2YQ==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/utils": "^3.24.1", + "@react-stately/toggle": "^3.7.4", + "@react-types/checkbox": "^3.8.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/toolbar": { + "version": "3.0.0-beta.5", + "resolved": "https://registry.npmjs.org/@react-aria/toolbar/-/toolbar-3.0.0-beta.5.tgz", + "integrity": "sha512-c8spY7aeLI6L+ygdXvEbAzaT41vExsxZ1Ld0t7BB+6iEF3nyBNJHshjkgdR7nv8FLgNk0no4tj0GTq4Jj4UqHQ==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/utils": "^3.24.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/tooltip": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@react-aria/tooltip/-/tooltip-3.7.4.tgz", + "integrity": "sha512-+XRx4HlLYqWY3fB8Z60bQi/rbWDIGlFUtXYbtoa1J+EyRWfhpvsYImP8qeeNO/vgjUtDy1j9oKa8p6App9mBMQ==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/utils": "^3.24.1", + "@react-stately/tooltip": "^3.4.9", + "@react-types/shared": "^3.23.1", + "@react-types/tooltip": "^3.4.9", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/tree": { + "version": "3.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@react-aria/tree/-/tree-3.0.0-alpha.1.tgz", + "integrity": "sha512-CucyeJ4VeAvWO5UJHt/l9JO65CVtsOVUctMOVNCQS77Isqp3olX9pvfD3LXt8fD5Ph2g0Q/b7siVpX5ieVB32g==", + "dependencies": { + "@react-aria/gridlist": "^3.8.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/selection": "^3.18.1", + "@react-aria/utils": "^3.24.1", + "@react-stately/tree": "^3.8.1", + "@react-types/button": "^3.9.4", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/utils": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.24.1.tgz", + "integrity": "sha512-O3s9qhPMd6n42x9sKeJ3lhu5V1Tlnzhu6Yk8QOvDuXf7UGuUjXf9mzfHJt1dYzID4l9Fwm8toczBzPM9t0jc8Q==", + "dependencies": { + "@react-aria/ssr": "^3.9.4", + "@react-stately/utils": "^3.10.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/visually-hidden": { + "version": "3.8.12", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.12.tgz", + "integrity": "sha512-Bawm+2Cmw3Xrlr7ARzl2RLtKh0lNUdJ0eNqzWcyx4c0VHUAWtThmH5l+HRqFUGzzutFZVo89SAy40BAbd0gjVw==", + "dependencies": { + "@react-aria/interactions": "^3.21.3", + "@react-aria/utils": "^3.24.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/calendar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@react-stately/calendar/-/calendar-3.5.1.tgz", + "integrity": "sha512-7l7QhqGUJ5AzWHfvZzbTe3J4t72Ht5BmhW4hlVI7flQXtfrmYkVtl3ZdytEZkkHmWGYZRW9b4IQTQGZxhtlElA==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@react-stately/utils": "^3.10.1", + "@react-types/calendar": "^3.4.6", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/checkbox": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@react-stately/checkbox/-/checkbox-3.6.5.tgz", + "integrity": "sha512-IXV3f9k+LtmfQLE+DKIN41Q5QB/YBLDCB1YVx5PEdRp52S9+EACD5683rjVm8NVRDwjMi2SP6RnFRk7fVb5Azg==", + "dependencies": { + "@react-stately/form": "^3.0.3", + "@react-stately/utils": "^3.10.1", + "@react-types/checkbox": "^3.8.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/collections": { + "version": "3.10.7", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.10.7.tgz", + "integrity": "sha512-KRo5O2MWVL8n3aiqb+XR3vP6akmHLhLWYZEmPKjIv0ghQaEebBTrN3wiEjtd6dzllv0QqcWvDLM1LntNfJ2TsA==", + "dependencies": { + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/color": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@react-stately/color/-/color-3.6.1.tgz", + "integrity": "sha512-iW0nAhl3+fUBegHMw5EcAbFVDpgwHBrivfC85pVoTM3pyzp66hqNN6R6xWxW6ETyljS8UOer59+/w4GDVGdPig==", + "dependencies": { + "@internationalized/number": "^3.5.3", + "@internationalized/string": "^3.2.3", + "@react-aria/i18n": "^3.11.1", + "@react-stately/form": "^3.0.3", + "@react-stately/numberfield": "^3.9.3", + "@react-stately/slider": "^3.5.4", + "@react-stately/utils": "^3.10.1", + "@react-types/color": "3.0.0-beta.25", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/combobox": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/@react-stately/combobox/-/combobox-3.8.4.tgz", + "integrity": "sha512-iLVGvKRRz0TeJXZhZyK783hveHpYA6xovOSdzSD+WGYpiPXo1QrcrNoH3AE0Z2sHtorU+8nc0j58vh5PB+m2AA==", + "dependencies": { + "@react-stately/collections": "^3.10.7", + "@react-stately/form": "^3.0.3", + "@react-stately/list": "^3.10.5", + "@react-stately/overlays": "^3.6.7", + "@react-stately/select": "^3.6.4", + "@react-stately/utils": "^3.10.1", + "@react-types/combobox": "^3.11.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/data": { + "version": "3.11.4", + "resolved": "https://registry.npmjs.org/@react-stately/data/-/data-3.11.4.tgz", + "integrity": "sha512-PbnUQxeE6AznSuEWYnRmrYQ9t5z1Asx98Jtrl96EeA6Iapt9kOjTN9ySqCxtPxMKleb1NIqG3+uHU3veIqmLsg==", + "dependencies": { + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/datepicker": { + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/@react-stately/datepicker/-/datepicker-3.9.4.tgz", + "integrity": "sha512-yBdX01jn6gq4NIVvHIqdjBUPo+WN8Bujc4OnPw+ZnfA4jI0eIgq04pfZ84cp1LVXW0IB0VaCu1AlQ/kvtZjfGA==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@internationalized/string": "^3.2.3", + "@react-stately/form": "^3.0.3", + "@react-stately/overlays": "^3.6.7", + "@react-stately/utils": "^3.10.1", + "@react-types/datepicker": "^3.7.4", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/dnd": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@react-stately/dnd/-/dnd-3.3.1.tgz", + "integrity": "sha512-I/Ci5xB8hSgAXzoWYWScfMM9UK1MX/eTlARBhiSlfudewweOtNJAI+cXJgU7uiUnGjh4B4v3qDBtlAH1dWDCsw==", + "dependencies": { + "@react-stately/selection": "^3.15.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/flags": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.0.3.tgz", + "integrity": "sha512-/ha7XFA0RZTQsbzSPwu3KkbNMgbvuM0GuMTYLTBWpgBrovBNTM+QqI/PfZTdHg8PwCYF4H5Y8gjdSpdulCvJFw==", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-stately/form": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@react-stately/form/-/form-3.0.3.tgz", + "integrity": "sha512-92YYBvlHEWUGUpXgIaQ48J50jU9XrxfjYIN8BTvvhBHdD63oWgm8DzQnyT/NIAMzdLnhkg7vP+fjG8LjHeyIAg==", + "dependencies": { + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/grid": { + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/@react-stately/grid/-/grid-3.8.7.tgz", + "integrity": "sha512-he3TXCLAhF5C5z1/G4ySzcwyt7PEiWcVIupxebJQqRyFrNWemSuv+7tolnStmG8maMVIyV3P/3j4eRBbdSlOIg==", + "dependencies": { + "@react-stately/collections": "^3.10.7", + "@react-stately/selection": "^3.15.1", + "@react-types/grid": "^3.2.6", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/list": { + "version": "3.10.5", + "resolved": "https://registry.npmjs.org/@react-stately/list/-/list-3.10.5.tgz", + "integrity": "sha512-fV9plO+6QDHiewsYIhboxcDhF17GO95xepC5ki0bKXo44gr14g/LSo/BMmsaMnV+1BuGdBunB05bO4QOIaigXA==", + "dependencies": { + "@react-stately/collections": "^3.10.7", + "@react-stately/selection": "^3.15.1", + "@react-stately/utils": "^3.10.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/menu": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@react-stately/menu/-/menu-3.7.1.tgz", + "integrity": "sha512-mX1w9HHzt+xal1WIT2xGrTQsoLvDwuB2R1Er1MBABs//MsJzccycatcgV/J/28m6tO5M9iuFQQvLV+i1dCtodg==", + "dependencies": { + "@react-stately/overlays": "^3.6.7", + "@react-types/menu": "^3.9.9", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/numberfield": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@react-stately/numberfield/-/numberfield-3.9.3.tgz", + "integrity": "sha512-UlPTLSabhLEuHtgzM0PgfhtEaHy3yttbzcRb8yHNvGo4KbCHeHpTHd3QghKfTFm024Mug7+mVlWCmMtW0f5ttg==", + "dependencies": { + "@internationalized/number": "^3.5.3", + "@react-stately/form": "^3.0.3", + "@react-stately/utils": "^3.10.1", + "@react-types/numberfield": "^3.8.3", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/overlays": { + "version": "3.6.7", + "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.6.7.tgz", + "integrity": "sha512-6zp8v/iNUm6YQap0loaFx6PlvN8C0DgWHNlrlzMtMmNuvjhjR0wYXVaTfNoUZBWj25tlDM81ukXOjpRXg9rLrw==", + "dependencies": { + "@react-stately/utils": "^3.10.1", + "@react-types/overlays": "^3.8.7", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/radio": { + "version": "3.10.4", + "resolved": "https://registry.npmjs.org/@react-stately/radio/-/radio-3.10.4.tgz", + "integrity": "sha512-kCIc7tAl4L7Hu4Wt9l2jaa+MzYmAJm0qmC8G8yPMbExpWbLRu6J8Un80GZu+JxvzgDlqDyrVvyv9zFifwH/NkQ==", + "dependencies": { + "@react-stately/form": "^3.0.3", + "@react-stately/utils": "^3.10.1", + "@react-types/radio": "^3.8.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/searchfield": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@react-stately/searchfield/-/searchfield-3.5.3.tgz", + "integrity": "sha512-H0OvlgwPIFdc471ypw79MDjz3WXaVq9+THaY6JM4DIohEJNN5Dwei7O9g6r6m/GqPXJIn5TT3b74kJ2Osc00YQ==", + "dependencies": { + "@react-stately/utils": "^3.10.1", + "@react-types/searchfield": "^3.5.5", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/select": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/@react-stately/select/-/select-3.6.4.tgz", + "integrity": "sha512-whZgF1N53D0/dS8tOFdrswB0alsk5Q5620HC3z+5f2Hpi8gwgAZ8TYa+2IcmMYRiT+bxVuvEc/NirU9yPmqGbA==", + "dependencies": { + "@react-stately/form": "^3.0.3", + "@react-stately/list": "^3.10.5", + "@react-stately/overlays": "^3.6.7", + "@react-types/select": "^3.9.4", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/selection": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/@react-stately/selection/-/selection-3.15.1.tgz", + "integrity": "sha512-6TQnN9L0UY9w19B7xzb1P6mbUVBtW840Cw1SjgNXCB3NPaCf59SwqClYzoj8O2ZFzMe8F/nUJtfU1NS65/OLlw==", + "dependencies": { + "@react-stately/collections": "^3.10.7", + "@react-stately/utils": "^3.10.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/slider": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@react-stately/slider/-/slider-3.5.4.tgz", + "integrity": "sha512-Jsf7K17dr93lkNKL9ij8HUcoM1sPbq8TvmibD6DhrK9If2lje+OOL8y4n4qreUnfMT56HCAeS9wCO3fg3eMyrw==", + "dependencies": { + "@react-stately/utils": "^3.10.1", + "@react-types/shared": "^3.23.1", + "@react-types/slider": "^3.7.3", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/table": { + "version": "3.11.8", + "resolved": "https://registry.npmjs.org/@react-stately/table/-/table-3.11.8.tgz", + "integrity": "sha512-EdyRW3lT1/kAVDp5FkEIi1BQ7tvmD2YgniGdLuW/l9LADo0T+oxZqruv60qpUS6sQap+59Riaxl91ClDxrJnpg==", + "dependencies": { + "@react-stately/collections": "^3.10.7", + "@react-stately/flags": "^3.0.3", + "@react-stately/grid": "^3.8.7", + "@react-stately/selection": "^3.15.1", + "@react-stately/utils": "^3.10.1", + "@react-types/grid": "^3.2.6", + "@react-types/shared": "^3.23.1", + "@react-types/table": "^3.9.5", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/tabs": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/@react-stately/tabs/-/tabs-3.6.6.tgz", + "integrity": "sha512-sOLxorH2uqjAA+v1ppkMCc2YyjgqvSGeBDgtR/lyPSDd4CVMoTExszROX2dqG0c8il9RQvzFuufUtQWMY6PgSA==", + "dependencies": { + "@react-stately/list": "^3.10.5", + "@react-types/shared": "^3.23.1", + "@react-types/tabs": "^3.3.7", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/toast": { + "version": "3.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@react-stately/toast/-/toast-3.0.0-beta.4.tgz", + "integrity": "sha512-YzEyFNAAQqZ+7pZpRiejotxDzp5CHagN21btVGGwU2jHu/oQRR+todtB3wADAp7EF5lfAlAsLABiD9ZNWQeDTw==", + "dependencies": { + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/toggle": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.7.4.tgz", + "integrity": "sha512-CoYFe9WrhLkDP4HGDpJYQKwfiYCRBAeoBQHv+JWl5eyK61S8xSwoHsveYuEZ3bowx71zyCnNAqWRrmNOxJ4CKA==", + "dependencies": { + "@react-stately/utils": "^3.10.1", + "@react-types/checkbox": "^3.8.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/tooltip": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/@react-stately/tooltip/-/tooltip-3.4.9.tgz", + "integrity": "sha512-P7CDJsdoKarz32qFwf3VNS01lyC+63gXpDZG31pUu+EO5BeQd4WKN/AH1Beuswpr4GWzxzFc1aXQgERFGVzraA==", + "dependencies": { + "@react-stately/overlays": "^3.6.7", + "@react-types/tooltip": "^3.4.9", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/tree": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@react-stately/tree/-/tree-3.8.1.tgz", + "integrity": "sha512-LOdkkruJWch3W89h4B/bXhfr0t0t1aRfEp+IMrrwdRAl23NaPqwl5ILHs4Xu5XDHqqhg8co73pHrJwUyiTWEjw==", + "dependencies": { + "@react-stately/collections": "^3.10.7", + "@react-stately/selection": "^3.15.1", + "@react-stately/utils": "^3.10.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/utils": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.1.tgz", + "integrity": "sha512-VS/EHRyicef25zDZcM/ClpzYMC5i2YGN6uegOeQawmgfGjb02yaCX0F0zR69Pod9m2Hr3wunTbtpgVXvYbZItg==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/virtualizer": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@react-stately/virtualizer/-/virtualizer-3.7.1.tgz", + "integrity": "sha512-voHgE6EQ+oZaLv6u2umKxakvIKNkCQuUihqKACTjdslp7SJh4Mvs3oLBI0hf0JOh+rCcFIKDvQtFwy1fXFRYBA==", + "dependencies": { + "@react-aria/utils": "^3.24.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/breadcrumbs": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/@react-types/breadcrumbs/-/breadcrumbs-3.7.5.tgz", + "integrity": "sha512-lV9IDYsMiu2TgdMIjEmsOE0YWwjb3jhUNK1DCZZfq6uWuiHLgyx2EncazJBUWSjHJ4ta32j7xTuXch+8Ai6u/A==", + "dependencies": { + "@react-types/link": "^3.5.5", + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/button": { + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.9.4.tgz", + "integrity": "sha512-raeQBJUxBp0axNF74TXB8/H50GY8Q3eV6cEKMbZFP1+Dzr09Ngv0tJBeW0ewAxAguNH5DRoMUAUGIXtSXskVdA==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/calendar": { + "version": "3.4.6", + "resolved": "https://registry.npmjs.org/@react-types/calendar/-/calendar-3.4.6.tgz", + "integrity": "sha512-WSntZPwtvsIYWvBQRAPvuCn55UTJBZroTvX0vQvWykJRQnPAI20G1hMQ3dNsnAL+gLZUYxBXn66vphmjUuSYew==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/checkbox": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.8.1.tgz", + "integrity": "sha512-5/oVByPw4MbR/8QSdHCaalmyWC71H/QGgd4aduTJSaNi825o+v/hsN2/CH7Fq9atkLKsC8fvKD00Bj2VGaKriQ==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/color": { + "version": "3.0.0-beta.25", + "resolved": "https://registry.npmjs.org/@react-types/color/-/color-3.0.0-beta.25.tgz", + "integrity": "sha512-D24ASvLeSWouBwOBi4ftUe4/BhrZj5AiHV7tXwrVeMGOy9Z9jyeK65Xysq+R3ecaSONLXsgai5CQMvj13cOacA==", + "dependencies": { + "@react-types/shared": "^3.23.1", + "@react-types/slider": "^3.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/combobox": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@react-types/combobox/-/combobox-3.11.1.tgz", + "integrity": "sha512-UNc3OHt5cUt5gCTHqhQIqhaWwKCpaNciD8R7eQazmHiA9fq8ROlV+7l3gdNgdhJbTf5Bu/V5ISnN7Y1xwL3zqQ==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/datepicker": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@react-types/datepicker/-/datepicker-3.7.4.tgz", + "integrity": "sha512-ZfvgscvNzBJpYyVWg3nstJtA/VlWLwErwSkd1ivZYam859N30w8yH+4qoYLa6FzWLCFlrsRHyvtxlEM7lUAt5A==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@react-types/calendar": "^3.4.6", + "@react-types/overlays": "^3.8.7", + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/dialog": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@react-types/dialog/-/dialog-3.5.10.tgz", + "integrity": "sha512-S9ga+edOLNLZw7/zVOnZdT5T40etpzUYBXEKdFPbxyPYnERvRxJAsC1/ASuBU9fQAXMRgLZzADWV+wJoGS/X9g==", + "dependencies": { + "@react-types/overlays": "^3.8.7", + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/form": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@react-types/form/-/form-3.7.4.tgz", + "integrity": "sha512-HZojAWrb6feYnhDEOy3vBamDVAHDl0l2JQZ7aIDLHmeTAGQC3JNZcm2fLTxqLye46zz8w8l8OHgI+NdD4PHdOw==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/grid": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@react-types/grid/-/grid-3.2.6.tgz", + "integrity": "sha512-XfHenL2jEBUYrhKiPdeM24mbLRXUn79wVzzMhrNYh24nBwhsPPpxF+gjFddT3Cy8dt6tRInfT6pMEu9nsXwaHw==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/link": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/@react-types/link/-/link-3.5.5.tgz", + "integrity": "sha512-G6P5WagHDR87npN7sEuC5IIgL1GsoY4WFWKO4734i2CXRYx24G9P0Su3AX4GA3qpspz8sK1AWkaCzBMmvnunfw==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/listbox": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/@react-types/listbox/-/listbox-3.4.9.tgz", + "integrity": "sha512-S5G+WmNKUIOPZxZ4svWwWQupP3C6LmVfnf8QQmPDvwYXGzVc0WovkqUWyhhjJirFDswTXRCO9p0yaTHHIlkdwQ==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/menu": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/@react-types/menu/-/menu-3.9.9.tgz", + "integrity": "sha512-FamUaPVs1Fxr4KOMI0YcR2rYZHoN7ypGtgiEiJ11v/tEPjPPGgeKDxii0McCrdOkjheatLN1yd2jmMwYj6hTDg==", + "dependencies": { + "@react-types/overlays": "^3.8.7", + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/meter": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@react-types/meter/-/meter-3.4.1.tgz", + "integrity": "sha512-AIJV4NDFAqKH94s02c5Da4TH2qgJjfrw978zuFM0KUBFD85WRPKh7MvgWpomvUgmzqE6lMCzIdi1KPKqrRabdw==", + "dependencies": { + "@react-types/progress": "^3.5.4" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/numberfield": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/@react-types/numberfield/-/numberfield-3.8.3.tgz", + "integrity": "sha512-z5fGfVj3oh5bmkw9zDvClA1nDBSFL9affOuyk2qZ/M2SRUmykDAPCksbfcMndft0XULWKbF4s2CYbVI+E/yrUA==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/overlays": { + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.8.7.tgz", + "integrity": "sha512-zCOYvI4at2DkhVpviIClJ7bRrLXYhSg3Z3v9xymuPH3mkiuuP/dm8mUCtkyY4UhVeUTHmrQh1bzaOP00A+SSQA==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/progress": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@react-types/progress/-/progress-3.5.4.tgz", + "integrity": "sha512-JNc246sTjasPyx5Dp7/s0rp3Bz4qlu4LrZTulZlxWyb53WgBNL7axc26CCi+I20rWL9+c7JjhrRxnLl/1cLN5g==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/radio": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@react-types/radio/-/radio-3.8.1.tgz", + "integrity": "sha512-bK0gio/qj1+0Ldu/3k/s9BaOZvnnRgvFtL3u5ky479+aLG5qf1CmYed3SKz8ErZ70JkpuCSrSwSCFf0t1IHovw==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/searchfield": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/@react-types/searchfield/-/searchfield-3.5.5.tgz", + "integrity": "sha512-T/NHg12+w23TxlXMdetogLDUldk1z5dDavzbnjKrLkajLb221bp8brlR/+O6C1CtFpuJGALqYHgTasU1qkQFSA==", + "dependencies": { + "@react-types/shared": "^3.23.1", + "@react-types/textfield": "^3.9.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/select": { + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/@react-types/select/-/select-3.9.4.tgz", + "integrity": "sha512-xI7dnOW2st91fPPcv6hdtrTdcfetYiqZuuVPZ5TRobY7Q10/Zqqe/KqtOw1zFKUj9xqNJe4Ov3xP5GSdcO60Eg==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/shared": { + "version": "3.23.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.23.1.tgz", + "integrity": "sha512-5d+3HbFDxGZjhbMBeFHRQhexMFt4pUce3okyRtUVKbbedQFUrtXSBg9VszgF2RTeQDKDkMCIQDtz5ccP/Lk1gw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/slider": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@react-types/slider/-/slider-3.7.3.tgz", + "integrity": "sha512-F8qFQaD2mqug2D0XeWMmjGBikiwbdERFlhFzdvNGbypPLz3AZICBKp1ZLPWdl0DMuy03G/jy6Gl4mDobl7RT2g==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/switch": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@react-types/switch/-/switch-3.5.3.tgz", + "integrity": "sha512-Nb6+J5MrPaFa8ZNFKGMzAsen/NNzl5UG/BbC65SLGPy7O0VDa/sUpn7dcu8V2xRpRwwIN/Oso4v63bt2sgdkgA==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/table": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/@react-types/table/-/table-3.9.5.tgz", + "integrity": "sha512-fgM2j9F/UR4Anmd28CueghCgBwOZoCVyN8fjaIFPd2MN4gCwUUfANwxLav65gZk4BpwUXGoQdsW+X50L3555mg==", + "dependencies": { + "@react-types/grid": "^3.2.6", + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/tabs": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@react-types/tabs/-/tabs-3.3.7.tgz", + "integrity": "sha512-ZdLe5xOcFX6+/ni45Dl2jO0jFATpTnoSqj6kLIS/BYv8oh0n817OjJkLf+DS3CLfNjApJWrHqAk34xNh6nRnEg==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/textfield": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@react-types/textfield/-/textfield-3.9.3.tgz", + "integrity": "sha512-DoAY6cYOL0pJhgNGI1Rosni7g72GAt4OVr2ltEx2S9ARmFZ0DBvdhA9lL2nywcnKMf27PEJcKMXzXc10qaHsJw==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/tooltip": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/@react-types/tooltip/-/tooltip-3.4.9.tgz", + "integrity": "sha512-wZ+uF1+Zc43qG+cOJzioBmLUNjRa7ApdcT0LI1VvaYvH5GdfjzUJOorLX9V/vAci0XMJ50UZ+qsh79aUlw2yqg==", + "dependencies": { + "@react-types/overlays": "^3.8.7", + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@remix-run/router": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.9.0.tgz", + "integrity": "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.11.tgz", + "integrity": "sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dependencies": { + "@types/trusted-types": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", + "devOptional": true + }, + "node_modules/@types/react": { + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "devOptional": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.13.1.tgz", + "integrity": "sha512-kZqi+WZQaZfPKnsflLJQCz6Ze9FFSMfXrrIOcyargekQxG37ES7DJNpJUE9Q/X5n3yTIP/WPutVNzgknQ7biLg==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.13.1", + "@typescript-eslint/type-utils": "7.13.1", + "@typescript-eslint/utils": "7.13.1", + "@typescript-eslint/visitor-keys": "7.13.1", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.13.1.tgz", + "integrity": "sha512-1ELDPlnLvDQ5ybTSrMhRTFDfOQEOXNM+eP+3HT/Yq7ruWpciQw+Avi73pdEbA4SooCawEWo3dtYbF68gN7Ed1A==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.13.1", + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/typescript-estree": "7.13.1", + "@typescript-eslint/visitor-keys": "7.13.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.13.1.tgz", + "integrity": "sha512-adbXNVEs6GmbzaCpymHQ0MB6E4TqoiVbC0iqG3uijR8ZYfpAXMGttouQzF4Oat3P2GxDVIrg7bMI/P65LiQZdg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/visitor-keys": "7.13.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.13.1.tgz", + "integrity": "sha512-aWDbLu1s9bmgPGXSzNCxELu+0+HQOapV/y+60gPXafR8e2g1Bifxzevaa+4L2ytCWm+CHqpELq4CSoN9ELiwCg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.13.1", + "@typescript-eslint/utils": "7.13.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.13.1.tgz", + "integrity": "sha512-7K7HMcSQIAND6RBL4kDl24sG/xKM13cA85dc7JnmQXw2cBDngg7c19B++JzvJHRG3zG36n9j1i451GBzRuHchw==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.13.1.tgz", + "integrity": "sha512-uxNr51CMV7npU1BxZzYjoVz9iyjckBduFBP0S5sLlh1tXYzHzgZ3BR9SVsNed+LmwKrmnqN3Kdl5t7eZ5TS1Yw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/visitor-keys": "7.13.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.13.1.tgz", + "integrity": "sha512-h5MzFBD5a/Gh/fvNdp9pTfqJAbuQC4sCN2WzuXme71lqFJsZtLbjxfSk4r3p02WIArOF9N94pdsLiGutpDbrXQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.13.1", + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/typescript-estree": "7.13.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.13.1.tgz", + "integrity": "sha512-k/Bfne7lrP7hcb7m9zSsgcBmo+8eicqqfNAJ7uUY+jkTFpKeH2FSkWpFRtimBxgkyvqfu9jTPRbYOvud6isdXA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.13.1", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vanilla-extract/css": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@vanilla-extract/css/-/css-1.15.3.tgz", + "integrity": "sha512-mxoskDAxdQAspbkmQRxBvolUi1u1jnyy9WZGm+GeH8V2wwhEvndzl1QoK7w8JfA0WFevTxbev5d+i+xACZlPhA==", + "peer": true, + "dependencies": { + "@emotion/hash": "^0.9.0", + "@vanilla-extract/private": "^1.0.5", + "css-what": "^6.1.0", + "cssesc": "^3.0.0", + "csstype": "^3.0.7", + "dedent": "^1.5.3", + "deep-object-diff": "^1.1.9", + "deepmerge": "^4.2.2", + "media-query-parser": "^2.0.2", + "modern-ahocorasick": "^1.0.0", + "picocolors": "^1.0.0" + } + }, + "node_modules/@vanilla-extract/dynamic": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vanilla-extract/dynamic/-/dynamic-2.1.0.tgz", + "integrity": "sha512-8zl0IgBYRtgD1h+56Zu13wHTiMTJSVEa4F7RWX9vTB/5Xe2KtjoiqApy/szHPVFA56c+ex6A4GpCQjT1bKXbYw==", + "dependencies": { + "@vanilla-extract/private": "^1.0.3" + } + }, + "node_modules/@vanilla-extract/private": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@vanilla-extract/private/-/private-1.0.5.tgz", + "integrity": "sha512-6YXeOEKYTA3UV+RC8DeAjFk+/okoNz/h88R+McnzA2zpaVqTR/Ep+vszkWYlGBcMNO7vEkqbq5nT/JMMvhi+tw==" + }, + "node_modules/@vanilla-extract/recipes": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@vanilla-extract/recipes/-/recipes-0.5.3.tgz", + "integrity": "sha512-SPREq1NmaoKuvJeOV0pppOkwy3pWZUoDufsyQ6iHrbkHhAU7XQqG9o0iZSmg5JoVgDLIiOr9djQb0x9wuxig7A==", + "peerDependencies": { + "@vanilla-extract/css": "^1.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz", + "integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.24.5", + "@babel/plugin-transform-react-jsx-self": "^7.24.5", + "@babel/plugin-transform-react-jsx-source": "^7.24.1", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, + "node_modules/acorn": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.16" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001637", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001637.tgz", + "integrity": "sha512-1x0qRI1mD1o9e+7mBI7XtzFAP4XszbHaVWsMiGbSPLYekKTJF7K+FNk6AsXH4sUpc+qrsI3pVgf1Jdl/uGkuSQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", + "integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==", + "dependencies": { + "clsx": "2.0.0" + }, + "funding": { + "url": "https://joebell.co.uk" + } + }, + "node_modules/class-variance-authority/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/classix": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/classix/-/classix-2.1.17.tgz", + "integrity": "sha512-0lBXJDoUtHoYHYfHop2BHyE1SehmUZhNfm9i5f3A1qvVTYT1mGLyOJRUZ+uE9QysOtmGnNvy4rrWPja1tIfPSA==" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "peer": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "peer": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", + "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==", + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "peer": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deep-object-diff": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.9.tgz", + "integrity": "sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==", + "peer": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dompurify": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.5.tgz", + "integrity": "sha512-lwG+n5h8QNpxtyrJW/gJWckL+1/DQiYMX8f7t8Z2AZTPw1esVrqjI63i7Zc2Gz0aKzLVMYC1V1PL/ky+aY/NgA==" + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.812", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.812.tgz", + "integrity": "sha512-7L8fC2Ey/b6SePDFKR2zHAy4mbdp1/38Yk5TsARO66W3hC5KEaeKMMHoxwtuH+jcu2AYLSn9QX04i95t6Fl1Hg==", + "dev": true + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.7.tgz", + "integrity": "sha512-yrj+KInFmwuQS2UQcg1SF83ha1tuHC1jMQbRNyuWtlEzzKRDgAl7L4Yp4NlDUZTZNlWvHEzOtJhMi40R7JxcSw==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/framer-motion": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.2.4.tgz", + "integrity": "sha512-D+EXd0lspaZijv3BJhAcSsyGz+gnvoEdnf+QWkPZdhoFzbeX/2skrH9XSVFb0osgUnCajW8x1frjhLuKwa/Reg==", + "dependencies": { + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/intl-messageformat": { + "version": "10.5.14", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.14.tgz", + "integrity": "sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/fast-memoize": "2.2.0", + "@formatjs/icu-messageformat-parser": "2.7.8", + "tslib": "^2.4.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isomorphic-dompurify": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-2.12.0.tgz", + "integrity": "sha512-jJm6VgJ9toBLqNUHuLudn+2Q3NBBaoPbsh5SzzO2dp9Zq9+p6fEg4Ffuq9RZsofb8OnqE6FJVVq3MRDLlmBHpA==", + "dependencies": { + "@types/dompurify": "^3.0.5", + "dompurify": "^3.1.5", + "jsdom": "^24.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.0.tgz", + "integrity": "sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==", + "dependencies": { + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.4", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.10", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.4", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.17.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/launchdarkly-js-client-sdk": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/launchdarkly-js-client-sdk/-/launchdarkly-js-client-sdk-3.4.0.tgz", + "integrity": "sha512-3v1jKy1RECT0SG/3SGlyRO32vweoNxvWiJuIChRh/Zhk98cHpANuwameXVhwJ4WEDNZJTur73baaKAyatSP46A==", + "dependencies": { + "escape-string-regexp": "^4.0.0", + "launchdarkly-js-sdk-common": "5.3.0" + } + }, + "node_modules/launchdarkly-js-client-sdk/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/launchdarkly-js-sdk-common": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/launchdarkly-js-sdk-common/-/launchdarkly-js-sdk-common-5.3.0.tgz", + "integrity": "sha512-NI5wFF8qhjtpRb4KelGdnwNIOf/boLlbLiseV7uf1jxSeoM/L30DAz87RT8VhYCSGCo4LedGILQFednI/MKFkA==", + "dependencies": { + "base64-js": "^1.3.0", + "fast-deep-equal": "^2.0.1", + "uuid": "^8.0.0" + } + }, + "node_modules/launchdarkly-js-sdk-common/node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/marked": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-13.0.1.tgz", + "integrity": "sha512-7kBohS6GrZKvCsNXZyVVXSW7/hGBHe49ng99YPkDCckSUrrG7MSFLCexsRxptzOmyW2eT5dySh4Md1V6my52fA==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/media-query-parser": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/media-query-parser/-/media-query-parser-2.0.2.tgz", + "integrity": "sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w==", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.12.5" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/modern-ahocorasick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modern-ahocorasick/-/modern-ahocorasick-1.0.1.tgz", + "integrity": "sha512-yoe+JbhTClckZ67b2itRtistFKf8yPYelHLc7e5xAwtNAXxM6wJTUx2C7QeVSJFDzKT7bCIFyBVybPMKvmB9AA==", + "peer": true + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/nwsapi": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz", + "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/rainbow-sprinkles": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/rainbow-sprinkles/-/rainbow-sprinkles-0.17.0.tgz", + "integrity": "sha512-ok3NrylQ0szvJtuBYaB/w09L9zOvvqcSQrvycT2A5XJxQNvwvkeADvTqQWGOQ3b6MkO8UmYccBPt8g8vVvxM9A==", + "peerDependencies": { + "@vanilla-extract/css": "^1", + "@vanilla-extract/dynamic": "^2" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-aria": { + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.33.1.tgz", + "integrity": "sha512-hFC3K/UA+90Krlx2IgRTgzFbC6FSPi4pUwHT+STperPLK+cTEHkI+3Lu0YYwQSBatkgxnIv9+GtFuVbps2kROw==", + "dependencies": { + "@internationalized/string": "^3.2.3", + "@react-aria/breadcrumbs": "^3.5.13", + "@react-aria/button": "^3.9.5", + "@react-aria/calendar": "^3.5.8", + "@react-aria/checkbox": "^3.14.3", + "@react-aria/combobox": "^3.9.1", + "@react-aria/datepicker": "^3.10.1", + "@react-aria/dialog": "^3.5.14", + "@react-aria/dnd": "^3.6.1", + "@react-aria/focus": "^3.17.1", + "@react-aria/gridlist": "^3.8.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/label": "^3.7.8", + "@react-aria/link": "^3.7.1", + "@react-aria/listbox": "^3.12.1", + "@react-aria/menu": "^3.14.1", + "@react-aria/meter": "^3.4.13", + "@react-aria/numberfield": "^3.11.3", + "@react-aria/overlays": "^3.22.1", + "@react-aria/progress": "^3.4.13", + "@react-aria/radio": "^3.10.4", + "@react-aria/searchfield": "^3.7.5", + "@react-aria/select": "^3.14.5", + "@react-aria/selection": "^3.18.1", + "@react-aria/separator": "^3.3.13", + "@react-aria/slider": "^3.7.8", + "@react-aria/ssr": "^3.9.4", + "@react-aria/switch": "^3.6.4", + "@react-aria/table": "^3.14.1", + "@react-aria/tabs": "^3.9.1", + "@react-aria/tag": "^3.4.1", + "@react-aria/textfield": "^3.14.5", + "@react-aria/tooltip": "^3.7.4", + "@react-aria/utils": "^3.24.1", + "@react-aria/visually-hidden": "^3.8.12", + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/react-aria-components": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-aria-components/-/react-aria-components-1.2.1.tgz", + "integrity": "sha512-iGIdDjbTyLLn0/tGUyBQxxu+E1bw4/H4AU89d0cRcu8yIdw6MXG29YElmRHn0ugiyrERrk/YQALihstnns5kRQ==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@internationalized/string": "^3.2.3", + "@react-aria/color": "3.0.0-beta.33", + "@react-aria/focus": "^3.17.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/menu": "^3.14.1", + "@react-aria/toolbar": "3.0.0-beta.5", + "@react-aria/tree": "3.0.0-alpha.1", + "@react-aria/utils": "^3.24.1", + "@react-stately/color": "^3.6.1", + "@react-stately/menu": "^3.7.1", + "@react-stately/table": "^3.11.8", + "@react-stately/utils": "^3.10.1", + "@react-types/color": "3.0.0-beta.25", + "@react-types/form": "^3.7.4", + "@react-types/grid": "^3.2.6", + "@react-types/shared": "^3.23.1", + "@react-types/table": "^3.9.5", + "@swc/helpers": "^0.5.0", + "client-only": "^0.0.1", + "react-aria": "^3.33.1", + "react-stately": "^3.31.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.16.0.tgz", + "integrity": "sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA==", + "dependencies": { + "@remix-run/router": "1.9.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.16.0.tgz", + "integrity": "sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg==", + "dependencies": { + "@remix-run/router": "1.9.0", + "react-router": "6.16.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-stately": { + "version": "3.31.1", + "resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.31.1.tgz", + "integrity": "sha512-wuq673NHkYSdoceGryjtMJJvB9iQgyDkQDsnTN0t2v91pXjGDsN/EcOvnUrxXSBtY9eLdIw74R54z9GX5cJNEg==", + "dependencies": { + "@react-stately/calendar": "^3.5.1", + "@react-stately/checkbox": "^3.6.5", + "@react-stately/collections": "^3.10.7", + "@react-stately/combobox": "^3.8.4", + "@react-stately/data": "^3.11.4", + "@react-stately/datepicker": "^3.9.4", + "@react-stately/dnd": "^3.3.1", + "@react-stately/form": "^3.0.3", + "@react-stately/list": "^3.10.5", + "@react-stately/menu": "^3.7.1", + "@react-stately/numberfield": "^3.9.3", + "@react-stately/overlays": "^3.6.7", + "@react-stately/radio": "^3.10.4", + "@react-stately/searchfield": "^3.5.3", + "@react-stately/select": "^3.6.4", + "@react-stately/selection": "^3.15.1", + "@react-stately/slider": "^3.5.4", + "@react-stately/table": "^3.11.8", + "@react-stately/tabs": "^3.6.6", + "@react-stately/toggle": "^3.7.4", + "@react-stately/tooltip": "^3.4.9", + "@react-stately/tree": "^3.8.1", + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/react-virtual": { + "version": "2.10.4", + "resolved": "https://registry.npmjs.org/react-virtual/-/react-virtual-2.10.4.tgz", + "integrity": "sha512-Ir6+oPQZTVHfa6+JL9M7cvMILstFZH/H3jqeYeKI4MSUX+rIruVwFC6nGVXw9wqAw8L0Kg2KvfXxI85OvYQdpQ==", + "funding": [ + "https://github.com/sponsors/tannerlinsley" + ], + "dependencies": { + "@reach/observe-rect": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.6.3 || ^17.0.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vite": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.1.tgz", + "integrity": "sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-singlefile": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/vite-plugin-singlefile/-/vite-plugin-singlefile-2.0.2.tgz", + "integrity": "sha512-Z2ou6HcvED5CF0hM+vcFSaFa+klyS8RyyLxW0PbMRLnMbvzTI6ueWyxdYNFhpuXZgz/aj6+E/dHFTdEcw6gb9w==", + "dev": true, + "dependencies": { + "micromatch": "^4.0.7" + }, + "engines": { + "node": ">18.0.0" + }, + "peerDependencies": { + "rollup": "^4.18.0", + "vite": "^5.3.1" + } + }, + "node_modules/vite-plugin-svgr": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.2.0.tgz", + "integrity": "sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.5", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" + }, + "peerDependencies": { + "vite": "^2.6.0 || 3 || 4 || 5" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/internal/dev_server/ui/package.json b/internal/dev_server/ui/package.json new file mode 100644 index 00000000..dac36ec8 --- /dev/null +++ b/internal/dev_server/ui/package.json @@ -0,0 +1,37 @@ +{ + "name": "ldcli-dev-server-ui", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "prettier:write": "prettier . --write", + "preview": "vite preview" + }, + "dependencies": { + "@launchpad-ui/components": "0.2.12", + "@launchpad-ui/core": "0.49.22", + "@launchpad-ui/icons": "0.18.4", + "@launchpad-ui/tokens": "0.9.12", + "launchdarkly-js-client-sdk": "3.4.0", + "react": "18.3.1", + "react-dom": "18.3.1" + }, + "devDependencies": { + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", + "@typescript-eslint/eslint-plugin": "7.13.1", + "@typescript-eslint/parser": "7.13.1", + "@vitejs/plugin-react": "4.3.1", + "eslint": "8.57.0", + "eslint-plugin-react-hooks": "4.6.2", + "eslint-plugin-react-refresh": "0.4.7", + "prettier": "3.3.2", + "typescript": "5.2.2", + "vite": "5.3.1", + "vite-plugin-singlefile": "2.0.2", + "vite-plugin-svgr": "^4.2.0" + } +} diff --git a/internal/dev_server/ui/src/App.css b/internal/dev_server/ui/src/App.css new file mode 100644 index 00000000..7bd331de --- /dev/null +++ b/internal/dev_server/ui/src/App.css @@ -0,0 +1,61 @@ +@import url('../node_modules/@launchpad-ui/tokens/dist/index.css'); +@import url('../node_modules/@launchpad-ui/tokens/dist/media-queries.css'); +@import url('../node_modules/@launchpad-ui/tokens/dist/themes.css'); + +html, +body, +#root { + height: 100%; + width: 100%; + box-sizing: border-box; +} + +#root { + padding: 2rem; +} + +.container { + max-width: 40rem; + margin: 0 auto; +} + +.only-show-overrides-label { + display: flex; + flex-direction: row; + align-items: center; +} + +ul.flags-list { + margin: 0; + padding: 0; +} + +ul.flags-list li { + list-style-type: none; + padding: 0.5rem 0; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} + +ul.flags-list li .flag-value { + overflow-x: overlay; + white-space: nowrap; + display: flex; + flex-direction: row; + align-items: center; + flex-shrink: 1; +} + +code { + background-color: var(--lp-color-bg-ui-secondary); + border: 1px solid var(--lp-color-border-ui-primary); + border-radius: 2px; + font-size: 87.5%; + padding: 0.125rem 0.25rem; +} + +code.has-override { + color: var(--lp-color-pink-600); +} diff --git a/internal/dev_server/ui/src/App.tsx b/internal/dev_server/ui/src/App.tsx new file mode 100644 index 00000000..ce1ba773 --- /dev/null +++ b/internal/dev_server/ui/src/App.tsx @@ -0,0 +1,324 @@ +import { LDFlagSet, LDFlagValue } from 'launchdarkly-js-client-sdk'; +import { + Button, + Checkbox, + IconButton, + Label, + Switch, + Modal, + ModalOverlay, + DialogTrigger, + Dialog, +} from '@launchpad-ui/components'; +import { Box, InlineEdit, TextField } from '@launchpad-ui/core'; +import Theme from '@launchpad-ui/tokens'; +import './App.css'; +import { useEffect, useRef, useState } from 'react'; +import { Icon } from '@launchpad-ui/icons'; + +const API_BASE = import.meta.env.PROD ? '' : '/api'; +const apiRoute = (pathname: string) => `${API_BASE}${pathname}`; + +function App() { + const [flags, setFlags] = useState(null); + const [overrides, setOverrides] = useState | null>(null); + const [onlyShowOverrides, setOnlyShowOverrides] = useState(false); + const overridesPresent = overrides && Object.keys(overrides).length > 0; + const textAreaRef = useRef(null); + + const updateOverride = (flagKey: string, overrideValue: LDFlagValue) => { + fetch(apiRoute(`/dev/projects/default/overrides/${flagKey}`), { + method: 'PUT', + body: JSON.stringify(overrideValue), + }) + .then(async (res) => { + if (!res.ok) { + return; // todo + } + + const updatedOverrides = { + ...overrides, + ...{ + [flagKey]: { + value: overrideValue, + }, + }, + }; + + setOverrides(updatedOverrides); + }) + .catch((_e) => { + // todo + }); + }; + + const removeOverride = (flagKey: string, updateState: boolean = true) => { + return fetch(apiRoute(`/dev/projects/default/overrides/${flagKey}`), { + method: 'DELETE', + }) + .then((res) => { + // todo: clean this up. + // + // In the remove-all-override case, we need to fan out and make the + // request for every override, so we don't want to be interleaving + // local state updates. Expect the consumer to update the local state + // when all requests are done + if (res.ok && updateState) { + const updatedOverrides = { ...overrides }; + delete updatedOverrides[flagKey]; + + setOverrides(updatedOverrides); + + if (Object.keys(updatedOverrides).length === 0) + setOnlyShowOverrides(false); + } + }) + .catch((_e) => { + // todo + }); + }; + + // Fetch flags / overrides on mount + useEffect(() => { + fetch(apiRoute('/dev/projects/default?expand=overrides')) + .then(async (res) => { + if (!res.ok) { + return; // todo + } + + const json = await res.json(); + const { flagsState: flags, overrides } = json; + const sortedFlags = Object.keys(flags) + .sort((a, b) => a.localeCompare(b)) + .reduce>((accum, flagKey) => { + accum[flagKey] = flags[flagKey]; + + return accum; + }, {}); + + setFlags(sortedFlags); + setOverrides(overrides); + }) + .catch((_e) => { + // todo + }); + }, []); + + if (!flags) { + return null; + } + + return ( + <> +
+ + + + +
    + {Object.entries(flags).map(([flagKey, { value: flagValue }]) => { + const overrideValue = overrides?.[flagKey]?.value; + const hasOverride = overrideValue !== undefined; + let valueNode; + + if (onlyShowOverrides && !hasOverride) { + return null; + } + + switch (typeof flagValue) { + case 'boolean': + valueNode = ( + { + updateOverride(flagKey, newValue); + }} + /> + ); + break; + case 'number': + valueNode = ( + { + updateOverride(flagKey, Number(e.target.value)); + }} + /> + ); + break; + case 'string': + valueNode = ( + { + updateOverride(flagKey, newValue); + }} + renderInput={} + > + {hasOverride ? overrideValue : flagValue} + + ); + break; + default: + valueNode = ( + + + + + + {({ close }) => ( +
    { + let newVal; + + try { + newVal = JSON.parse( + textAreaRef?.current?.value || '', + ); + } catch (err) { + window.alert('Invalid JSON formatting'); + return; + } + + updateOverride(flagKey, newVal); + }} + > +