From bdf21aadbf191ea6e24e0111245246a47e8be31b Mon Sep 17 00:00:00 2001 From: pk910 Date: Sat, 31 Jan 2026 18:27:15 +0100 Subject: [PATCH 01/32] refactor package structure --- Makefile | 6 +- cmd/root.go | 8 +- cmd/tasks.go | 2 +- cmd/validate.go | 4 +- pkg/{coordinator => assertoor}/config.go | 16 +- pkg/{coordinator => assertoor}/coordinator.go | 24 +- pkg/{coordinator => assertoor}/init.go | 2 +- .../testregistry.go | 12 +- pkg/{coordinator => assertoor}/testrunner.go | 6 +- pkg/{coordinator => }/buildinfo/buildinfo.go | 0 pkg/{coordinator => }/clients/clients.go | 4 +- .../clients/consensus/block.go | 0 .../clients/consensus/block_utils.go | 0 .../clients/consensus/blockcache.go | 0 .../clients/consensus/chainspec.go | 0 .../clients/consensus/checkpoint.go | 0 .../clients/consensus/client.go | 2 +- .../clients/consensus/clientlogic.go | 2 +- .../clients/consensus/clienttype.go | 0 .../clients/consensus/forks.go | 0 .../clients/consensus/pool.go | 0 .../clients/consensus/rpc/beaconapi.go | 0 .../clients/consensus/rpc/beaconstream.go | 2 +- .../consensus/rpc/eventstream/eventstream.go | 0 .../clients/consensus/rpc/syncstatus.go | 0 .../clients/consensus/subscriptions.go | 0 .../clients/execution/block.go | 0 .../clients/execution/blockcache.go | 2 +- .../clients/execution/client.go | 2 +- .../clients/execution/clientlogic.go | 0 .../clients/execution/clienttype.go | 0 .../clients/execution/forks.go | 0 .../clients/execution/pool.go | 0 .../clients/execution/rpc/chainspec.go | 0 .../clients/execution/rpc/ethconfig.go | 0 .../clients/execution/rpc/executionapi.go | 0 .../clients/execution/rpc/syncstatus.go | 0 .../clients/execution/subscriptions.go | 0 pkg/coordinator/tasks/tasks.go | 119 -------- pkg/{coordinator => }/db/assertoor_state.go | 0 pkg/{coordinator => }/db/common.go | 0 .../db/schema/pgsql/20240913135112_init.sql | 0 .../pgsql/20250114224233_task_summaries.sql | 0 .../db/schema/sqlite/20240913135112_init.sql | 0 .../sqlite/20250114224233_task_summaries.sql | 0 pkg/{coordinator => }/db/task_logs.go | 0 pkg/{coordinator => }/db/task_results.go | 0 pkg/{coordinator => }/db/task_states.go | 0 pkg/{coordinator => }/db/test_configs.go | 0 pkg/{coordinator => }/db/test_runs.go | 0 pkg/{coordinator => }/helper/bigint.go | 0 pkg/{coordinator => }/helper/duration.go | 0 pkg/{coordinator => }/helper/rawmessage.go | 0 pkg/{coordinator => }/logger/dbreader.go | 2 +- pkg/{coordinator => }/logger/dbwriter.go | 2 +- pkg/{coordinator => }/logger/logreader.go | 2 +- pkg/{coordinator => }/logger/logscope.go | 2 +- pkg/{coordinator => }/logger/membuf.go | 2 +- pkg/{coordinator => }/names/config.go | 0 pkg/{coordinator => }/names/validatornames.go | 0 pkg/{coordinator => }/scheduler/options.go | 4 +- pkg/{coordinator => }/scheduler/scheduler.go | 2 +- pkg/{coordinator => }/scheduler/services.go | 10 +- .../scheduler/task_execution.go | 2 +- pkg/{coordinator => }/scheduler/task_state.go | 10 +- .../tasks/check_clients_are_healthy/README.md | 0 .../tasks/check_clients_are_healthy/config.go | 2 +- .../tasks/check_clients_are_healthy/task.go | 10 +- .../README.md | 0 .../config.go | 0 .../check_consensus_attestation_stats/task.go | 4 +- .../check_consensus_block_proposals/README.md | 0 .../check_consensus_block_proposals/config.go | 0 .../check_consensus_block_proposals/task.go | 6 +- .../tasks/check_consensus_finality/README.md | 0 .../tasks/check_consensus_finality/config.go | 0 .../tasks/check_consensus_finality/task.go | 2 +- .../tasks/check_consensus_forks/README.md | 0 .../tasks/check_consensus_forks/config.go | 0 .../tasks/check_consensus_forks/task.go | 4 +- .../tasks/check_consensus_identity/README.md | 0 .../tasks/check_consensus_identity/config.go | 2 +- .../tasks/check_consensus_identity/task.go | 6 +- .../check_consensus_proposer_duty/README.md | 0 .../check_consensus_proposer_duty/config.go | 0 .../check_consensus_proposer_duty/task.go | 4 +- .../tasks/check_consensus_reorgs/README.md | 0 .../tasks/check_consensus_reorgs/config.go | 0 .../tasks/check_consensus_reorgs/task.go | 4 +- .../check_consensus_slot_range/README.md | 0 .../check_consensus_slot_range/config.go | 0 .../tasks/check_consensus_slot_range/task.go | 2 +- .../check_consensus_sync_status/README.md | 0 .../check_consensus_sync_status/config.go | 2 +- .../tasks/check_consensus_sync_status/task.go | 8 +- .../README.md | 0 .../config.go | 0 .../check_consensus_validator_status/task.go | 4 +- .../tasks/check_eth_call/README.md | 0 .../tasks/check_eth_call/config.go | 0 .../tasks/check_eth_call/task.go | 4 +- .../tasks/check_eth_config/README.md | 0 .../tasks/check_eth_config/config.go | 0 .../tasks/check_eth_config/task.go | 4 +- .../check_execution_sync_status/README.md | 0 .../check_execution_sync_status/config.go | 2 +- .../tasks/check_execution_sync_status/task.go | 8 +- .../tasks/generate_attestations/README.md | 0 .../tasks/generate_attestations/config.go | 0 .../tasks/generate_attestations/task.go | 6 +- .../generate_blob_transactions/README.md | 0 .../generate_blob_transactions/config.go | 0 .../tasks/generate_blob_transactions/task.go | 8 +- .../tasks/generate_bls_changes/README.md | 0 .../tasks/generate_bls_changes/config.go | 0 .../tasks/generate_bls_changes/task.go | 6 +- .../tasks/generate_child_wallet/README.md | 0 .../tasks/generate_child_wallet/config.go | 0 .../tasks/generate_child_wallet/task.go | 6 +- .../tasks/generate_consolidations/README.md | 0 .../tasks/generate_consolidations/config.go | 0 .../tasks/generate_consolidations/task.go | 8 +- .../tasks/generate_deposits/README.md | 0 .../tasks/generate_deposits/config.go | 0 .../deposit_contract/DepositContract.abi | 0 .../deposit_contract/DepositContract.sol | 0 .../deposit_contract/deposit_contract.go | 0 .../tasks/generate_deposits/task.go | 10 +- .../tasks/generate_eoa_transactions/README.md | 0 .../tasks/generate_eoa_transactions/config.go | 0 .../tasks/generate_eoa_transactions/task.go | 6 +- .../tasks/generate_exits/README.md | 0 .../tasks/generate_exits/config.go | 0 .../tasks/generate_exits/task.go | 4 +- .../tasks/generate_slashings/README.md | 0 .../tasks/generate_slashings/config.go | 0 .../tasks/generate_slashings/task.go | 4 +- .../tasks/generate_transaction/README.md | 0 .../tasks/generate_transaction/config.go | 2 +- .../tasks/generate_transaction/task.go | 10 +- .../generate_withdrawal_requests/README.md | 0 .../generate_withdrawal_requests/config.go | 0 .../generate_withdrawal_requests/task.go | 8 +- .../tasks/get_consensus_specs/README.md | 0 .../tasks/get_consensus_specs/config.go | 0 .../tasks/get_consensus_specs/task.go | 2 +- .../tasks/get_consensus_validators/README.md | 0 .../tasks/get_consensus_validators/config.go | 0 .../tasks/get_consensus_validators/task.go | 4 +- .../tasks/get_execution_block/README.md | 0 .../tasks/get_execution_block/config.go | 0 .../tasks/get_execution_block/task.go | 4 +- .../tasks/get_pubkeys_from_mnemonic/README.md | 0 .../tasks/get_pubkeys_from_mnemonic/config.go | 0 .../tasks/get_pubkeys_from_mnemonic/task.go | 2 +- .../tasks/get_random_mnemonic/README.md | 0 .../tasks/get_random_mnemonic/config.go | 0 .../tasks/get_random_mnemonic/task.go | 2 +- .../tasks/get_wallet_details/README.md | 0 .../tasks/get_wallet_details/config.go | 0 .../tasks/get_wallet_details/task.go | 4 +- pkg/{coordinator => }/tasks/info.go | 0 .../tasks/run_command/README.md | 0 .../tasks/run_command/config.go | 0 .../tasks/run_command/task.go | 2 +- .../tasks/run_external_tasks/README.md | 0 .../tasks/run_external_tasks/config.go | 0 .../tasks/run_external_tasks/task.go | 4 +- .../tasks/run_shell/README.md | 0 .../tasks/run_shell/config.go | 0 .../tasks/run_shell/result_file.go | 0 pkg/{coordinator => }/tasks/run_shell/task.go | 4 +- .../tasks/run_task_background/README.md | 0 .../tasks/run_task_background/config.go | 2 +- .../tasks/run_task_background/task.go | 4 +- .../tasks/run_task_matrix/README.md | 0 .../tasks/run_task_matrix/config.go | 2 +- .../tasks/run_task_matrix/task.go | 4 +- .../tasks/run_task_options/README.md | 0 .../tasks/run_task_options/config.go | 2 +- .../tasks/run_task_options/task.go | 4 +- .../tasks/run_tasks/README.md | 0 .../tasks/run_tasks/config.go | 2 +- pkg/{coordinator => }/tasks/run_tasks/task.go | 2 +- .../tasks/run_tasks_concurrent/README.md | 0 .../tasks/run_tasks_concurrent/config.go | 2 +- .../tasks/run_tasks_concurrent/task.go | 4 +- pkg/{coordinator => }/tasks/sleep/README.md | 0 pkg/{coordinator => }/tasks/sleep/config.go | 2 +- pkg/{coordinator => }/tasks/sleep/task.go | 2 +- pkg/tasks/tasks.go | 119 ++++++++ pkg/{coordinator => }/test/dbtest.go | 10 +- pkg/{coordinator => }/test/descriptor.go | 2 +- pkg/{coordinator => }/test/test.go | 12 +- pkg/{coordinator => }/types/coordinator.go | 10 +- pkg/{coordinator => }/types/scheduler.go | 10 +- pkg/{coordinator => }/types/task.go | 4 +- pkg/{coordinator => }/types/test.go | 2 +- pkg/{coordinator => }/types/vars.go | 0 pkg/{coordinator => }/vars/scope_filter.go | 2 +- pkg/{coordinator => }/vars/utils.go | 0 pkg/{coordinator => }/vars/variables.go | 2 +- .../wallet/blobtx/blob_encode.go | 0 pkg/{coordinator => }/wallet/blobtx/blobtx.go | 0 pkg/{coordinator => }/wallet/manager.go | 2 +- pkg/{coordinator => }/wallet/wallet.go | 2 +- pkg/{coordinator => }/wallet/walletpool.go | 0 pkg/{coordinator => }/web/api/constants.go | 0 pkg/{coordinator => }/web/api/docs/docs.go | 258 +++++++++--------- .../web/api/docs/swagger.json | 258 +++++++++--------- .../web/api/docs/swagger.yaml | 236 ++++++++-------- .../web/api/get_task_details_api.go | 2 +- .../web/api/get_task_result_api.go | 4 +- pkg/{coordinator => }/web/api/get_test_api.go | 2 +- .../web/api/get_test_run_api.go | 4 +- .../web/api/get_test_run_details_api.go | 2 +- .../web/api/get_test_run_status_api.go | 0 .../web/api/get_test_runs_api.go | 2 +- .../web/api/get_tests_api.go | 0 pkg/{coordinator => }/web/api/handler.go | 2 +- .../web/api/post_test_run_api.go | 2 +- .../web/api/post_test_run_cancel_api.go | 0 .../web/api/post_test_runs_delete_api.go | 0 .../web/api/post_tests_delete_api.go | 0 .../web/api/post_tests_register_api.go | 4 +- .../api/post_tests_register_external_api.go | 4 +- pkg/{coordinator => }/web/handlers/clients.go | 6 +- pkg/{coordinator => }/web/handlers/handler.go | 10 +- pkg/{coordinator => }/web/handlers/index.go | 2 +- pkg/{coordinator => }/web/handlers/logs.go | 0 .../web/handlers/registry.go | 4 +- pkg/{coordinator => }/web/handlers/sidebar.go | 2 +- pkg/{coordinator => }/web/handlers/test.go | 2 +- .../web/handlers/test_run.go | 6 +- pkg/{coordinator => }/web/server.go | 10 +- .../web/static/css/bootstrap.min.css | 0 .../web/static/css/bootstrap.min.css.map | 0 .../web/static/css/fontawesome-all.min.css | 0 .../web/static/css/fontawesome.min.css | 0 .../web/static/css/layout.css | 0 pkg/{coordinator => }/web/static/embed.go | 0 pkg/{coordinator => }/web/static/favicon.ico | Bin .../web/static/js/ace-1.5.0/ace.js | 0 .../web/static/js/ace-1.5.0/mode-yaml.js | 0 .../web/static/js/assertoor.js | 0 .../web/static/js/bootstrap.bundle.min.js | 0 .../web/static/js/bootstrap.bundle.min.js.map | 0 .../web/static/js/clipboard.min.js | 0 .../web/static/js/color-modes.js | 0 .../web/static/js/jquery.min.js | 0 .../web/static/js/yaml-0.3.0.min.js | 0 .../web/static/webfonts/fa-brands-400.ttf | Bin .../web/static/webfonts/fa-brands-400.woff2 | Bin .../web/static/webfonts/fa-regular-400.ttf | Bin .../web/static/webfonts/fa-regular-400.woff2 | Bin .../web/static/webfonts/fa-solid-900.ttf | Bin .../web/static/webfonts/fa-solid-900.woff2 | Bin .../static/webfonts/fa-v4compatibility.ttf | Bin .../static/webfonts/fa-v4compatibility.woff2 | Bin .../web/templates/_layout/404.html | 0 .../web/templates/_layout/500.html | 0 .../web/templates/_layout/blank.html | 0 .../web/templates/_layout/footer.html | 0 .../web/templates/_layout/header.html | 0 .../web/templates/_layout/layout.html | 0 .../web/templates/clients/clients.html | 0 .../web/templates/index/index.html | 0 .../web/templates/registry/registry.html | 0 .../web/templates/sidebar/sidebar.html | 0 .../web/templates/templates.go | 0 .../web/templates/test/test.html | 0 .../web/templates/test/test_runs.html | 0 .../web/templates/test_run/test_run.html | 0 pkg/{coordinator => }/web/types/config.go | 0 .../web/utils/templateFucs.go | 0 275 files changed, 741 insertions(+), 725 deletions(-) rename pkg/{coordinator => assertoor}/config.go (92%) rename pkg/{coordinator => assertoor}/coordinator.go (94%) rename pkg/{coordinator => assertoor}/init.go (90%) rename pkg/{coordinator => assertoor}/testregistry.go (96%) rename pkg/{coordinator => assertoor}/testrunner.go (98%) rename pkg/{coordinator => }/buildinfo/buildinfo.go (100%) rename pkg/{coordinator => }/clients/clients.go (97%) rename pkg/{coordinator => }/clients/consensus/block.go (100%) rename pkg/{coordinator => }/clients/consensus/block_utils.go (100%) rename pkg/{coordinator => }/clients/consensus/blockcache.go (100%) rename pkg/{coordinator => }/clients/consensus/chainspec.go (100%) rename pkg/{coordinator => }/clients/consensus/checkpoint.go (100%) rename pkg/{coordinator => }/clients/consensus/client.go (97%) rename pkg/{coordinator => }/clients/consensus/clientlogic.go (99%) rename pkg/{coordinator => }/clients/consensus/clienttype.go (100%) rename pkg/{coordinator => }/clients/consensus/forks.go (100%) rename pkg/{coordinator => }/clients/consensus/pool.go (100%) rename pkg/{coordinator => }/clients/consensus/rpc/beaconapi.go (100%) rename pkg/{coordinator => }/clients/consensus/rpc/beaconstream.go (98%) rename pkg/{coordinator => }/clients/consensus/rpc/eventstream/eventstream.go (100%) rename pkg/{coordinator => }/clients/consensus/rpc/syncstatus.go (100%) rename pkg/{coordinator => }/clients/consensus/subscriptions.go (100%) rename pkg/{coordinator => }/clients/execution/block.go (100%) rename pkg/{coordinator => }/clients/execution/blockcache.go (98%) rename pkg/{coordinator => }/clients/execution/client.go (97%) rename pkg/{coordinator => }/clients/execution/clientlogic.go (100%) rename pkg/{coordinator => }/clients/execution/clienttype.go (100%) rename pkg/{coordinator => }/clients/execution/forks.go (100%) rename pkg/{coordinator => }/clients/execution/pool.go (100%) rename pkg/{coordinator => }/clients/execution/rpc/chainspec.go (100%) rename pkg/{coordinator => }/clients/execution/rpc/ethconfig.go (100%) rename pkg/{coordinator => }/clients/execution/rpc/executionapi.go (100%) rename pkg/{coordinator => }/clients/execution/rpc/syncstatus.go (100%) rename pkg/{coordinator => }/clients/execution/subscriptions.go (100%) delete mode 100644 pkg/coordinator/tasks/tasks.go rename pkg/{coordinator => }/db/assertoor_state.go (100%) rename pkg/{coordinator => }/db/common.go (100%) rename pkg/{coordinator => }/db/schema/pgsql/20240913135112_init.sql (100%) rename pkg/{coordinator => }/db/schema/pgsql/20250114224233_task_summaries.sql (100%) rename pkg/{coordinator => }/db/schema/sqlite/20240913135112_init.sql (100%) rename pkg/{coordinator => }/db/schema/sqlite/20250114224233_task_summaries.sql (100%) rename pkg/{coordinator => }/db/task_logs.go (100%) rename pkg/{coordinator => }/db/task_results.go (100%) rename pkg/{coordinator => }/db/task_states.go (100%) rename pkg/{coordinator => }/db/test_configs.go (100%) rename pkg/{coordinator => }/db/test_runs.go (100%) rename pkg/{coordinator => }/helper/bigint.go (100%) rename pkg/{coordinator => }/helper/duration.go (100%) rename pkg/{coordinator => }/helper/rawmessage.go (100%) rename pkg/{coordinator => }/logger/dbreader.go (92%) rename pkg/{coordinator => }/logger/dbwriter.go (98%) rename pkg/{coordinator => }/logger/logreader.go (66%) rename pkg/{coordinator => }/logger/logscope.go (97%) rename pkg/{coordinator => }/logger/membuf.go (97%) rename pkg/{coordinator => }/names/config.go (100%) rename pkg/{coordinator => }/names/validatornames.go (100%) rename pkg/{coordinator => }/scheduler/options.go (83%) rename pkg/{coordinator => }/scheduler/scheduler.go (98%) rename pkg/{coordinator => }/scheduler/services.go (75%) rename pkg/{coordinator => }/scheduler/task_execution.go (98%) rename pkg/{coordinator => }/scheduler/task_state.go (96%) rename pkg/{coordinator => }/tasks/check_clients_are_healthy/README.md (100%) rename pkg/{coordinator => }/tasks/check_clients_are_healthy/config.go (95%) rename pkg/{coordinator => }/tasks/check_clients_are_healthy/task.go (93%) rename pkg/{coordinator => }/tasks/check_consensus_attestation_stats/README.md (100%) rename pkg/{coordinator => }/tasks/check_consensus_attestation_stats/config.go (100%) rename pkg/{coordinator => }/tasks/check_consensus_attestation_stats/task.go (99%) rename pkg/{coordinator => }/tasks/check_consensus_block_proposals/README.md (100%) rename pkg/{coordinator => }/tasks/check_consensus_block_proposals/config.go (100%) rename pkg/{coordinator => }/tasks/check_consensus_block_proposals/task.go (99%) rename pkg/{coordinator => }/tasks/check_consensus_finality/README.md (100%) rename pkg/{coordinator => }/tasks/check_consensus_finality/config.go (100%) rename pkg/{coordinator => }/tasks/check_consensus_finality/task.go (98%) rename pkg/{coordinator => }/tasks/check_consensus_forks/README.md (100%) rename pkg/{coordinator => }/tasks/check_consensus_forks/config.go (100%) rename pkg/{coordinator => }/tasks/check_consensus_forks/task.go (97%) rename pkg/{coordinator => }/tasks/check_consensus_identity/README.md (100%) rename pkg/{coordinator => }/tasks/check_consensus_identity/config.go (96%) rename pkg/{coordinator => }/tasks/check_consensus_identity/task.go (98%) rename pkg/{coordinator => }/tasks/check_consensus_proposer_duty/README.md (100%) rename pkg/{coordinator => }/tasks/check_consensus_proposer_duty/config.go (100%) rename pkg/{coordinator => }/tasks/check_consensus_proposer_duty/task.go (97%) rename pkg/{coordinator => }/tasks/check_consensus_reorgs/README.md (100%) rename pkg/{coordinator => }/tasks/check_consensus_reorgs/config.go (100%) rename pkg/{coordinator => }/tasks/check_consensus_reorgs/task.go (97%) rename pkg/{coordinator => }/tasks/check_consensus_slot_range/README.md (100%) rename pkg/{coordinator => }/tasks/check_consensus_slot_range/config.go (100%) rename pkg/{coordinator => }/tasks/check_consensus_slot_range/task.go (98%) rename pkg/{coordinator => }/tasks/check_consensus_sync_status/README.md (100%) rename pkg/{coordinator => }/tasks/check_consensus_sync_status/config.go (96%) rename pkg/{coordinator => }/tasks/check_consensus_sync_status/task.go (95%) rename pkg/{coordinator => }/tasks/check_consensus_validator_status/README.md (100%) rename pkg/{coordinator => }/tasks/check_consensus_validator_status/config.go (100%) rename pkg/{coordinator => }/tasks/check_consensus_validator_status/task.go (98%) rename pkg/{coordinator => }/tasks/check_eth_call/README.md (100%) rename pkg/{coordinator => }/tasks/check_eth_call/config.go (100%) rename pkg/{coordinator => }/tasks/check_eth_call/task.go (98%) rename pkg/{coordinator => }/tasks/check_eth_config/README.md (100%) rename pkg/{coordinator => }/tasks/check_eth_config/config.go (100%) rename pkg/{coordinator => }/tasks/check_eth_config/task.go (97%) rename pkg/{coordinator => }/tasks/check_execution_sync_status/README.md (100%) rename pkg/{coordinator => }/tasks/check_execution_sync_status/config.go (95%) rename pkg/{coordinator => }/tasks/check_execution_sync_status/task.go (95%) rename pkg/{coordinator => }/tasks/generate_attestations/README.md (100%) rename pkg/{coordinator => }/tasks/generate_attestations/config.go (100%) rename pkg/{coordinator => }/tasks/generate_attestations/task.go (99%) rename pkg/{coordinator => }/tasks/generate_blob_transactions/README.md (100%) rename pkg/{coordinator => }/tasks/generate_blob_transactions/config.go (100%) rename pkg/{coordinator => }/tasks/generate_blob_transactions/task.go (97%) rename pkg/{coordinator => }/tasks/generate_bls_changes/README.md (100%) rename pkg/{coordinator => }/tasks/generate_bls_changes/config.go (100%) rename pkg/{coordinator => }/tasks/generate_bls_changes/task.go (97%) rename pkg/{coordinator => }/tasks/generate_child_wallet/README.md (100%) rename pkg/{coordinator => }/tasks/generate_child_wallet/config.go (100%) rename pkg/{coordinator => }/tasks/generate_child_wallet/task.go (95%) rename pkg/{coordinator => }/tasks/generate_consolidations/README.md (100%) rename pkg/{coordinator => }/tasks/generate_consolidations/config.go (100%) rename pkg/{coordinator => }/tasks/generate_consolidations/task.go (97%) rename pkg/{coordinator => }/tasks/generate_deposits/README.md (100%) rename pkg/{coordinator => }/tasks/generate_deposits/config.go (100%) rename pkg/{coordinator => }/tasks/generate_deposits/deposit_contract/DepositContract.abi (100%) rename pkg/{coordinator => }/tasks/generate_deposits/deposit_contract/DepositContract.sol (100%) rename pkg/{coordinator => }/tasks/generate_deposits/deposit_contract/deposit_contract.go (100%) rename pkg/{coordinator => }/tasks/generate_deposits/task.go (97%) rename pkg/{coordinator => }/tasks/generate_eoa_transactions/README.md (100%) rename pkg/{coordinator => }/tasks/generate_eoa_transactions/config.go (100%) rename pkg/{coordinator => }/tasks/generate_eoa_transactions/task.go (98%) rename pkg/{coordinator => }/tasks/generate_exits/README.md (100%) rename pkg/{coordinator => }/tasks/generate_exits/config.go (100%) rename pkg/{coordinator => }/tasks/generate_exits/task.go (98%) rename pkg/{coordinator => }/tasks/generate_slashings/README.md (100%) rename pkg/{coordinator => }/tasks/generate_slashings/config.go (100%) rename pkg/{coordinator => }/tasks/generate_slashings/task.go (98%) rename pkg/{coordinator => }/tasks/generate_transaction/README.md (100%) rename pkg/{coordinator => }/tasks/generate_transaction/config.go (98%) rename pkg/{coordinator => }/tasks/generate_transaction/task.go (97%) rename pkg/{coordinator => }/tasks/generate_withdrawal_requests/README.md (100%) rename pkg/{coordinator => }/tasks/generate_withdrawal_requests/config.go (100%) rename pkg/{coordinator => }/tasks/generate_withdrawal_requests/task.go (97%) rename pkg/{coordinator => }/tasks/get_consensus_specs/README.md (100%) rename pkg/{coordinator => }/tasks/get_consensus_specs/config.go (100%) rename pkg/{coordinator => }/tasks/get_consensus_specs/task.go (96%) rename pkg/{coordinator => }/tasks/get_consensus_validators/README.md (100%) rename pkg/{coordinator => }/tasks/get_consensus_validators/config.go (100%) rename pkg/{coordinator => }/tasks/get_consensus_validators/task.go (98%) rename pkg/{coordinator => }/tasks/get_execution_block/README.md (100%) rename pkg/{coordinator => }/tasks/get_execution_block/config.go (100%) rename pkg/{coordinator => }/tasks/get_execution_block/task.go (94%) rename pkg/{coordinator => }/tasks/get_pubkeys_from_mnemonic/README.md (100%) rename pkg/{coordinator => }/tasks/get_pubkeys_from_mnemonic/config.go (100%) rename pkg/{coordinator => }/tasks/get_pubkeys_from_mnemonic/task.go (97%) rename pkg/{coordinator => }/tasks/get_random_mnemonic/README.md (100%) rename pkg/{coordinator => }/tasks/get_random_mnemonic/config.go (100%) rename pkg/{coordinator => }/tasks/get_random_mnemonic/task.go (96%) rename pkg/{coordinator => }/tasks/get_wallet_details/README.md (100%) rename pkg/{coordinator => }/tasks/get_wallet_details/config.go (100%) rename pkg/{coordinator => }/tasks/get_wallet_details/task.go (94%) rename pkg/{coordinator => }/tasks/info.go (100%) rename pkg/{coordinator => }/tasks/run_command/README.md (100%) rename pkg/{coordinator => }/tasks/run_command/config.go (100%) rename pkg/{coordinator => }/tasks/run_command/task.go (97%) rename pkg/{coordinator => }/tasks/run_external_tasks/README.md (100%) rename pkg/{coordinator => }/tasks/run_external_tasks/config.go (100%) rename pkg/{coordinator => }/tasks/run_external_tasks/task.go (98%) rename pkg/{coordinator => }/tasks/run_shell/README.md (100%) rename pkg/{coordinator => }/tasks/run_shell/config.go (100%) rename pkg/{coordinator => }/tasks/run_shell/result_file.go (100%) rename pkg/{coordinator => }/tasks/run_shell/task.go (98%) rename pkg/{coordinator => }/tasks/run_task_background/README.md (100%) rename pkg/{coordinator => }/tasks/run_task_background/config.go (94%) rename pkg/{coordinator => }/tasks/run_task_background/task.go (98%) rename pkg/{coordinator => }/tasks/run_task_matrix/README.md (100%) rename pkg/{coordinator => }/tasks/run_task_matrix/config.go (94%) rename pkg/{coordinator => }/tasks/run_task_matrix/task.go (98%) rename pkg/{coordinator => }/tasks/run_task_options/README.md (100%) rename pkg/{coordinator => }/tasks/run_task_options/config.go (93%) rename pkg/{coordinator => }/tasks/run_task_options/task.go (97%) rename pkg/{coordinator => }/tasks/run_tasks/README.md (100%) rename pkg/{coordinator => }/tasks/run_tasks/config.go (92%) rename pkg/{coordinator => }/tasks/run_tasks/task.go (97%) rename pkg/{coordinator => }/tasks/run_tasks_concurrent/README.md (100%) rename pkg/{coordinator => }/tasks/run_tasks_concurrent/config.go (94%) rename pkg/{coordinator => }/tasks/run_tasks_concurrent/task.go (98%) rename pkg/{coordinator => }/tasks/sleep/README.md (100%) rename pkg/{coordinator => }/tasks/sleep/config.go (84%) rename pkg/{coordinator => }/tasks/sleep/task.go (96%) create mode 100644 pkg/tasks/tasks.go rename pkg/{coordinator => }/test/dbtest.go (96%) rename pkg/{coordinator => }/test/descriptor.go (98%) rename pkg/{coordinator => }/test/test.go (95%) rename pkg/{coordinator => }/types/coordinator.go (76%) rename pkg/{coordinator => }/types/scheduler.go (74%) rename pkg/{coordinator => }/types/task.go (94%) rename pkg/{coordinator => }/types/test.go (97%) rename pkg/{coordinator => }/types/vars.go (100%) rename pkg/{coordinator => }/vars/scope_filter.go (96%) rename pkg/{coordinator => }/vars/utils.go (100%) rename pkg/{coordinator => }/vars/variables.go (99%) rename pkg/{coordinator => }/wallet/blobtx/blob_encode.go (100%) rename pkg/{coordinator => }/wallet/blobtx/blobtx.go (100%) rename pkg/{coordinator => }/wallet/manager.go (98%) rename pkg/{coordinator => }/wallet/wallet.go (99%) rename pkg/{coordinator => }/wallet/walletpool.go (100%) rename pkg/{coordinator => }/web/api/constants.go (100%) rename pkg/{coordinator => }/web/api/docs/docs.go (85%) rename pkg/{coordinator => }/web/api/docs/swagger.json (85%) rename pkg/{coordinator => }/web/api/docs/swagger.yaml (73%) rename pkg/{coordinator => }/web/api/get_task_details_api.go (98%) rename pkg/{coordinator => }/web/api/get_task_result_api.go (97%) rename pkg/{coordinator => }/web/api/get_test_api.go (97%) rename pkg/{coordinator => }/web/api/get_test_run_api.go (98%) rename pkg/{coordinator => }/web/api/get_test_run_details_api.go (99%) rename pkg/{coordinator => }/web/api/get_test_run_status_api.go (100%) rename pkg/{coordinator => }/web/api/get_test_runs_api.go (96%) rename pkg/{coordinator => }/web/api/get_tests_api.go (100%) rename pkg/{coordinator => }/web/api/handler.go (96%) rename pkg/{coordinator => }/web/api/post_test_run_api.go (97%) rename pkg/{coordinator => }/web/api/post_test_run_cancel_api.go (100%) rename pkg/{coordinator => }/web/api/post_test_runs_delete_api.go (100%) rename pkg/{coordinator => }/web/api/post_tests_delete_api.go (100%) rename pkg/{coordinator => }/web/api/post_tests_register_api.go (96%) rename pkg/{coordinator => }/web/api/post_tests_register_external_api.go (96%) rename pkg/{coordinator => }/web/handlers/clients.go (95%) rename pkg/{coordinator => }/web/handlers/handler.go (95%) rename pkg/{coordinator => }/web/handlers/index.go (99%) rename pkg/{coordinator => }/web/handlers/logs.go (100%) rename pkg/{coordinator => }/web/handlers/registry.go (98%) rename pkg/{coordinator => }/web/handlers/sidebar.go (97%) rename pkg/{coordinator => }/web/handlers/test.go (98%) rename pkg/{coordinator => }/web/handlers/test_run.go (97%) rename pkg/{coordinator => }/web/server.go (95%) rename pkg/{coordinator => }/web/static/css/bootstrap.min.css (100%) rename pkg/{coordinator => }/web/static/css/bootstrap.min.css.map (100%) rename pkg/{coordinator => }/web/static/css/fontawesome-all.min.css (100%) rename pkg/{coordinator => }/web/static/css/fontawesome.min.css (100%) rename pkg/{coordinator => }/web/static/css/layout.css (100%) rename pkg/{coordinator => }/web/static/embed.go (100%) rename pkg/{coordinator => }/web/static/favicon.ico (100%) rename pkg/{coordinator => }/web/static/js/ace-1.5.0/ace.js (100%) rename pkg/{coordinator => }/web/static/js/ace-1.5.0/mode-yaml.js (100%) rename pkg/{coordinator => }/web/static/js/assertoor.js (100%) rename pkg/{coordinator => }/web/static/js/bootstrap.bundle.min.js (100%) rename pkg/{coordinator => }/web/static/js/bootstrap.bundle.min.js.map (100%) rename pkg/{coordinator => }/web/static/js/clipboard.min.js (100%) rename pkg/{coordinator => }/web/static/js/color-modes.js (100%) rename pkg/{coordinator => }/web/static/js/jquery.min.js (100%) rename pkg/{coordinator => }/web/static/js/yaml-0.3.0.min.js (100%) rename pkg/{coordinator => }/web/static/webfonts/fa-brands-400.ttf (100%) rename pkg/{coordinator => }/web/static/webfonts/fa-brands-400.woff2 (100%) rename pkg/{coordinator => }/web/static/webfonts/fa-regular-400.ttf (100%) rename pkg/{coordinator => }/web/static/webfonts/fa-regular-400.woff2 (100%) rename pkg/{coordinator => }/web/static/webfonts/fa-solid-900.ttf (100%) rename pkg/{coordinator => }/web/static/webfonts/fa-solid-900.woff2 (100%) rename pkg/{coordinator => }/web/static/webfonts/fa-v4compatibility.ttf (100%) rename pkg/{coordinator => }/web/static/webfonts/fa-v4compatibility.woff2 (100%) rename pkg/{coordinator => }/web/templates/_layout/404.html (100%) rename pkg/{coordinator => }/web/templates/_layout/500.html (100%) rename pkg/{coordinator => }/web/templates/_layout/blank.html (100%) rename pkg/{coordinator => }/web/templates/_layout/footer.html (100%) rename pkg/{coordinator => }/web/templates/_layout/header.html (100%) rename pkg/{coordinator => }/web/templates/_layout/layout.html (100%) rename pkg/{coordinator => }/web/templates/clients/clients.html (100%) rename pkg/{coordinator => }/web/templates/index/index.html (100%) rename pkg/{coordinator => }/web/templates/registry/registry.html (100%) rename pkg/{coordinator => }/web/templates/sidebar/sidebar.html (100%) rename pkg/{coordinator => }/web/templates/templates.go (100%) rename pkg/{coordinator => }/web/templates/test/test.html (100%) rename pkg/{coordinator => }/web/templates/test/test_runs.html (100%) rename pkg/{coordinator => }/web/templates/test_run/test_run.html (100%) rename pkg/{coordinator => }/web/types/config.go (100%) rename pkg/{coordinator => }/web/utils/templateFucs.go (100%) diff --git a/Makefile b/Makefile index bc8d1cf0..c4fc5789 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # assertoor VERSION := $(shell git rev-parse --short HEAD) -GOLDFLAGS += -X 'github.com/ethpandaops/assertoor/pkg/coordinator/buildinfo.BuildVersion="$(VERSION)"' -GOLDFLAGS += -X 'github.com/ethpandaops/assertoor/pkg/coordinator/buildinfo.BuildRelease="$(RELEASE)"' +GOLDFLAGS += -X 'github.com/ethpandaops/assertoor/pkg/buildinfo.BuildVersion="$(VERSION)"' +GOLDFLAGS += -X 'github.com/ethpandaops/assertoor/pkg/buildinfo.BuildRelease="$(RELEASE)"' CURRENT_UID := $(shell id -u) CURRENT_GID := $(shell id -g) @@ -17,7 +17,7 @@ build: env CGO_ENABLED=1 go build -v -o bin/ -ldflags="-s -w $(GOLDFLAGS)" . docs: - go install github.com/swaggo/swag/cmd/swag@v1.16.3 && swag init -g web/api/handler.go -d pkg/coordinator --parseDependency -o pkg/coordinator/web/api/docs + go install github.com/swaggo/swag/cmd/swag@v1.16.3 && swag init -g web/api/handler.go -d pkg --parseDependency -o pkg/web/api/docs clean: rm -f bin/* diff --git a/cmd/root.go b/cmd/root.go index ba2a4ca7..6404d885 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,8 +3,8 @@ package cmd import ( "context" - "github.com/ethpandaops/assertoor/pkg/coordinator" - "github.com/ethpandaops/assertoor/pkg/coordinator/buildinfo" + "github.com/ethpandaops/assertoor/pkg/assertoor" + "github.com/ethpandaops/assertoor/pkg/buildinfo" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -21,7 +21,7 @@ var rootCmd = &cobra.Command{ return } - config, err := coordinator.NewConfig(cfgFile) + config, err := assertoor.NewConfig(cfgFile) if err != nil { logr.Fatal(err) } @@ -44,7 +44,7 @@ var rootCmd = &cobra.Command{ logr.SetLevel(logrus.DebugLevel) } - coord := coordinator.NewCoordinator(config, logr, metricsPort) + coord := assertoor.NewCoordinator(config, logr, metricsPort) if err := coord.Run(cmd.Context()); err != nil { logr.Fatal(err) diff --git a/cmd/tasks.go b/cmd/tasks.go index 68beeb30..93db46ac 100644 --- a/cmd/tasks.go +++ b/cmd/tasks.go @@ -7,7 +7,7 @@ import ( "fmt" "log" - "github.com/ethpandaops/assertoor/pkg/coordinator/tasks" + "github.com/ethpandaops/assertoor/pkg/tasks" "github.com/spf13/cobra" "gopkg.in/yaml.v3" ) diff --git a/cmd/validate.go b/cmd/validate.go index 10859c50..9e185962 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -3,7 +3,7 @@ package cmd import ( "os" - "github.com/ethpandaops/assertoor/pkg/coordinator" + "github.com/ethpandaops/assertoor/pkg/assertoor" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -27,7 +27,7 @@ var validateCmd = &cobra.Command{ } // Load configuration - config, err := coordinator.NewConfig(cfgFile) + config, err := assertoor.NewConfig(cfgFile) if err != nil { logrus.WithError(err).Error("failed to load configuration") os.Exit(1) diff --git a/pkg/coordinator/config.go b/pkg/assertoor/config.go similarity index 92% rename from pkg/coordinator/config.go rename to pkg/assertoor/config.go index 27d7d1a0..5cac457c 100644 --- a/pkg/coordinator/config.go +++ b/pkg/assertoor/config.go @@ -1,4 +1,4 @@ -package coordinator +package assertoor import ( "fmt" @@ -7,13 +7,13 @@ import ( "strconv" "strings" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients" - "github.com/ethpandaops/assertoor/pkg/coordinator/db" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" - "github.com/ethpandaops/assertoor/pkg/coordinator/names" - "github.com/ethpandaops/assertoor/pkg/coordinator/test" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - web_types "github.com/ethpandaops/assertoor/pkg/coordinator/web/types" + "github.com/ethpandaops/assertoor/pkg/clients" + "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/helper" + "github.com/ethpandaops/assertoor/pkg/names" + "github.com/ethpandaops/assertoor/pkg/test" + "github.com/ethpandaops/assertoor/pkg/types" + web_types "github.com/ethpandaops/assertoor/pkg/web/types" "gopkg.in/yaml.v3" ) diff --git a/pkg/coordinator/coordinator.go b/pkg/assertoor/coordinator.go similarity index 94% rename from pkg/coordinator/coordinator.go rename to pkg/assertoor/coordinator.go index e16ca858..bd8524b7 100644 --- a/pkg/coordinator/coordinator.go +++ b/pkg/assertoor/coordinator.go @@ -1,4 +1,4 @@ -package coordinator +package assertoor import ( "context" @@ -11,17 +11,17 @@ import ( "strings" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/buildinfo" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus" - "github.com/ethpandaops/assertoor/pkg/coordinator/db" - "github.com/ethpandaops/assertoor/pkg/coordinator/logger" - "github.com/ethpandaops/assertoor/pkg/coordinator/names" - "github.com/ethpandaops/assertoor/pkg/coordinator/test" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" - "github.com/ethpandaops/assertoor/pkg/coordinator/wallet" - "github.com/ethpandaops/assertoor/pkg/coordinator/web" + "github.com/ethpandaops/assertoor/pkg/buildinfo" + "github.com/ethpandaops/assertoor/pkg/clients" + "github.com/ethpandaops/assertoor/pkg/clients/consensus" + "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/logger" + "github.com/ethpandaops/assertoor/pkg/names" + "github.com/ethpandaops/assertoor/pkg/test" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" + "github.com/ethpandaops/assertoor/pkg/wallet" + "github.com/ethpandaops/assertoor/pkg/web" "github.com/jmoiron/sqlx" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/sirupsen/logrus" diff --git a/pkg/coordinator/init.go b/pkg/assertoor/init.go similarity index 90% rename from pkg/coordinator/init.go rename to pkg/assertoor/init.go index bb9679c2..035d360d 100644 --- a/pkg/coordinator/init.go +++ b/pkg/assertoor/init.go @@ -1,4 +1,4 @@ -package coordinator +package assertoor import ( hbls "github.com/herumi/bls-eth-go-binary/bls" diff --git a/pkg/coordinator/testregistry.go b/pkg/assertoor/testregistry.go similarity index 96% rename from pkg/coordinator/testregistry.go rename to pkg/assertoor/testregistry.go index f7e212c1..abc06ebc 100644 --- a/pkg/coordinator/testregistry.go +++ b/pkg/assertoor/testregistry.go @@ -1,4 +1,4 @@ -package coordinator +package assertoor import ( "context" @@ -9,11 +9,11 @@ import ( "sync" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/db" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" - "github.com/ethpandaops/assertoor/pkg/coordinator/test" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/helper" + "github.com/ethpandaops/assertoor/pkg/test" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" "github.com/jmoiron/sqlx" "gopkg.in/yaml.v3" ) diff --git a/pkg/coordinator/testrunner.go b/pkg/assertoor/testrunner.go similarity index 98% rename from pkg/coordinator/testrunner.go rename to pkg/assertoor/testrunner.go index 950f79cf..199c93a5 100644 --- a/pkg/coordinator/testrunner.go +++ b/pkg/assertoor/testrunner.go @@ -1,4 +1,4 @@ -package coordinator +package assertoor import ( "context" @@ -7,8 +7,8 @@ import ( "sync" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/test" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/test" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/gorhill/cronexpr" ) diff --git a/pkg/coordinator/buildinfo/buildinfo.go b/pkg/buildinfo/buildinfo.go similarity index 100% rename from pkg/coordinator/buildinfo/buildinfo.go rename to pkg/buildinfo/buildinfo.go diff --git a/pkg/coordinator/clients/clients.go b/pkg/clients/clients.go similarity index 97% rename from pkg/coordinator/clients/clients.go rename to pkg/clients/clients.go index a0b5560d..60f2380d 100644 --- a/pkg/coordinator/clients/clients.go +++ b/pkg/clients/clients.go @@ -9,8 +9,8 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution" + "github.com/ethpandaops/assertoor/pkg/clients/consensus" + "github.com/ethpandaops/assertoor/pkg/clients/execution" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/clients/consensus/block.go b/pkg/clients/consensus/block.go similarity index 100% rename from pkg/coordinator/clients/consensus/block.go rename to pkg/clients/consensus/block.go diff --git a/pkg/coordinator/clients/consensus/block_utils.go b/pkg/clients/consensus/block_utils.go similarity index 100% rename from pkg/coordinator/clients/consensus/block_utils.go rename to pkg/clients/consensus/block_utils.go diff --git a/pkg/coordinator/clients/consensus/blockcache.go b/pkg/clients/consensus/blockcache.go similarity index 100% rename from pkg/coordinator/clients/consensus/blockcache.go rename to pkg/clients/consensus/blockcache.go diff --git a/pkg/coordinator/clients/consensus/chainspec.go b/pkg/clients/consensus/chainspec.go similarity index 100% rename from pkg/coordinator/clients/consensus/chainspec.go rename to pkg/clients/consensus/chainspec.go diff --git a/pkg/coordinator/clients/consensus/checkpoint.go b/pkg/clients/consensus/checkpoint.go similarity index 100% rename from pkg/coordinator/clients/consensus/checkpoint.go rename to pkg/clients/consensus/checkpoint.go diff --git a/pkg/coordinator/clients/consensus/client.go b/pkg/clients/consensus/client.go similarity index 97% rename from pkg/coordinator/clients/consensus/client.go rename to pkg/clients/consensus/client.go index 5d34a377..03ba04bf 100644 --- a/pkg/coordinator/clients/consensus/client.go +++ b/pkg/clients/consensus/client.go @@ -6,7 +6,7 @@ import ( "time" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus/rpc" + "github.com/ethpandaops/assertoor/pkg/clients/consensus/rpc" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/clients/consensus/clientlogic.go b/pkg/clients/consensus/clientlogic.go similarity index 99% rename from pkg/coordinator/clients/consensus/clientlogic.go rename to pkg/clients/consensus/clientlogic.go index c268054c..1a8bde2c 100644 --- a/pkg/coordinator/clients/consensus/clientlogic.go +++ b/pkg/clients/consensus/clientlogic.go @@ -10,7 +10,7 @@ import ( v1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus/rpc" + "github.com/ethpandaops/assertoor/pkg/clients/consensus/rpc" ) func (client *Client) runClientLoop() { diff --git a/pkg/coordinator/clients/consensus/clienttype.go b/pkg/clients/consensus/clienttype.go similarity index 100% rename from pkg/coordinator/clients/consensus/clienttype.go rename to pkg/clients/consensus/clienttype.go diff --git a/pkg/coordinator/clients/consensus/forks.go b/pkg/clients/consensus/forks.go similarity index 100% rename from pkg/coordinator/clients/consensus/forks.go rename to pkg/clients/consensus/forks.go diff --git a/pkg/coordinator/clients/consensus/pool.go b/pkg/clients/consensus/pool.go similarity index 100% rename from pkg/coordinator/clients/consensus/pool.go rename to pkg/clients/consensus/pool.go diff --git a/pkg/coordinator/clients/consensus/rpc/beaconapi.go b/pkg/clients/consensus/rpc/beaconapi.go similarity index 100% rename from pkg/coordinator/clients/consensus/rpc/beaconapi.go rename to pkg/clients/consensus/rpc/beaconapi.go diff --git a/pkg/coordinator/clients/consensus/rpc/beaconstream.go b/pkg/clients/consensus/rpc/beaconstream.go similarity index 98% rename from pkg/coordinator/clients/consensus/rpc/beaconstream.go rename to pkg/clients/consensus/rpc/beaconstream.go index cf52f0b8..28981e0f 100644 --- a/pkg/coordinator/clients/consensus/rpc/beaconstream.go +++ b/pkg/clients/consensus/rpc/beaconstream.go @@ -12,7 +12,7 @@ import ( v1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/donovanhide/eventsource" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus/rpc/eventstream" + "github.com/ethpandaops/assertoor/pkg/clients/consensus/rpc/eventstream" ) const ( diff --git a/pkg/coordinator/clients/consensus/rpc/eventstream/eventstream.go b/pkg/clients/consensus/rpc/eventstream/eventstream.go similarity index 100% rename from pkg/coordinator/clients/consensus/rpc/eventstream/eventstream.go rename to pkg/clients/consensus/rpc/eventstream/eventstream.go diff --git a/pkg/coordinator/clients/consensus/rpc/syncstatus.go b/pkg/clients/consensus/rpc/syncstatus.go similarity index 100% rename from pkg/coordinator/clients/consensus/rpc/syncstatus.go rename to pkg/clients/consensus/rpc/syncstatus.go diff --git a/pkg/coordinator/clients/consensus/subscriptions.go b/pkg/clients/consensus/subscriptions.go similarity index 100% rename from pkg/coordinator/clients/consensus/subscriptions.go rename to pkg/clients/consensus/subscriptions.go diff --git a/pkg/coordinator/clients/execution/block.go b/pkg/clients/execution/block.go similarity index 100% rename from pkg/coordinator/clients/execution/block.go rename to pkg/clients/execution/block.go diff --git a/pkg/coordinator/clients/execution/blockcache.go b/pkg/clients/execution/blockcache.go similarity index 98% rename from pkg/coordinator/clients/execution/blockcache.go rename to pkg/clients/execution/blockcache.go index 4a0f0454..b71790d3 100644 --- a/pkg/coordinator/clients/execution/blockcache.go +++ b/pkg/clients/execution/blockcache.go @@ -12,7 +12,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution/rpc" + "github.com/ethpandaops/assertoor/pkg/clients/execution/rpc" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/clients/execution/client.go b/pkg/clients/execution/client.go similarity index 97% rename from pkg/coordinator/clients/execution/client.go rename to pkg/clients/execution/client.go index 1b65564c..3114d36e 100644 --- a/pkg/coordinator/clients/execution/client.go +++ b/pkg/clients/execution/client.go @@ -6,7 +6,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution/rpc" + "github.com/ethpandaops/assertoor/pkg/clients/execution/rpc" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/clients/execution/clientlogic.go b/pkg/clients/execution/clientlogic.go similarity index 100% rename from pkg/coordinator/clients/execution/clientlogic.go rename to pkg/clients/execution/clientlogic.go diff --git a/pkg/coordinator/clients/execution/clienttype.go b/pkg/clients/execution/clienttype.go similarity index 100% rename from pkg/coordinator/clients/execution/clienttype.go rename to pkg/clients/execution/clienttype.go diff --git a/pkg/coordinator/clients/execution/forks.go b/pkg/clients/execution/forks.go similarity index 100% rename from pkg/coordinator/clients/execution/forks.go rename to pkg/clients/execution/forks.go diff --git a/pkg/coordinator/clients/execution/pool.go b/pkg/clients/execution/pool.go similarity index 100% rename from pkg/coordinator/clients/execution/pool.go rename to pkg/clients/execution/pool.go diff --git a/pkg/coordinator/clients/execution/rpc/chainspec.go b/pkg/clients/execution/rpc/chainspec.go similarity index 100% rename from pkg/coordinator/clients/execution/rpc/chainspec.go rename to pkg/clients/execution/rpc/chainspec.go diff --git a/pkg/coordinator/clients/execution/rpc/ethconfig.go b/pkg/clients/execution/rpc/ethconfig.go similarity index 100% rename from pkg/coordinator/clients/execution/rpc/ethconfig.go rename to pkg/clients/execution/rpc/ethconfig.go diff --git a/pkg/coordinator/clients/execution/rpc/executionapi.go b/pkg/clients/execution/rpc/executionapi.go similarity index 100% rename from pkg/coordinator/clients/execution/rpc/executionapi.go rename to pkg/clients/execution/rpc/executionapi.go diff --git a/pkg/coordinator/clients/execution/rpc/syncstatus.go b/pkg/clients/execution/rpc/syncstatus.go similarity index 100% rename from pkg/coordinator/clients/execution/rpc/syncstatus.go rename to pkg/clients/execution/rpc/syncstatus.go diff --git a/pkg/coordinator/clients/execution/subscriptions.go b/pkg/clients/execution/subscriptions.go similarity index 100% rename from pkg/coordinator/clients/execution/subscriptions.go rename to pkg/clients/execution/subscriptions.go diff --git a/pkg/coordinator/tasks/tasks.go b/pkg/coordinator/tasks/tasks.go deleted file mode 100644 index 5481d40e..00000000 --- a/pkg/coordinator/tasks/tasks.go +++ /dev/null @@ -1,119 +0,0 @@ -package tasks - -import ( - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - - checkclientsarehealthy "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_clients_are_healthy" - checkconsensusattestationstats "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_consensus_attestation_stats" - checkconsensusblockproposals "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_consensus_block_proposals" - checkconsensusfinality "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_consensus_finality" - checkconsensusforks "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_consensus_forks" - checkconsensusidentity "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_consensus_identity" - checkconsensusproposerduty "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_consensus_proposer_duty" - checkconsensusreorgs "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_consensus_reorgs" - checkconsensusslotrange "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_consensus_slot_range" - checkconsensussyncstatus "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_consensus_sync_status" - checkconsensusvalidatorstatus "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_consensus_validator_status" - checkethcall "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_eth_call" - checkethconfig "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_eth_config" - checkexecutionsyncstatus "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_execution_sync_status" - generateattestations "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_attestations" - generateblobtransactions "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_blob_transactions" - generateblschanges "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_bls_changes" - generatechildwallet "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_child_wallet" - generateconsolidations "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_consolidations" - generatedeposits "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_deposits" - generateeoatransactions "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_eoa_transactions" - generateexits "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_exits" - generateslashings "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_slashings" - generatetransaction "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_transaction" - generatewithdrawalrequests "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_withdrawal_requests" - getconsensusspecs "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/get_consensus_specs" - getconsensusvalidators "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/get_consensus_validators" - checkexecutionblock "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/get_execution_block" - getpubkeysfrommnemonic "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/get_pubkeys_from_mnemonic" - getrandommnemonic "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/get_random_mnemonic" - getwalletdetails "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/get_wallet_details" - runcommand "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/run_command" - runexternaltasks "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/run_external_tasks" - runshell "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/run_shell" - runtaskbackground "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/run_task_background" - runtaskmatrix "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/run_task_matrix" - runtaskoptions "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/run_task_options" - runtasks "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/run_tasks" - runtasksconcurrent "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/run_tasks_concurrent" - sleep "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/sleep" -) - -var AvailableTaskDescriptors = []*types.TaskDescriptor{ - checkclientsarehealthy.TaskDescriptor, - checkconsensusattestationstats.TaskDescriptor, - checkconsensusblockproposals.TaskDescriptor, - checkconsensusfinality.TaskDescriptor, - checkconsensusforks.TaskDescriptor, - checkconsensusidentity.TaskDescriptor, - checkconsensusproposerduty.TaskDescriptor, - checkconsensusreorgs.TaskDescriptor, - checkconsensusslotrange.TaskDescriptor, - checkconsensussyncstatus.TaskDescriptor, - checkconsensusvalidatorstatus.TaskDescriptor, - checkexecutionblock.TaskDescriptor, - checkethcall.TaskDescriptor, - checkethconfig.TaskDescriptor, - checkexecutionsyncstatus.TaskDescriptor, - generateattestations.TaskDescriptor, - generateblobtransactions.TaskDescriptor, - generateblschanges.TaskDescriptor, - generatechildwallet.TaskDescriptor, - generateconsolidations.TaskDescriptor, - generateeoatransactions.TaskDescriptor, - generatedeposits.TaskDescriptor, - generateexits.TaskDescriptor, - generateslashings.TaskDescriptor, - generatetransaction.TaskDescriptor, - generatewithdrawalrequests.TaskDescriptor, - getpubkeysfrommnemonic.TaskDescriptor, - getconsensusspecs.TaskDescriptor, - getconsensusvalidators.TaskDescriptor, - getrandommnemonic.TaskDescriptor, - getwalletdetails.TaskDescriptor, - runcommand.TaskDescriptor, - runexternaltasks.TaskDescriptor, - runshell.TaskDescriptor, - runtaskbackground.TaskDescriptor, - runtaskmatrix.TaskDescriptor, - runtaskoptions.TaskDescriptor, - runtasks.TaskDescriptor, - runtasksconcurrent.TaskDescriptor, - sleep.TaskDescriptor, -} - -func GetTaskDescriptor(name string) *types.TaskDescriptor { - // lookup task descriptor by name - var taskDescriptor *types.TaskDescriptor - - for _, taskDesc := range AvailableTaskDescriptors { - if taskDesc.Name == name { - taskDescriptor = taskDesc - break - } - - if len(taskDesc.Aliases) > 0 { - isAlias := false - - for _, alias := range taskDesc.Aliases { - if alias == name { - isAlias = true - break - } - } - - if isAlias { - taskDescriptor = taskDesc - break - } - } - } - - return taskDescriptor -} diff --git a/pkg/coordinator/db/assertoor_state.go b/pkg/db/assertoor_state.go similarity index 100% rename from pkg/coordinator/db/assertoor_state.go rename to pkg/db/assertoor_state.go diff --git a/pkg/coordinator/db/common.go b/pkg/db/common.go similarity index 100% rename from pkg/coordinator/db/common.go rename to pkg/db/common.go diff --git a/pkg/coordinator/db/schema/pgsql/20240913135112_init.sql b/pkg/db/schema/pgsql/20240913135112_init.sql similarity index 100% rename from pkg/coordinator/db/schema/pgsql/20240913135112_init.sql rename to pkg/db/schema/pgsql/20240913135112_init.sql diff --git a/pkg/coordinator/db/schema/pgsql/20250114224233_task_summaries.sql b/pkg/db/schema/pgsql/20250114224233_task_summaries.sql similarity index 100% rename from pkg/coordinator/db/schema/pgsql/20250114224233_task_summaries.sql rename to pkg/db/schema/pgsql/20250114224233_task_summaries.sql diff --git a/pkg/coordinator/db/schema/sqlite/20240913135112_init.sql b/pkg/db/schema/sqlite/20240913135112_init.sql similarity index 100% rename from pkg/coordinator/db/schema/sqlite/20240913135112_init.sql rename to pkg/db/schema/sqlite/20240913135112_init.sql diff --git a/pkg/coordinator/db/schema/sqlite/20250114224233_task_summaries.sql b/pkg/db/schema/sqlite/20250114224233_task_summaries.sql similarity index 100% rename from pkg/coordinator/db/schema/sqlite/20250114224233_task_summaries.sql rename to pkg/db/schema/sqlite/20250114224233_task_summaries.sql diff --git a/pkg/coordinator/db/task_logs.go b/pkg/db/task_logs.go similarity index 100% rename from pkg/coordinator/db/task_logs.go rename to pkg/db/task_logs.go diff --git a/pkg/coordinator/db/task_results.go b/pkg/db/task_results.go similarity index 100% rename from pkg/coordinator/db/task_results.go rename to pkg/db/task_results.go diff --git a/pkg/coordinator/db/task_states.go b/pkg/db/task_states.go similarity index 100% rename from pkg/coordinator/db/task_states.go rename to pkg/db/task_states.go diff --git a/pkg/coordinator/db/test_configs.go b/pkg/db/test_configs.go similarity index 100% rename from pkg/coordinator/db/test_configs.go rename to pkg/db/test_configs.go diff --git a/pkg/coordinator/db/test_runs.go b/pkg/db/test_runs.go similarity index 100% rename from pkg/coordinator/db/test_runs.go rename to pkg/db/test_runs.go diff --git a/pkg/coordinator/helper/bigint.go b/pkg/helper/bigint.go similarity index 100% rename from pkg/coordinator/helper/bigint.go rename to pkg/helper/bigint.go diff --git a/pkg/coordinator/helper/duration.go b/pkg/helper/duration.go similarity index 100% rename from pkg/coordinator/helper/duration.go rename to pkg/helper/duration.go diff --git a/pkg/coordinator/helper/rawmessage.go b/pkg/helper/rawmessage.go similarity index 100% rename from pkg/coordinator/helper/rawmessage.go rename to pkg/helper/rawmessage.go diff --git a/pkg/coordinator/logger/dbreader.go b/pkg/logger/dbreader.go similarity index 92% rename from pkg/coordinator/logger/dbreader.go rename to pkg/logger/dbreader.go index cd928aeb..fdcd92ac 100644 --- a/pkg/coordinator/logger/dbreader.go +++ b/pkg/logger/dbreader.go @@ -1,6 +1,6 @@ package logger -import "github.com/ethpandaops/assertoor/pkg/coordinator/db" +import "github.com/ethpandaops/assertoor/pkg/db" type logDBReader struct { database *db.Database diff --git a/pkg/coordinator/logger/dbwriter.go b/pkg/logger/dbwriter.go similarity index 98% rename from pkg/coordinator/logger/dbwriter.go rename to pkg/logger/dbwriter.go index be720d36..767cea46 100644 --- a/pkg/coordinator/logger/dbwriter.go +++ b/pkg/logger/dbwriter.go @@ -4,7 +4,7 @@ import ( "sync" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/db" + "github.com/ethpandaops/assertoor/pkg/db" "github.com/jmoiron/sqlx" "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" diff --git a/pkg/coordinator/logger/logreader.go b/pkg/logger/logreader.go similarity index 66% rename from pkg/coordinator/logger/logreader.go rename to pkg/logger/logreader.go index 2588a96c..5c43e2be 100644 --- a/pkg/coordinator/logger/logreader.go +++ b/pkg/logger/logreader.go @@ -1,6 +1,6 @@ package logger -import "github.com/ethpandaops/assertoor/pkg/coordinator/db" +import "github.com/ethpandaops/assertoor/pkg/db" type LogReader interface { GetLogEntries(from, limit uint64) []*db.TaskLog diff --git a/pkg/coordinator/logger/logscope.go b/pkg/logger/logscope.go similarity index 97% rename from pkg/coordinator/logger/logscope.go rename to pkg/logger/logscope.go index c5cd8a2d..5abf86dd 100644 --- a/pkg/coordinator/logger/logscope.go +++ b/pkg/logger/logscope.go @@ -4,7 +4,7 @@ import ( "io" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/db" + "github.com/ethpandaops/assertoor/pkg/db" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/logger/membuf.go b/pkg/logger/membuf.go similarity index 97% rename from pkg/coordinator/logger/membuf.go rename to pkg/logger/membuf.go index 0ddf5d11..5cc12ec4 100644 --- a/pkg/coordinator/logger/membuf.go +++ b/pkg/logger/membuf.go @@ -3,7 +3,7 @@ package logger import ( "sync" - "github.com/ethpandaops/assertoor/pkg/coordinator/db" + "github.com/ethpandaops/assertoor/pkg/db" "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" ) diff --git a/pkg/coordinator/names/config.go b/pkg/names/config.go similarity index 100% rename from pkg/coordinator/names/config.go rename to pkg/names/config.go diff --git a/pkg/coordinator/names/validatornames.go b/pkg/names/validatornames.go similarity index 100% rename from pkg/coordinator/names/validatornames.go rename to pkg/names/validatornames.go diff --git a/pkg/coordinator/scheduler/options.go b/pkg/scheduler/options.go similarity index 83% rename from pkg/coordinator/scheduler/options.go rename to pkg/scheduler/options.go index 6b7f6e91..d15dc8e0 100644 --- a/pkg/coordinator/scheduler/options.go +++ b/pkg/scheduler/options.go @@ -3,8 +3,8 @@ package scheduler import ( "fmt" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/helper" + "github.com/ethpandaops/assertoor/pkg/types" "gopkg.in/yaml.v3" ) diff --git a/pkg/coordinator/scheduler/scheduler.go b/pkg/scheduler/scheduler.go similarity index 98% rename from pkg/coordinator/scheduler/scheduler.go rename to pkg/scheduler/scheduler.go index 717d5981..499aebec 100644 --- a/pkg/coordinator/scheduler/scheduler.go +++ b/pkg/scheduler/scheduler.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/scheduler/services.go b/pkg/scheduler/services.go similarity index 75% rename from pkg/coordinator/scheduler/services.go rename to pkg/scheduler/services.go index 387f260d..fe8dec2b 100644 --- a/pkg/coordinator/scheduler/services.go +++ b/pkg/scheduler/services.go @@ -1,11 +1,11 @@ package scheduler import ( - "github.com/ethpandaops/assertoor/pkg/coordinator/clients" - "github.com/ethpandaops/assertoor/pkg/coordinator/db" - "github.com/ethpandaops/assertoor/pkg/coordinator/names" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/wallet" + "github.com/ethpandaops/assertoor/pkg/clients" + "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/names" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/wallet" ) type servicesProvider struct { diff --git a/pkg/coordinator/scheduler/task_execution.go b/pkg/scheduler/task_execution.go similarity index 98% rename from pkg/coordinator/scheduler/task_execution.go rename to pkg/scheduler/task_execution.go index 96d0a831..c0068939 100644 --- a/pkg/coordinator/scheduler/task_execution.go +++ b/pkg/scheduler/task_execution.go @@ -6,7 +6,7 @@ import ( "runtime/debug" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" ) // ExecuteTask executes a task diff --git a/pkg/coordinator/scheduler/task_state.go b/pkg/scheduler/task_state.go similarity index 96% rename from pkg/coordinator/scheduler/task_state.go rename to pkg/scheduler/task_state.go index be864e84..e05b5c8b 100644 --- a/pkg/coordinator/scheduler/task_state.go +++ b/pkg/scheduler/task_state.go @@ -5,11 +5,11 @@ import ( "sync" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/db" - "github.com/ethpandaops/assertoor/pkg/coordinator/logger" - "github.com/ethpandaops/assertoor/pkg/coordinator/tasks" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/logger" + "github.com/ethpandaops/assertoor/pkg/tasks" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" "github.com/jmoiron/sqlx" "gopkg.in/yaml.v3" ) diff --git a/pkg/coordinator/tasks/check_clients_are_healthy/README.md b/pkg/tasks/check_clients_are_healthy/README.md similarity index 100% rename from pkg/coordinator/tasks/check_clients_are_healthy/README.md rename to pkg/tasks/check_clients_are_healthy/README.md diff --git a/pkg/coordinator/tasks/check_clients_are_healthy/config.go b/pkg/tasks/check_clients_are_healthy/config.go similarity index 95% rename from pkg/coordinator/tasks/check_clients_are_healthy/config.go rename to pkg/tasks/check_clients_are_healthy/config.go index 7e2811e0..da73ebda 100644 --- a/pkg/coordinator/tasks/check_clients_are_healthy/config.go +++ b/pkg/tasks/check_clients_are_healthy/config.go @@ -3,7 +3,7 @@ package checkclientsarehealthy import ( "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" + "github.com/ethpandaops/assertoor/pkg/helper" ) type Config struct { diff --git a/pkg/coordinator/tasks/check_clients_are_healthy/task.go b/pkg/tasks/check_clients_are_healthy/task.go similarity index 93% rename from pkg/coordinator/tasks/check_clients_are_healthy/task.go rename to pkg/tasks/check_clients_are_healthy/task.go index 69d367a8..67bcdfe9 100644 --- a/pkg/coordinator/tasks/check_clients_are_healthy/task.go +++ b/pkg/tasks/check_clients_are_healthy/task.go @@ -5,11 +5,11 @@ import ( "fmt" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/clients" + "github.com/ethpandaops/assertoor/pkg/clients/consensus" + "github.com/ethpandaops/assertoor/pkg/clients/execution" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/check_consensus_attestation_stats/README.md b/pkg/tasks/check_consensus_attestation_stats/README.md similarity index 100% rename from pkg/coordinator/tasks/check_consensus_attestation_stats/README.md rename to pkg/tasks/check_consensus_attestation_stats/README.md diff --git a/pkg/coordinator/tasks/check_consensus_attestation_stats/config.go b/pkg/tasks/check_consensus_attestation_stats/config.go similarity index 100% rename from pkg/coordinator/tasks/check_consensus_attestation_stats/config.go rename to pkg/tasks/check_consensus_attestation_stats/config.go diff --git a/pkg/coordinator/tasks/check_consensus_attestation_stats/task.go b/pkg/tasks/check_consensus_attestation_stats/task.go similarity index 99% rename from pkg/coordinator/tasks/check_consensus_attestation_stats/task.go rename to pkg/tasks/check_consensus_attestation_stats/task.go index 52e3e68c..91bfd485 100644 --- a/pkg/coordinator/tasks/check_consensus_attestation_stats/task.go +++ b/pkg/tasks/check_consensus_attestation_stats/task.go @@ -8,8 +8,8 @@ import ( "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/clients/consensus" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/prysmaticlabs/go-bitfield" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/check_consensus_block_proposals/README.md b/pkg/tasks/check_consensus_block_proposals/README.md similarity index 100% rename from pkg/coordinator/tasks/check_consensus_block_proposals/README.md rename to pkg/tasks/check_consensus_block_proposals/README.md diff --git a/pkg/coordinator/tasks/check_consensus_block_proposals/config.go b/pkg/tasks/check_consensus_block_proposals/config.go similarity index 100% rename from pkg/coordinator/tasks/check_consensus_block_proposals/config.go rename to pkg/tasks/check_consensus_block_proposals/config.go diff --git a/pkg/coordinator/tasks/check_consensus_block_proposals/task.go b/pkg/tasks/check_consensus_block_proposals/task.go similarity index 99% rename from pkg/coordinator/tasks/check_consensus_block_proposals/task.go rename to pkg/tasks/check_consensus_block_proposals/task.go index 2faaad3b..798da5f7 100644 --- a/pkg/coordinator/tasks/check_consensus_block_proposals/task.go +++ b/pkg/tasks/check_consensus_block_proposals/task.go @@ -12,9 +12,9 @@ import ( "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/ethereum/go-ethereum/common" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/clients/consensus" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" "github.com/juliangruber/go-intersect" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/check_consensus_finality/README.md b/pkg/tasks/check_consensus_finality/README.md similarity index 100% rename from pkg/coordinator/tasks/check_consensus_finality/README.md rename to pkg/tasks/check_consensus_finality/README.md diff --git a/pkg/coordinator/tasks/check_consensus_finality/config.go b/pkg/tasks/check_consensus_finality/config.go similarity index 100% rename from pkg/coordinator/tasks/check_consensus_finality/config.go rename to pkg/tasks/check_consensus_finality/config.go diff --git a/pkg/coordinator/tasks/check_consensus_finality/task.go b/pkg/tasks/check_consensus_finality/task.go similarity index 98% rename from pkg/coordinator/tasks/check_consensus_finality/task.go rename to pkg/tasks/check_consensus_finality/task.go index aa20526c..6462d0cc 100644 --- a/pkg/coordinator/tasks/check_consensus_finality/task.go +++ b/pkg/tasks/check_consensus_finality/task.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/check_consensus_forks/README.md b/pkg/tasks/check_consensus_forks/README.md similarity index 100% rename from pkg/coordinator/tasks/check_consensus_forks/README.md rename to pkg/tasks/check_consensus_forks/README.md diff --git a/pkg/coordinator/tasks/check_consensus_forks/config.go b/pkg/tasks/check_consensus_forks/config.go similarity index 100% rename from pkg/coordinator/tasks/check_consensus_forks/config.go rename to pkg/tasks/check_consensus_forks/config.go diff --git a/pkg/coordinator/tasks/check_consensus_forks/task.go b/pkg/tasks/check_consensus_forks/task.go similarity index 97% rename from pkg/coordinator/tasks/check_consensus_forks/task.go rename to pkg/tasks/check_consensus_forks/task.go index 20adb193..97be7e00 100644 --- a/pkg/coordinator/tasks/check_consensus_forks/task.go +++ b/pkg/tasks/check_consensus_forks/task.go @@ -5,8 +5,8 @@ import ( "fmt" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/check_consensus_identity/README.md b/pkg/tasks/check_consensus_identity/README.md similarity index 100% rename from pkg/coordinator/tasks/check_consensus_identity/README.md rename to pkg/tasks/check_consensus_identity/README.md diff --git a/pkg/coordinator/tasks/check_consensus_identity/config.go b/pkg/tasks/check_consensus_identity/config.go similarity index 96% rename from pkg/coordinator/tasks/check_consensus_identity/config.go rename to pkg/tasks/check_consensus_identity/config.go index 769b1dac..37626398 100644 --- a/pkg/coordinator/tasks/check_consensus_identity/config.go +++ b/pkg/tasks/check_consensus_identity/config.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" + "github.com/ethpandaops/assertoor/pkg/helper" ) type Config struct { diff --git a/pkg/coordinator/tasks/check_consensus_identity/task.go b/pkg/tasks/check_consensus_identity/task.go similarity index 98% rename from pkg/coordinator/tasks/check_consensus_identity/task.go rename to pkg/tasks/check_consensus_identity/task.go index 2df01bf1..ff89cded 100644 --- a/pkg/coordinator/tasks/check_consensus_identity/task.go +++ b/pkg/tasks/check_consensus_identity/task.go @@ -12,9 +12,9 @@ import ( "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/rlp" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/clients" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/check_consensus_proposer_duty/README.md b/pkg/tasks/check_consensus_proposer_duty/README.md similarity index 100% rename from pkg/coordinator/tasks/check_consensus_proposer_duty/README.md rename to pkg/tasks/check_consensus_proposer_duty/README.md diff --git a/pkg/coordinator/tasks/check_consensus_proposer_duty/config.go b/pkg/tasks/check_consensus_proposer_duty/config.go similarity index 100% rename from pkg/coordinator/tasks/check_consensus_proposer_duty/config.go rename to pkg/tasks/check_consensus_proposer_duty/config.go diff --git a/pkg/coordinator/tasks/check_consensus_proposer_duty/task.go b/pkg/tasks/check_consensus_proposer_duty/task.go similarity index 97% rename from pkg/coordinator/tasks/check_consensus_proposer_duty/task.go rename to pkg/tasks/check_consensus_proposer_duty/task.go index 66c5d85e..ce309271 100644 --- a/pkg/coordinator/tasks/check_consensus_proposer_duty/task.go +++ b/pkg/tasks/check_consensus_proposer_duty/task.go @@ -7,8 +7,8 @@ import ( "time" v1 "github.com/attestantio/go-eth2-client/api/v1" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/clients/consensus" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/check_consensus_reorgs/README.md b/pkg/tasks/check_consensus_reorgs/README.md similarity index 100% rename from pkg/coordinator/tasks/check_consensus_reorgs/README.md rename to pkg/tasks/check_consensus_reorgs/README.md diff --git a/pkg/coordinator/tasks/check_consensus_reorgs/config.go b/pkg/tasks/check_consensus_reorgs/config.go similarity index 100% rename from pkg/coordinator/tasks/check_consensus_reorgs/config.go rename to pkg/tasks/check_consensus_reorgs/config.go diff --git a/pkg/coordinator/tasks/check_consensus_reorgs/task.go b/pkg/tasks/check_consensus_reorgs/task.go similarity index 97% rename from pkg/coordinator/tasks/check_consensus_reorgs/task.go rename to pkg/tasks/check_consensus_reorgs/task.go index 103c08ae..3cf3b71a 100644 --- a/pkg/coordinator/tasks/check_consensus_reorgs/task.go +++ b/pkg/tasks/check_consensus_reorgs/task.go @@ -6,8 +6,8 @@ import ( "fmt" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/clients/consensus" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/check_consensus_slot_range/README.md b/pkg/tasks/check_consensus_slot_range/README.md similarity index 100% rename from pkg/coordinator/tasks/check_consensus_slot_range/README.md rename to pkg/tasks/check_consensus_slot_range/README.md diff --git a/pkg/coordinator/tasks/check_consensus_slot_range/config.go b/pkg/tasks/check_consensus_slot_range/config.go similarity index 100% rename from pkg/coordinator/tasks/check_consensus_slot_range/config.go rename to pkg/tasks/check_consensus_slot_range/config.go diff --git a/pkg/coordinator/tasks/check_consensus_slot_range/task.go b/pkg/tasks/check_consensus_slot_range/task.go similarity index 98% rename from pkg/coordinator/tasks/check_consensus_slot_range/task.go rename to pkg/tasks/check_consensus_slot_range/task.go index 021bf96a..f5c6fc30 100644 --- a/pkg/coordinator/tasks/check_consensus_slot_range/task.go +++ b/pkg/tasks/check_consensus_slot_range/task.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/check_consensus_sync_status/README.md b/pkg/tasks/check_consensus_sync_status/README.md similarity index 100% rename from pkg/coordinator/tasks/check_consensus_sync_status/README.md rename to pkg/tasks/check_consensus_sync_status/README.md diff --git a/pkg/coordinator/tasks/check_consensus_sync_status/config.go b/pkg/tasks/check_consensus_sync_status/config.go similarity index 96% rename from pkg/coordinator/tasks/check_consensus_sync_status/config.go rename to pkg/tasks/check_consensus_sync_status/config.go index ea59c11e..b7babb52 100644 --- a/pkg/coordinator/tasks/check_consensus_sync_status/config.go +++ b/pkg/tasks/check_consensus_sync_status/config.go @@ -4,7 +4,7 @@ import ( "errors" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" + "github.com/ethpandaops/assertoor/pkg/helper" ) type Config struct { diff --git a/pkg/coordinator/tasks/check_consensus_sync_status/task.go b/pkg/tasks/check_consensus_sync_status/task.go similarity index 95% rename from pkg/coordinator/tasks/check_consensus_sync_status/task.go rename to pkg/tasks/check_consensus_sync_status/task.go index 7df04f49..b8676edb 100644 --- a/pkg/coordinator/tasks/check_consensus_sync_status/task.go +++ b/pkg/tasks/check_consensus_sync_status/task.go @@ -5,10 +5,10 @@ import ( "fmt" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus/rpc" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/clients" + "github.com/ethpandaops/assertoor/pkg/clients/consensus/rpc" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/check_consensus_validator_status/README.md b/pkg/tasks/check_consensus_validator_status/README.md similarity index 100% rename from pkg/coordinator/tasks/check_consensus_validator_status/README.md rename to pkg/tasks/check_consensus_validator_status/README.md diff --git a/pkg/coordinator/tasks/check_consensus_validator_status/config.go b/pkg/tasks/check_consensus_validator_status/config.go similarity index 100% rename from pkg/coordinator/tasks/check_consensus_validator_status/config.go rename to pkg/tasks/check_consensus_validator_status/config.go diff --git a/pkg/coordinator/tasks/check_consensus_validator_status/task.go b/pkg/tasks/check_consensus_validator_status/task.go similarity index 98% rename from pkg/coordinator/tasks/check_consensus_validator_status/task.go rename to pkg/tasks/check_consensus_validator_status/task.go index ba24c24c..cb0c63f4 100644 --- a/pkg/coordinator/tasks/check_consensus_validator_status/task.go +++ b/pkg/tasks/check_consensus_validator_status/task.go @@ -10,8 +10,8 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/ethereum/go-ethereum/common" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/check_eth_call/README.md b/pkg/tasks/check_eth_call/README.md similarity index 100% rename from pkg/coordinator/tasks/check_eth_call/README.md rename to pkg/tasks/check_eth_call/README.md diff --git a/pkg/coordinator/tasks/check_eth_call/config.go b/pkg/tasks/check_eth_call/config.go similarity index 100% rename from pkg/coordinator/tasks/check_eth_call/config.go rename to pkg/tasks/check_eth_call/config.go diff --git a/pkg/coordinator/tasks/check_eth_call/task.go b/pkg/tasks/check_eth_call/task.go similarity index 98% rename from pkg/coordinator/tasks/check_eth_call/task.go rename to pkg/tasks/check_eth_call/task.go index 43c40eea..c6441f73 100644 --- a/pkg/coordinator/tasks/check_eth_call/task.go +++ b/pkg/tasks/check_eth_call/task.go @@ -9,9 +9,9 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution" + "github.com/ethpandaops/assertoor/pkg/clients/execution" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/check_eth_config/README.md b/pkg/tasks/check_eth_config/README.md similarity index 100% rename from pkg/coordinator/tasks/check_eth_config/README.md rename to pkg/tasks/check_eth_config/README.md diff --git a/pkg/coordinator/tasks/check_eth_config/config.go b/pkg/tasks/check_eth_config/config.go similarity index 100% rename from pkg/coordinator/tasks/check_eth_config/config.go rename to pkg/tasks/check_eth_config/config.go diff --git a/pkg/coordinator/tasks/check_eth_config/task.go b/pkg/tasks/check_eth_config/task.go similarity index 97% rename from pkg/coordinator/tasks/check_eth_config/task.go rename to pkg/tasks/check_eth_config/task.go index 2029d363..697c643b 100644 --- a/pkg/coordinator/tasks/check_eth_config/task.go +++ b/pkg/tasks/check_eth_config/task.go @@ -7,8 +7,8 @@ import ( "strings" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/clients/execution" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/check_execution_sync_status/README.md b/pkg/tasks/check_execution_sync_status/README.md similarity index 100% rename from pkg/coordinator/tasks/check_execution_sync_status/README.md rename to pkg/tasks/check_execution_sync_status/README.md diff --git a/pkg/coordinator/tasks/check_execution_sync_status/config.go b/pkg/tasks/check_execution_sync_status/config.go similarity index 95% rename from pkg/coordinator/tasks/check_execution_sync_status/config.go rename to pkg/tasks/check_execution_sync_status/config.go index 3930fa8d..6e1158ec 100644 --- a/pkg/coordinator/tasks/check_execution_sync_status/config.go +++ b/pkg/tasks/check_execution_sync_status/config.go @@ -4,7 +4,7 @@ import ( "errors" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" + "github.com/ethpandaops/assertoor/pkg/helper" ) type Config struct { diff --git a/pkg/coordinator/tasks/check_execution_sync_status/task.go b/pkg/tasks/check_execution_sync_status/task.go similarity index 95% rename from pkg/coordinator/tasks/check_execution_sync_status/task.go rename to pkg/tasks/check_execution_sync_status/task.go index a461bcfa..e11ac8be 100644 --- a/pkg/coordinator/tasks/check_execution_sync_status/task.go +++ b/pkg/tasks/check_execution_sync_status/task.go @@ -5,10 +5,10 @@ import ( "fmt" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution/rpc" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/clients" + "github.com/ethpandaops/assertoor/pkg/clients/execution/rpc" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/generate_attestations/README.md b/pkg/tasks/generate_attestations/README.md similarity index 100% rename from pkg/coordinator/tasks/generate_attestations/README.md rename to pkg/tasks/generate_attestations/README.md diff --git a/pkg/coordinator/tasks/generate_attestations/config.go b/pkg/tasks/generate_attestations/config.go similarity index 100% rename from pkg/coordinator/tasks/generate_attestations/config.go rename to pkg/tasks/generate_attestations/config.go diff --git a/pkg/coordinator/tasks/generate_attestations/task.go b/pkg/tasks/generate_attestations/task.go similarity index 99% rename from pkg/coordinator/tasks/generate_attestations/task.go rename to pkg/tasks/generate_attestations/task.go index 46f75fe8..e80c6ddc 100644 --- a/pkg/coordinator/tasks/generate_attestations/task.go +++ b/pkg/tasks/generate_attestations/task.go @@ -11,9 +11,9 @@ import ( v1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus/rpc" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/clients/consensus" + "github.com/ethpandaops/assertoor/pkg/clients/consensus/rpc" + "github.com/ethpandaops/assertoor/pkg/types" hbls "github.com/herumi/bls-eth-go-binary/bls" "github.com/protolambda/zrnt/eth2/beacon/common" "github.com/protolambda/ztyp/tree" diff --git a/pkg/coordinator/tasks/generate_blob_transactions/README.md b/pkg/tasks/generate_blob_transactions/README.md similarity index 100% rename from pkg/coordinator/tasks/generate_blob_transactions/README.md rename to pkg/tasks/generate_blob_transactions/README.md diff --git a/pkg/coordinator/tasks/generate_blob_transactions/config.go b/pkg/tasks/generate_blob_transactions/config.go similarity index 100% rename from pkg/coordinator/tasks/generate_blob_transactions/config.go rename to pkg/tasks/generate_blob_transactions/config.go diff --git a/pkg/coordinator/tasks/generate_blob_transactions/task.go b/pkg/tasks/generate_blob_transactions/task.go similarity index 97% rename from pkg/coordinator/tasks/generate_blob_transactions/task.go rename to pkg/tasks/generate_blob_transactions/task.go index 6eafe5f0..3892e671 100644 --- a/pkg/coordinator/tasks/generate_blob_transactions/task.go +++ b/pkg/tasks/generate_blob_transactions/task.go @@ -11,10 +11,10 @@ import ( "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/wallet" - "github.com/ethpandaops/assertoor/pkg/coordinator/wallet/blobtx" + "github.com/ethpandaops/assertoor/pkg/clients/execution" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/wallet" + "github.com/ethpandaops/assertoor/pkg/wallet/blobtx" "github.com/holiman/uint256" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/generate_bls_changes/README.md b/pkg/tasks/generate_bls_changes/README.md similarity index 100% rename from pkg/coordinator/tasks/generate_bls_changes/README.md rename to pkg/tasks/generate_bls_changes/README.md diff --git a/pkg/coordinator/tasks/generate_bls_changes/config.go b/pkg/tasks/generate_bls_changes/config.go similarity index 100% rename from pkg/coordinator/tasks/generate_bls_changes/config.go rename to pkg/tasks/generate_bls_changes/config.go diff --git a/pkg/coordinator/tasks/generate_bls_changes/task.go b/pkg/tasks/generate_bls_changes/task.go similarity index 97% rename from pkg/coordinator/tasks/generate_bls_changes/task.go rename to pkg/tasks/generate_bls_changes/task.go index 792cc89a..260a7e18 100644 --- a/pkg/coordinator/tasks/generate_bls_changes/task.go +++ b/pkg/tasks/generate_bls_changes/task.go @@ -11,9 +11,9 @@ import ( v1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec/capella" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/clients/consensus" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" hbls "github.com/herumi/bls-eth-go-binary/bls" "github.com/protolambda/zrnt/eth2/beacon/common" "github.com/protolambda/ztyp/tree" diff --git a/pkg/coordinator/tasks/generate_child_wallet/README.md b/pkg/tasks/generate_child_wallet/README.md similarity index 100% rename from pkg/coordinator/tasks/generate_child_wallet/README.md rename to pkg/tasks/generate_child_wallet/README.md diff --git a/pkg/coordinator/tasks/generate_child_wallet/config.go b/pkg/tasks/generate_child_wallet/config.go similarity index 100% rename from pkg/coordinator/tasks/generate_child_wallet/config.go rename to pkg/tasks/generate_child_wallet/config.go diff --git a/pkg/coordinator/tasks/generate_child_wallet/task.go b/pkg/tasks/generate_child_wallet/task.go similarity index 95% rename from pkg/coordinator/tasks/generate_child_wallet/task.go rename to pkg/tasks/generate_child_wallet/task.go index 8abfd132..2259c33f 100644 --- a/pkg/coordinator/tasks/generate_child_wallet/task.go +++ b/pkg/tasks/generate_child_wallet/task.go @@ -7,9 +7,9 @@ import ( "time" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" - "github.com/ethpandaops/assertoor/pkg/coordinator/wallet" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" + "github.com/ethpandaops/assertoor/pkg/wallet" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/generate_consolidations/README.md b/pkg/tasks/generate_consolidations/README.md similarity index 100% rename from pkg/coordinator/tasks/generate_consolidations/README.md rename to pkg/tasks/generate_consolidations/README.md diff --git a/pkg/coordinator/tasks/generate_consolidations/config.go b/pkg/tasks/generate_consolidations/config.go similarity index 100% rename from pkg/coordinator/tasks/generate_consolidations/config.go rename to pkg/tasks/generate_consolidations/config.go diff --git a/pkg/coordinator/tasks/generate_consolidations/task.go b/pkg/tasks/generate_consolidations/task.go similarity index 97% rename from pkg/coordinator/tasks/generate_consolidations/task.go rename to pkg/tasks/generate_consolidations/task.go index 26b00718..1b59e1b4 100644 --- a/pkg/coordinator/tasks/generate_consolidations/task.go +++ b/pkg/tasks/generate_consolidations/task.go @@ -17,10 +17,10 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/wallet" + "github.com/ethpandaops/assertoor/pkg/clients/consensus" + "github.com/ethpandaops/assertoor/pkg/clients/execution" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/wallet" "github.com/sirupsen/logrus" "github.com/tyler-smith/go-bip39" util "github.com/wealdtech/go-eth2-util" diff --git a/pkg/coordinator/tasks/generate_deposits/README.md b/pkg/tasks/generate_deposits/README.md similarity index 100% rename from pkg/coordinator/tasks/generate_deposits/README.md rename to pkg/tasks/generate_deposits/README.md diff --git a/pkg/coordinator/tasks/generate_deposits/config.go b/pkg/tasks/generate_deposits/config.go similarity index 100% rename from pkg/coordinator/tasks/generate_deposits/config.go rename to pkg/tasks/generate_deposits/config.go diff --git a/pkg/coordinator/tasks/generate_deposits/deposit_contract/DepositContract.abi b/pkg/tasks/generate_deposits/deposit_contract/DepositContract.abi similarity index 100% rename from pkg/coordinator/tasks/generate_deposits/deposit_contract/DepositContract.abi rename to pkg/tasks/generate_deposits/deposit_contract/DepositContract.abi diff --git a/pkg/coordinator/tasks/generate_deposits/deposit_contract/DepositContract.sol b/pkg/tasks/generate_deposits/deposit_contract/DepositContract.sol similarity index 100% rename from pkg/coordinator/tasks/generate_deposits/deposit_contract/DepositContract.sol rename to pkg/tasks/generate_deposits/deposit_contract/DepositContract.sol diff --git a/pkg/coordinator/tasks/generate_deposits/deposit_contract/deposit_contract.go b/pkg/tasks/generate_deposits/deposit_contract/deposit_contract.go similarity index 100% rename from pkg/coordinator/tasks/generate_deposits/deposit_contract/deposit_contract.go rename to pkg/tasks/generate_deposits/deposit_contract/deposit_contract.go diff --git a/pkg/coordinator/tasks/generate_deposits/task.go b/pkg/tasks/generate_deposits/task.go similarity index 97% rename from pkg/coordinator/tasks/generate_deposits/task.go rename to pkg/tasks/generate_deposits/task.go index 83d199cb..ba223a4b 100644 --- a/pkg/coordinator/tasks/generate_deposits/task.go +++ b/pkg/tasks/generate_deposits/task.go @@ -17,10 +17,10 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/wallet" + "github.com/ethpandaops/assertoor/pkg/clients/consensus" + "github.com/ethpandaops/assertoor/pkg/clients/execution" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/wallet" hbls "github.com/herumi/bls-eth-go-binary/bls" "github.com/protolambda/zrnt/eth2/beacon/common" "github.com/protolambda/zrnt/eth2/util/hashing" @@ -30,7 +30,7 @@ import ( e2types "github.com/wealdtech/go-eth2-types/v2" util "github.com/wealdtech/go-eth2-util" - depositcontract "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_deposits/deposit_contract" + depositcontract "github.com/ethpandaops/assertoor/pkg/tasks/generate_deposits/deposit_contract" ) var ( diff --git a/pkg/coordinator/tasks/generate_eoa_transactions/README.md b/pkg/tasks/generate_eoa_transactions/README.md similarity index 100% rename from pkg/coordinator/tasks/generate_eoa_transactions/README.md rename to pkg/tasks/generate_eoa_transactions/README.md diff --git a/pkg/coordinator/tasks/generate_eoa_transactions/config.go b/pkg/tasks/generate_eoa_transactions/config.go similarity index 100% rename from pkg/coordinator/tasks/generate_eoa_transactions/config.go rename to pkg/tasks/generate_eoa_transactions/config.go diff --git a/pkg/coordinator/tasks/generate_eoa_transactions/task.go b/pkg/tasks/generate_eoa_transactions/task.go similarity index 98% rename from pkg/coordinator/tasks/generate_eoa_transactions/task.go rename to pkg/tasks/generate_eoa_transactions/task.go index 222de87d..35d5730e 100644 --- a/pkg/coordinator/tasks/generate_eoa_transactions/task.go +++ b/pkg/tasks/generate_eoa_transactions/task.go @@ -13,9 +13,9 @@ import ( "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/wallet" + "github.com/ethpandaops/assertoor/pkg/clients/execution" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/wallet" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/generate_exits/README.md b/pkg/tasks/generate_exits/README.md similarity index 100% rename from pkg/coordinator/tasks/generate_exits/README.md rename to pkg/tasks/generate_exits/README.md diff --git a/pkg/coordinator/tasks/generate_exits/config.go b/pkg/tasks/generate_exits/config.go similarity index 100% rename from pkg/coordinator/tasks/generate_exits/config.go rename to pkg/tasks/generate_exits/config.go diff --git a/pkg/coordinator/tasks/generate_exits/task.go b/pkg/tasks/generate_exits/task.go similarity index 98% rename from pkg/coordinator/tasks/generate_exits/task.go rename to pkg/tasks/generate_exits/task.go index 83f6a0c7..331716f0 100644 --- a/pkg/coordinator/tasks/generate_exits/task.go +++ b/pkg/tasks/generate_exits/task.go @@ -10,8 +10,8 @@ import ( v1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/clients/consensus" + "github.com/ethpandaops/assertoor/pkg/types" hbls "github.com/herumi/bls-eth-go-binary/bls" "github.com/protolambda/zrnt/eth2/beacon/common" "github.com/protolambda/ztyp/tree" diff --git a/pkg/coordinator/tasks/generate_slashings/README.md b/pkg/tasks/generate_slashings/README.md similarity index 100% rename from pkg/coordinator/tasks/generate_slashings/README.md rename to pkg/tasks/generate_slashings/README.md diff --git a/pkg/coordinator/tasks/generate_slashings/config.go b/pkg/tasks/generate_slashings/config.go similarity index 100% rename from pkg/coordinator/tasks/generate_slashings/config.go rename to pkg/tasks/generate_slashings/config.go diff --git a/pkg/coordinator/tasks/generate_slashings/task.go b/pkg/tasks/generate_slashings/task.go similarity index 98% rename from pkg/coordinator/tasks/generate_slashings/task.go rename to pkg/tasks/generate_slashings/task.go index 22de3ba8..c873f1c1 100644 --- a/pkg/coordinator/tasks/generate_slashings/task.go +++ b/pkg/tasks/generate_slashings/task.go @@ -11,8 +11,8 @@ import ( v1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/clients/consensus" + "github.com/ethpandaops/assertoor/pkg/types" hbls "github.com/herumi/bls-eth-go-binary/bls" "github.com/protolambda/zrnt/eth2/beacon/common" "github.com/protolambda/ztyp/tree" diff --git a/pkg/coordinator/tasks/generate_transaction/README.md b/pkg/tasks/generate_transaction/README.md similarity index 100% rename from pkg/coordinator/tasks/generate_transaction/README.md rename to pkg/tasks/generate_transaction/README.md diff --git a/pkg/coordinator/tasks/generate_transaction/config.go b/pkg/tasks/generate_transaction/config.go similarity index 98% rename from pkg/coordinator/tasks/generate_transaction/config.go rename to pkg/tasks/generate_transaction/config.go index acbe00fc..88cd6219 100644 --- a/pkg/coordinator/tasks/generate_transaction/config.go +++ b/pkg/tasks/generate_transaction/config.go @@ -4,7 +4,7 @@ import ( "errors" "math/big" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" + "github.com/ethpandaops/assertoor/pkg/helper" ) type Config struct { diff --git a/pkg/coordinator/tasks/generate_transaction/task.go b/pkg/tasks/generate_transaction/task.go similarity index 97% rename from pkg/coordinator/tasks/generate_transaction/task.go rename to pkg/tasks/generate_transaction/task.go index c6d78ca2..cbebe4b0 100644 --- a/pkg/coordinator/tasks/generate_transaction/task.go +++ b/pkg/tasks/generate_transaction/task.go @@ -14,11 +14,11 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" - "github.com/ethpandaops/assertoor/pkg/coordinator/wallet" - "github.com/ethpandaops/assertoor/pkg/coordinator/wallet/blobtx" + "github.com/ethpandaops/assertoor/pkg/clients/execution" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" + "github.com/ethpandaops/assertoor/pkg/wallet" + "github.com/ethpandaops/assertoor/pkg/wallet/blobtx" "github.com/holiman/uint256" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/generate_withdrawal_requests/README.md b/pkg/tasks/generate_withdrawal_requests/README.md similarity index 100% rename from pkg/coordinator/tasks/generate_withdrawal_requests/README.md rename to pkg/tasks/generate_withdrawal_requests/README.md diff --git a/pkg/coordinator/tasks/generate_withdrawal_requests/config.go b/pkg/tasks/generate_withdrawal_requests/config.go similarity index 100% rename from pkg/coordinator/tasks/generate_withdrawal_requests/config.go rename to pkg/tasks/generate_withdrawal_requests/config.go diff --git a/pkg/coordinator/tasks/generate_withdrawal_requests/task.go b/pkg/tasks/generate_withdrawal_requests/task.go similarity index 97% rename from pkg/coordinator/tasks/generate_withdrawal_requests/task.go rename to pkg/tasks/generate_withdrawal_requests/task.go index 76c1f78a..f555369e 100644 --- a/pkg/coordinator/tasks/generate_withdrawal_requests/task.go +++ b/pkg/tasks/generate_withdrawal_requests/task.go @@ -18,10 +18,10 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/wallet" + "github.com/ethpandaops/assertoor/pkg/clients/consensus" + "github.com/ethpandaops/assertoor/pkg/clients/execution" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/wallet" "github.com/sirupsen/logrus" "github.com/tyler-smith/go-bip39" util "github.com/wealdtech/go-eth2-util" diff --git a/pkg/coordinator/tasks/get_consensus_specs/README.md b/pkg/tasks/get_consensus_specs/README.md similarity index 100% rename from pkg/coordinator/tasks/get_consensus_specs/README.md rename to pkg/tasks/get_consensus_specs/README.md diff --git a/pkg/coordinator/tasks/get_consensus_specs/config.go b/pkg/tasks/get_consensus_specs/config.go similarity index 100% rename from pkg/coordinator/tasks/get_consensus_specs/config.go rename to pkg/tasks/get_consensus_specs/config.go diff --git a/pkg/coordinator/tasks/get_consensus_specs/task.go b/pkg/tasks/get_consensus_specs/task.go similarity index 96% rename from pkg/coordinator/tasks/get_consensus_specs/task.go rename to pkg/tasks/get_consensus_specs/task.go index 7db40e84..e393c9d3 100644 --- a/pkg/coordinator/tasks/get_consensus_specs/task.go +++ b/pkg/tasks/get_consensus_specs/task.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/get_consensus_validators/README.md b/pkg/tasks/get_consensus_validators/README.md similarity index 100% rename from pkg/coordinator/tasks/get_consensus_validators/README.md rename to pkg/tasks/get_consensus_validators/README.md diff --git a/pkg/coordinator/tasks/get_consensus_validators/config.go b/pkg/tasks/get_consensus_validators/config.go similarity index 100% rename from pkg/coordinator/tasks/get_consensus_validators/config.go rename to pkg/tasks/get_consensus_validators/config.go diff --git a/pkg/coordinator/tasks/get_consensus_validators/task.go b/pkg/tasks/get_consensus_validators/task.go similarity index 98% rename from pkg/coordinator/tasks/get_consensus_validators/task.go rename to pkg/tasks/get_consensus_validators/task.go index 73f46332..78930d6b 100644 --- a/pkg/coordinator/tasks/get_consensus_validators/task.go +++ b/pkg/tasks/get_consensus_validators/task.go @@ -7,8 +7,8 @@ import ( "strings" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/get_execution_block/README.md b/pkg/tasks/get_execution_block/README.md similarity index 100% rename from pkg/coordinator/tasks/get_execution_block/README.md rename to pkg/tasks/get_execution_block/README.md diff --git a/pkg/coordinator/tasks/get_execution_block/config.go b/pkg/tasks/get_execution_block/config.go similarity index 100% rename from pkg/coordinator/tasks/get_execution_block/config.go rename to pkg/tasks/get_execution_block/config.go diff --git a/pkg/coordinator/tasks/get_execution_block/task.go b/pkg/tasks/get_execution_block/task.go similarity index 94% rename from pkg/coordinator/tasks/get_execution_block/task.go rename to pkg/tasks/get_execution_block/task.go index c9449a50..2840fa76 100644 --- a/pkg/coordinator/tasks/get_execution_block/task.go +++ b/pkg/tasks/get_execution_block/task.go @@ -8,8 +8,8 @@ import ( "github.com/sirupsen/logrus" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" ) var ( diff --git a/pkg/coordinator/tasks/get_pubkeys_from_mnemonic/README.md b/pkg/tasks/get_pubkeys_from_mnemonic/README.md similarity index 100% rename from pkg/coordinator/tasks/get_pubkeys_from_mnemonic/README.md rename to pkg/tasks/get_pubkeys_from_mnemonic/README.md diff --git a/pkg/coordinator/tasks/get_pubkeys_from_mnemonic/config.go b/pkg/tasks/get_pubkeys_from_mnemonic/config.go similarity index 100% rename from pkg/coordinator/tasks/get_pubkeys_from_mnemonic/config.go rename to pkg/tasks/get_pubkeys_from_mnemonic/config.go diff --git a/pkg/coordinator/tasks/get_pubkeys_from_mnemonic/task.go b/pkg/tasks/get_pubkeys_from_mnemonic/task.go similarity index 97% rename from pkg/coordinator/tasks/get_pubkeys_from_mnemonic/task.go rename to pkg/tasks/get_pubkeys_from_mnemonic/task.go index cd4506e3..f5a94b65 100644 --- a/pkg/coordinator/tasks/get_pubkeys_from_mnemonic/task.go +++ b/pkg/tasks/get_pubkeys_from_mnemonic/task.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/sirupsen/logrus" "github.com/tyler-smith/go-bip39" util "github.com/wealdtech/go-eth2-util" diff --git a/pkg/coordinator/tasks/get_random_mnemonic/README.md b/pkg/tasks/get_random_mnemonic/README.md similarity index 100% rename from pkg/coordinator/tasks/get_random_mnemonic/README.md rename to pkg/tasks/get_random_mnemonic/README.md diff --git a/pkg/coordinator/tasks/get_random_mnemonic/config.go b/pkg/tasks/get_random_mnemonic/config.go similarity index 100% rename from pkg/coordinator/tasks/get_random_mnemonic/config.go rename to pkg/tasks/get_random_mnemonic/config.go diff --git a/pkg/coordinator/tasks/get_random_mnemonic/task.go b/pkg/tasks/get_random_mnemonic/task.go similarity index 96% rename from pkg/coordinator/tasks/get_random_mnemonic/task.go rename to pkg/tasks/get_random_mnemonic/task.go index 290d5316..2b5e403a 100644 --- a/pkg/coordinator/tasks/get_random_mnemonic/task.go +++ b/pkg/tasks/get_random_mnemonic/task.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/sirupsen/logrus" "github.com/tyler-smith/go-bip39" ) diff --git a/pkg/coordinator/tasks/get_wallet_details/README.md b/pkg/tasks/get_wallet_details/README.md similarity index 100% rename from pkg/coordinator/tasks/get_wallet_details/README.md rename to pkg/tasks/get_wallet_details/README.md diff --git a/pkg/coordinator/tasks/get_wallet_details/config.go b/pkg/tasks/get_wallet_details/config.go similarity index 100% rename from pkg/coordinator/tasks/get_wallet_details/config.go rename to pkg/tasks/get_wallet_details/config.go diff --git a/pkg/coordinator/tasks/get_wallet_details/task.go b/pkg/tasks/get_wallet_details/task.go similarity index 94% rename from pkg/coordinator/tasks/get_wallet_details/task.go rename to pkg/tasks/get_wallet_details/task.go index 9f080fd8..f1422c97 100644 --- a/pkg/coordinator/tasks/get_wallet_details/task.go +++ b/pkg/tasks/get_wallet_details/task.go @@ -7,8 +7,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/wallet" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/wallet" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/info.go b/pkg/tasks/info.go similarity index 100% rename from pkg/coordinator/tasks/info.go rename to pkg/tasks/info.go diff --git a/pkg/coordinator/tasks/run_command/README.md b/pkg/tasks/run_command/README.md similarity index 100% rename from pkg/coordinator/tasks/run_command/README.md rename to pkg/tasks/run_command/README.md diff --git a/pkg/coordinator/tasks/run_command/config.go b/pkg/tasks/run_command/config.go similarity index 100% rename from pkg/coordinator/tasks/run_command/config.go rename to pkg/tasks/run_command/config.go diff --git a/pkg/coordinator/tasks/run_command/task.go b/pkg/tasks/run_command/task.go similarity index 97% rename from pkg/coordinator/tasks/run_command/task.go rename to pkg/tasks/run_command/task.go index f654b6f4..8b7ad695 100644 --- a/pkg/coordinator/tasks/run_command/task.go +++ b/pkg/tasks/run_command/task.go @@ -6,7 +6,7 @@ import ( "os/exec" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/run_external_tasks/README.md b/pkg/tasks/run_external_tasks/README.md similarity index 100% rename from pkg/coordinator/tasks/run_external_tasks/README.md rename to pkg/tasks/run_external_tasks/README.md diff --git a/pkg/coordinator/tasks/run_external_tasks/config.go b/pkg/tasks/run_external_tasks/config.go similarity index 100% rename from pkg/coordinator/tasks/run_external_tasks/config.go rename to pkg/tasks/run_external_tasks/config.go diff --git a/pkg/coordinator/tasks/run_external_tasks/task.go b/pkg/tasks/run_external_tasks/task.go similarity index 98% rename from pkg/coordinator/tasks/run_external_tasks/task.go rename to pkg/tasks/run_external_tasks/task.go index ed192e63..130b1cd0 100644 --- a/pkg/coordinator/tasks/run_external_tasks/task.go +++ b/pkg/tasks/run_external_tasks/task.go @@ -11,8 +11,8 @@ import ( "strings" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" "github.com/sirupsen/logrus" "gopkg.in/yaml.v2" ) diff --git a/pkg/coordinator/tasks/run_shell/README.md b/pkg/tasks/run_shell/README.md similarity index 100% rename from pkg/coordinator/tasks/run_shell/README.md rename to pkg/tasks/run_shell/README.md diff --git a/pkg/coordinator/tasks/run_shell/config.go b/pkg/tasks/run_shell/config.go similarity index 100% rename from pkg/coordinator/tasks/run_shell/config.go rename to pkg/tasks/run_shell/config.go diff --git a/pkg/coordinator/tasks/run_shell/result_file.go b/pkg/tasks/run_shell/result_file.go similarity index 100% rename from pkg/coordinator/tasks/run_shell/result_file.go rename to pkg/tasks/run_shell/result_file.go diff --git a/pkg/coordinator/tasks/run_shell/task.go b/pkg/tasks/run_shell/task.go similarity index 98% rename from pkg/coordinator/tasks/run_shell/task.go rename to pkg/tasks/run_shell/task.go index 0bf770d4..27510001 100644 --- a/pkg/coordinator/tasks/run_shell/task.go +++ b/pkg/tasks/run_shell/task.go @@ -13,8 +13,8 @@ import ( "syscall" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/db" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/jmoiron/sqlx" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/run_task_background/README.md b/pkg/tasks/run_task_background/README.md similarity index 100% rename from pkg/coordinator/tasks/run_task_background/README.md rename to pkg/tasks/run_task_background/README.md diff --git a/pkg/coordinator/tasks/run_task_background/config.go b/pkg/tasks/run_task_background/config.go similarity index 94% rename from pkg/coordinator/tasks/run_task_background/config.go rename to pkg/tasks/run_task_background/config.go index ed61111b..cfcd1496 100644 --- a/pkg/coordinator/tasks/run_task_background/config.go +++ b/pkg/tasks/run_task_background/config.go @@ -3,7 +3,7 @@ package runtaskbackground import ( "errors" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" + "github.com/ethpandaops/assertoor/pkg/helper" ) type Config struct { diff --git a/pkg/coordinator/tasks/run_task_background/task.go b/pkg/tasks/run_task_background/task.go similarity index 98% rename from pkg/coordinator/tasks/run_task_background/task.go rename to pkg/tasks/run_task_background/task.go index 5861bccc..243b6a80 100644 --- a/pkg/coordinator/tasks/run_task_background/task.go +++ b/pkg/tasks/run_task_background/task.go @@ -6,8 +6,8 @@ import ( "sync" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/run_task_matrix/README.md b/pkg/tasks/run_task_matrix/README.md similarity index 100% rename from pkg/coordinator/tasks/run_task_matrix/README.md rename to pkg/tasks/run_task_matrix/README.md diff --git a/pkg/coordinator/tasks/run_task_matrix/config.go b/pkg/tasks/run_task_matrix/config.go similarity index 94% rename from pkg/coordinator/tasks/run_task_matrix/config.go rename to pkg/tasks/run_task_matrix/config.go index 5a95b9bc..79940b90 100644 --- a/pkg/coordinator/tasks/run_task_matrix/config.go +++ b/pkg/tasks/run_task_matrix/config.go @@ -3,7 +3,7 @@ package runtaskmatrix import ( "errors" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" + "github.com/ethpandaops/assertoor/pkg/helper" ) type Config struct { diff --git a/pkg/coordinator/tasks/run_task_matrix/task.go b/pkg/tasks/run_task_matrix/task.go similarity index 98% rename from pkg/coordinator/tasks/run_task_matrix/task.go rename to pkg/tasks/run_task_matrix/task.go index bb1f0e02..2449152f 100644 --- a/pkg/coordinator/tasks/run_task_matrix/task.go +++ b/pkg/tasks/run_task_matrix/task.go @@ -6,8 +6,8 @@ import ( "sync" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/run_task_options/README.md b/pkg/tasks/run_task_options/README.md similarity index 100% rename from pkg/coordinator/tasks/run_task_options/README.md rename to pkg/tasks/run_task_options/README.md diff --git a/pkg/coordinator/tasks/run_task_options/config.go b/pkg/tasks/run_task_options/config.go similarity index 93% rename from pkg/coordinator/tasks/run_task_options/config.go rename to pkg/tasks/run_task_options/config.go index 2ef23bff..448a0ce8 100644 --- a/pkg/coordinator/tasks/run_task_options/config.go +++ b/pkg/tasks/run_task_options/config.go @@ -3,7 +3,7 @@ package runtaskoptions import ( "errors" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" + "github.com/ethpandaops/assertoor/pkg/helper" ) type Config struct { diff --git a/pkg/coordinator/tasks/run_task_options/task.go b/pkg/tasks/run_task_options/task.go similarity index 97% rename from pkg/coordinator/tasks/run_task_options/task.go rename to pkg/tasks/run_task_options/task.go index d5de2696..17eb1967 100644 --- a/pkg/coordinator/tasks/run_task_options/task.go +++ b/pkg/tasks/run_task_options/task.go @@ -5,8 +5,8 @@ import ( "fmt" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/run_tasks/README.md b/pkg/tasks/run_tasks/README.md similarity index 100% rename from pkg/coordinator/tasks/run_tasks/README.md rename to pkg/tasks/run_tasks/README.md diff --git a/pkg/coordinator/tasks/run_tasks/config.go b/pkg/tasks/run_tasks/config.go similarity index 92% rename from pkg/coordinator/tasks/run_tasks/config.go rename to pkg/tasks/run_tasks/config.go index 7d1a05c0..bfca3d67 100644 --- a/pkg/coordinator/tasks/run_tasks/config.go +++ b/pkg/tasks/run_tasks/config.go @@ -3,7 +3,7 @@ package runtasks import ( "errors" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" + "github.com/ethpandaops/assertoor/pkg/helper" ) type Config struct { diff --git a/pkg/coordinator/tasks/run_tasks/task.go b/pkg/tasks/run_tasks/task.go similarity index 97% rename from pkg/coordinator/tasks/run_tasks/task.go rename to pkg/tasks/run_tasks/task.go index 7607bd9b..99dd30c9 100644 --- a/pkg/coordinator/tasks/run_tasks/task.go +++ b/pkg/tasks/run_tasks/task.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/run_tasks_concurrent/README.md b/pkg/tasks/run_tasks_concurrent/README.md similarity index 100% rename from pkg/coordinator/tasks/run_tasks_concurrent/README.md rename to pkg/tasks/run_tasks_concurrent/README.md diff --git a/pkg/coordinator/tasks/run_tasks_concurrent/config.go b/pkg/tasks/run_tasks_concurrent/config.go similarity index 94% rename from pkg/coordinator/tasks/run_tasks_concurrent/config.go rename to pkg/tasks/run_tasks_concurrent/config.go index 9a529821..5cf6d345 100644 --- a/pkg/coordinator/tasks/run_tasks_concurrent/config.go +++ b/pkg/tasks/run_tasks_concurrent/config.go @@ -3,7 +3,7 @@ package runtasksconcurrent import ( "errors" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" + "github.com/ethpandaops/assertoor/pkg/helper" ) type Config struct { diff --git a/pkg/coordinator/tasks/run_tasks_concurrent/task.go b/pkg/tasks/run_tasks_concurrent/task.go similarity index 98% rename from pkg/coordinator/tasks/run_tasks_concurrent/task.go rename to pkg/tasks/run_tasks_concurrent/task.go index 16e4477a..a296b41f 100644 --- a/pkg/coordinator/tasks/run_tasks_concurrent/task.go +++ b/pkg/tasks/run_tasks_concurrent/task.go @@ -6,8 +6,8 @@ import ( "sync" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/tasks/sleep/README.md b/pkg/tasks/sleep/README.md similarity index 100% rename from pkg/coordinator/tasks/sleep/README.md rename to pkg/tasks/sleep/README.md diff --git a/pkg/coordinator/tasks/sleep/config.go b/pkg/tasks/sleep/config.go similarity index 84% rename from pkg/coordinator/tasks/sleep/config.go rename to pkg/tasks/sleep/config.go index e6cc1d16..bec62d55 100644 --- a/pkg/coordinator/tasks/sleep/config.go +++ b/pkg/tasks/sleep/config.go @@ -3,7 +3,7 @@ package sleep import ( "errors" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" + "github.com/ethpandaops/assertoor/pkg/helper" ) type Config struct { diff --git a/pkg/coordinator/tasks/sleep/task.go b/pkg/tasks/sleep/task.go similarity index 96% rename from pkg/coordinator/tasks/sleep/task.go rename to pkg/tasks/sleep/task.go index 270e8d43..b95f9494 100644 --- a/pkg/coordinator/tasks/sleep/task.go +++ b/pkg/tasks/sleep/task.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/sirupsen/logrus" ) diff --git a/pkg/tasks/tasks.go b/pkg/tasks/tasks.go new file mode 100644 index 00000000..713c6340 --- /dev/null +++ b/pkg/tasks/tasks.go @@ -0,0 +1,119 @@ +package tasks + +import ( + "github.com/ethpandaops/assertoor/pkg/types" + + checkclientsarehealthy "github.com/ethpandaops/assertoor/pkg/tasks/check_clients_are_healthy" + checkconsensusattestationstats "github.com/ethpandaops/assertoor/pkg/tasks/check_consensus_attestation_stats" + checkconsensusblockproposals "github.com/ethpandaops/assertoor/pkg/tasks/check_consensus_block_proposals" + checkconsensusfinality "github.com/ethpandaops/assertoor/pkg/tasks/check_consensus_finality" + checkconsensusforks "github.com/ethpandaops/assertoor/pkg/tasks/check_consensus_forks" + checkconsensusidentity "github.com/ethpandaops/assertoor/pkg/tasks/check_consensus_identity" + checkconsensusproposerduty "github.com/ethpandaops/assertoor/pkg/tasks/check_consensus_proposer_duty" + checkconsensusreorgs "github.com/ethpandaops/assertoor/pkg/tasks/check_consensus_reorgs" + checkconsensusslotrange "github.com/ethpandaops/assertoor/pkg/tasks/check_consensus_slot_range" + checkconsensussyncstatus "github.com/ethpandaops/assertoor/pkg/tasks/check_consensus_sync_status" + checkconsensusvalidatorstatus "github.com/ethpandaops/assertoor/pkg/tasks/check_consensus_validator_status" + checkethcall "github.com/ethpandaops/assertoor/pkg/tasks/check_eth_call" + checkethconfig "github.com/ethpandaops/assertoor/pkg/tasks/check_eth_config" + checkexecutionsyncstatus "github.com/ethpandaops/assertoor/pkg/tasks/check_execution_sync_status" + generateattestations "github.com/ethpandaops/assertoor/pkg/tasks/generate_attestations" + generateblobtransactions "github.com/ethpandaops/assertoor/pkg/tasks/generate_blob_transactions" + generateblschanges "github.com/ethpandaops/assertoor/pkg/tasks/generate_bls_changes" + generatechildwallet "github.com/ethpandaops/assertoor/pkg/tasks/generate_child_wallet" + generateconsolidations "github.com/ethpandaops/assertoor/pkg/tasks/generate_consolidations" + generatedeposits "github.com/ethpandaops/assertoor/pkg/tasks/generate_deposits" + generateeoatransactions "github.com/ethpandaops/assertoor/pkg/tasks/generate_eoa_transactions" + generateexits "github.com/ethpandaops/assertoor/pkg/tasks/generate_exits" + generateslashings "github.com/ethpandaops/assertoor/pkg/tasks/generate_slashings" + generatetransaction "github.com/ethpandaops/assertoor/pkg/tasks/generate_transaction" + generatewithdrawalrequests "github.com/ethpandaops/assertoor/pkg/tasks/generate_withdrawal_requests" + getconsensusspecs "github.com/ethpandaops/assertoor/pkg/tasks/get_consensus_specs" + getconsensusvalidators "github.com/ethpandaops/assertoor/pkg/tasks/get_consensus_validators" + checkexecutionblock "github.com/ethpandaops/assertoor/pkg/tasks/get_execution_block" + getpubkeysfrommnemonic "github.com/ethpandaops/assertoor/pkg/tasks/get_pubkeys_from_mnemonic" + getrandommnemonic "github.com/ethpandaops/assertoor/pkg/tasks/get_random_mnemonic" + getwalletdetails "github.com/ethpandaops/assertoor/pkg/tasks/get_wallet_details" + runcommand "github.com/ethpandaops/assertoor/pkg/tasks/run_command" + runexternaltasks "github.com/ethpandaops/assertoor/pkg/tasks/run_external_tasks" + runshell "github.com/ethpandaops/assertoor/pkg/tasks/run_shell" + runtaskbackground "github.com/ethpandaops/assertoor/pkg/tasks/run_task_background" + runtaskmatrix "github.com/ethpandaops/assertoor/pkg/tasks/run_task_matrix" + runtaskoptions "github.com/ethpandaops/assertoor/pkg/tasks/run_task_options" + runtasks "github.com/ethpandaops/assertoor/pkg/tasks/run_tasks" + runtasksconcurrent "github.com/ethpandaops/assertoor/pkg/tasks/run_tasks_concurrent" + sleep "github.com/ethpandaops/assertoor/pkg/tasks/sleep" +) + +var AvailableTaskDescriptors = []*types.TaskDescriptor{ + checkclientsarehealthy.TaskDescriptor, + checkconsensusattestationstats.TaskDescriptor, + checkconsensusblockproposals.TaskDescriptor, + checkconsensusfinality.TaskDescriptor, + checkconsensusforks.TaskDescriptor, + checkconsensusidentity.TaskDescriptor, + checkconsensusproposerduty.TaskDescriptor, + checkconsensusreorgs.TaskDescriptor, + checkconsensusslotrange.TaskDescriptor, + checkconsensussyncstatus.TaskDescriptor, + checkconsensusvalidatorstatus.TaskDescriptor, + checkexecutionblock.TaskDescriptor, + checkethcall.TaskDescriptor, + checkethconfig.TaskDescriptor, + checkexecutionsyncstatus.TaskDescriptor, + generateattestations.TaskDescriptor, + generateblobtransactions.TaskDescriptor, + generateblschanges.TaskDescriptor, + generatechildwallet.TaskDescriptor, + generateconsolidations.TaskDescriptor, + generateeoatransactions.TaskDescriptor, + generatedeposits.TaskDescriptor, + generateexits.TaskDescriptor, + generateslashings.TaskDescriptor, + generatetransaction.TaskDescriptor, + generatewithdrawalrequests.TaskDescriptor, + getpubkeysfrommnemonic.TaskDescriptor, + getconsensusspecs.TaskDescriptor, + getconsensusvalidators.TaskDescriptor, + getrandommnemonic.TaskDescriptor, + getwalletdetails.TaskDescriptor, + runcommand.TaskDescriptor, + runexternaltasks.TaskDescriptor, + runshell.TaskDescriptor, + runtaskbackground.TaskDescriptor, + runtaskmatrix.TaskDescriptor, + runtaskoptions.TaskDescriptor, + runtasks.TaskDescriptor, + runtasksconcurrent.TaskDescriptor, + sleep.TaskDescriptor, +} + +func GetTaskDescriptor(name string) *types.TaskDescriptor { + // lookup task descriptor by name + var taskDescriptor *types.TaskDescriptor + + for _, taskDesc := range AvailableTaskDescriptors { + if taskDesc.Name == name { + taskDescriptor = taskDesc + break + } + + if len(taskDesc.Aliases) > 0 { + isAlias := false + + for _, alias := range taskDesc.Aliases { + if alias == name { + isAlias = true + break + } + } + + if isAlias { + taskDescriptor = taskDesc + break + } + } + } + + return taskDescriptor +} diff --git a/pkg/coordinator/test/dbtest.go b/pkg/test/dbtest.go similarity index 96% rename from pkg/coordinator/test/dbtest.go rename to pkg/test/dbtest.go index 94b0aa18..75703183 100644 --- a/pkg/coordinator/test/dbtest.go +++ b/pkg/test/dbtest.go @@ -6,11 +6,11 @@ import ( "sync" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/db" - "github.com/ethpandaops/assertoor/pkg/coordinator/logger" - "github.com/ethpandaops/assertoor/pkg/coordinator/tasks" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/logger" + "github.com/ethpandaops/assertoor/pkg/tasks" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" "gopkg.in/yaml.v3" ) diff --git a/pkg/coordinator/test/descriptor.go b/pkg/test/descriptor.go similarity index 98% rename from pkg/coordinator/test/descriptor.go rename to pkg/test/descriptor.go index e1e9979f..799c8abc 100644 --- a/pkg/coordinator/test/descriptor.go +++ b/pkg/test/descriptor.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" ) diff --git a/pkg/coordinator/test/test.go b/pkg/test/test.go similarity index 95% rename from pkg/coordinator/test/test.go rename to pkg/test/test.go index 704e6767..11ee4e11 100644 --- a/pkg/coordinator/test/test.go +++ b/pkg/test/test.go @@ -5,12 +5,12 @@ import ( "fmt" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/db" - "github.com/ethpandaops/assertoor/pkg/coordinator/logger" - "github.com/ethpandaops/assertoor/pkg/coordinator/scheduler" - "github.com/ethpandaops/assertoor/pkg/coordinator/tasks" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/vars" + "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/logger" + "github.com/ethpandaops/assertoor/pkg/scheduler" + "github.com/ethpandaops/assertoor/pkg/tasks" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/vars" "github.com/jmoiron/sqlx" "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" diff --git a/pkg/coordinator/types/coordinator.go b/pkg/types/coordinator.go similarity index 76% rename from pkg/coordinator/types/coordinator.go rename to pkg/types/coordinator.go index 229872ca..9660fd35 100644 --- a/pkg/coordinator/types/coordinator.go +++ b/pkg/types/coordinator.go @@ -3,11 +3,11 @@ package types import ( "context" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients" - "github.com/ethpandaops/assertoor/pkg/coordinator/db" - "github.com/ethpandaops/assertoor/pkg/coordinator/logger" - "github.com/ethpandaops/assertoor/pkg/coordinator/names" - "github.com/ethpandaops/assertoor/pkg/coordinator/wallet" + "github.com/ethpandaops/assertoor/pkg/clients" + "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/logger" + "github.com/ethpandaops/assertoor/pkg/names" + "github.com/ethpandaops/assertoor/pkg/wallet" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/types/scheduler.go b/pkg/types/scheduler.go similarity index 74% rename from pkg/coordinator/types/scheduler.go rename to pkg/types/scheduler.go index c9999a47..6d43ee24 100644 --- a/pkg/coordinator/types/scheduler.go +++ b/pkg/types/scheduler.go @@ -3,11 +3,11 @@ package types import ( "context" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients" - "github.com/ethpandaops/assertoor/pkg/coordinator/db" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" - "github.com/ethpandaops/assertoor/pkg/coordinator/names" - "github.com/ethpandaops/assertoor/pkg/coordinator/wallet" + "github.com/ethpandaops/assertoor/pkg/clients" + "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/helper" + "github.com/ethpandaops/assertoor/pkg/names" + "github.com/ethpandaops/assertoor/pkg/wallet" ) type TaskSchedulerRunner interface { diff --git a/pkg/coordinator/types/task.go b/pkg/types/task.go similarity index 94% rename from pkg/coordinator/types/task.go rename to pkg/types/task.go index b1e5467a..01fe65eb 100644 --- a/pkg/coordinator/types/task.go +++ b/pkg/types/task.go @@ -4,8 +4,8 @@ import ( "context" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" - "github.com/ethpandaops/assertoor/pkg/coordinator/logger" + "github.com/ethpandaops/assertoor/pkg/helper" + "github.com/ethpandaops/assertoor/pkg/logger" ) type TaskDescriptor struct { diff --git a/pkg/coordinator/types/test.go b/pkg/types/test.go similarity index 97% rename from pkg/coordinator/types/test.go rename to pkg/types/test.go index 02f4287d..224e70f6 100644 --- a/pkg/coordinator/types/test.go +++ b/pkg/types/test.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" + "github.com/ethpandaops/assertoor/pkg/helper" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/types/vars.go b/pkg/types/vars.go similarity index 100% rename from pkg/coordinator/types/vars.go rename to pkg/types/vars.go diff --git a/pkg/coordinator/vars/scope_filter.go b/pkg/vars/scope_filter.go similarity index 96% rename from pkg/coordinator/vars/scope_filter.go rename to pkg/vars/scope_filter.go index fa2a6aa1..2d8bb1ab 100644 --- a/pkg/coordinator/vars/scope_filter.go +++ b/pkg/vars/scope_filter.go @@ -1,7 +1,7 @@ package vars import ( - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" ) type ScopeFilter struct { diff --git a/pkg/coordinator/vars/utils.go b/pkg/vars/utils.go similarity index 100% rename from pkg/coordinator/vars/utils.go rename to pkg/vars/utils.go diff --git a/pkg/coordinator/vars/variables.go b/pkg/vars/variables.go similarity index 99% rename from pkg/coordinator/vars/variables.go rename to pkg/vars/variables.go index 2c7ccef9..56ceac7a 100644 --- a/pkg/coordinator/vars/variables.go +++ b/pkg/vars/variables.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/itchyny/gojq" "gopkg.in/yaml.v3" ) diff --git a/pkg/coordinator/wallet/blobtx/blob_encode.go b/pkg/wallet/blobtx/blob_encode.go similarity index 100% rename from pkg/coordinator/wallet/blobtx/blob_encode.go rename to pkg/wallet/blobtx/blob_encode.go diff --git a/pkg/coordinator/wallet/blobtx/blobtx.go b/pkg/wallet/blobtx/blobtx.go similarity index 100% rename from pkg/coordinator/wallet/blobtx/blobtx.go rename to pkg/wallet/blobtx/blobtx.go diff --git a/pkg/coordinator/wallet/manager.go b/pkg/wallet/manager.go similarity index 98% rename from pkg/coordinator/wallet/manager.go rename to pkg/wallet/manager.go index 15176335..d3970597 100644 --- a/pkg/coordinator/wallet/manager.go +++ b/pkg/wallet/manager.go @@ -11,7 +11,7 @@ import ( "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution" + "github.com/ethpandaops/assertoor/pkg/clients/execution" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/wallet/wallet.go b/pkg/wallet/wallet.go similarity index 99% rename from pkg/coordinator/wallet/wallet.go rename to pkg/wallet/wallet.go index d923997d..2eed3969 100644 --- a/pkg/coordinator/wallet/wallet.go +++ b/pkg/wallet/wallet.go @@ -14,7 +14,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution" + "github.com/ethpandaops/assertoor/pkg/clients/execution" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/wallet/walletpool.go b/pkg/wallet/walletpool.go similarity index 100% rename from pkg/coordinator/wallet/walletpool.go rename to pkg/wallet/walletpool.go diff --git a/pkg/coordinator/web/api/constants.go b/pkg/web/api/constants.go similarity index 100% rename from pkg/coordinator/web/api/constants.go rename to pkg/web/api/constants.go diff --git a/pkg/coordinator/web/api/docs/docs.go b/pkg/web/api/docs/docs.go similarity index 85% rename from pkg/coordinator/web/api/docs/docs.go rename to pkg/web/api/docs/docs.go index 5c8de73b..2cea98cb 100644 --- a/pkg/coordinator/web/api/docs/docs.go +++ b/pkg/web/api/docs/docs.go @@ -41,13 +41,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.GetTestResponse" + "$ref": "#/definitions/web_api.GetTestResponse" } } } @@ -57,13 +57,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -95,13 +95,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.GetTestRunResponse" + "$ref": "#/definitions/web_api.GetTestRunResponse" } } } @@ -111,13 +111,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -148,7 +148,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api.PostTestRunCancelRequest" + "$ref": "#/definitions/web_api.PostTestRunCancelRequest" } } ], @@ -158,13 +158,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.PostTestRunCancelResponse" + "$ref": "#/definitions/web_api.PostTestRunCancelResponse" } } } @@ -174,13 +174,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -212,13 +212,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.GetTestRunDetailsResponse" + "$ref": "#/definitions/web_api.GetTestRunDetailsResponse" } } } @@ -228,13 +228,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -266,7 +266,7 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", @@ -282,13 +282,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -327,13 +327,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.GetTestRunDetailedTask" + "$ref": "#/definitions/web_api.GetTestRunDetailedTask" } } } @@ -343,19 +343,19 @@ const docTemplate = `{ "400": { "description": "Bad Request", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "404": { "description": "Not Found", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -412,19 +412,19 @@ const docTemplate = `{ "400": { "description": "Bad Request", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "404": { "description": "Not Found", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -455,7 +455,7 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", @@ -463,7 +463,7 @@ const docTemplate = `{ "data": { "type": "array", "items": { - "$ref": "#/definitions/api.GetTestRunsResponse" + "$ref": "#/definitions/web_api.GetTestRunsResponse" } } } @@ -474,13 +474,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -508,7 +508,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api.PostTestRunsDeleteRequest" + "$ref": "#/definitions/web_api.PostTestRunsDeleteRequest" } } ], @@ -518,13 +518,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.PostTestRunsDeleteResponse" + "$ref": "#/definitions/web_api.PostTestRunsDeleteResponse" } } } @@ -534,13 +534,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -564,7 +564,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api.PostTestRunsScheduleRequest" + "$ref": "#/definitions/web_api.PostTestRunsScheduleRequest" } } ], @@ -574,13 +574,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.PostTestRunsScheduleResponse" + "$ref": "#/definitions/web_api.PostTestRunsScheduleResponse" } } } @@ -590,13 +590,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -619,7 +619,7 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", @@ -627,7 +627,7 @@ const docTemplate = `{ "data": { "type": "array", "items": { - "$ref": "#/definitions/api.GetTestsResponse" + "$ref": "#/definitions/web_api.GetTestsResponse" } } } @@ -638,13 +638,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -672,7 +672,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api.PostTestsDeleteRequest" + "$ref": "#/definitions/web_api.PostTestsDeleteRequest" } } ], @@ -682,13 +682,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.PostTestsDeleteResponse" + "$ref": "#/definitions/web_api.PostTestsDeleteResponse" } } } @@ -698,13 +698,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -732,7 +732,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api.PostTestsRegisterRequest" + "$ref": "#/definitions/web_api.PostTestsRegisterRequest" } } ], @@ -742,13 +742,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.PostTestsRegisterResponse" + "$ref": "#/definitions/web_api.PostTestsRegisterResponse" } } } @@ -758,13 +758,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -791,7 +791,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api.PostTestsRegisterExternalRequest" + "$ref": "#/definitions/web_api.PostTestsRegisterExternalRequest" } } ], @@ -801,13 +801,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.PostTestsRegisterExternalResponse" + "$ref": "#/definitions/web_api.PostTestsRegisterExternalResponse" } } } @@ -817,13 +817,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -831,9 +831,51 @@ const docTemplate = `{ } }, "definitions": { - "api.GetTestResponse": { + "helper.RawMessage": { + "type": "object" + }, + "types.TestSchedule": { "type": "object", "properties": { + "cron": { + "type": "array", + "items": { + "type": "string" + } + }, + "skipQueue": { + "type": "boolean" + }, + "startup": { + "type": "boolean" + } + } + }, + "types.TestStatus": { + "type": "string", + "enum": [ + "pending", + "running", + "success", + "failure", + "skipped", + "aborted" + ], + "x-enum-varnames": [ + "TestStatusPending", + "TestStatusRunning", + "TestStatusSuccess", + "TestStatusFailure", + "TestStatusSkipped", + "TestStatusAborted" + ] + }, + "web_api.GetTestResponse": { + "type": "object", + "properties": { + "basePath": { + "type": "string" + }, "config": { "type": "object", "additionalProperties": {} @@ -861,7 +903,7 @@ const docTemplate = `{ } } }, - "api.GetTestRunDetailedTask": { + "web_api.GetTestRunDetailedTask": { "type": "object", "properties": { "completed": { @@ -876,7 +918,7 @@ const docTemplate = `{ "log": { "type": "array", "items": { - "$ref": "#/definitions/api.GetTestRunDetailedTaskLog" + "$ref": "#/definitions/web_api.GetTestRunDetailedTaskLog" } }, "name": { @@ -917,7 +959,7 @@ const docTemplate = `{ } } }, - "api.GetTestRunDetailedTaskLog": { + "web_api.GetTestRunDetailedTaskLog": { "type": "object", "properties": { "data": { @@ -940,7 +982,7 @@ const docTemplate = `{ } } }, - "api.GetTestRunDetailsResponse": { + "web_api.GetTestRunDetailsResponse": { "type": "object", "properties": { "name": { @@ -961,7 +1003,7 @@ const docTemplate = `{ "tasks": { "type": "array", "items": { - "$ref": "#/definitions/api.GetTestRunDetailedTask" + "$ref": "#/definitions/web_api.GetTestRunDetailedTask" } }, "test_id": { @@ -969,7 +1011,7 @@ const docTemplate = `{ } } }, - "api.GetTestRunResponse": { + "web_api.GetTestRunResponse": { "type": "object", "properties": { "name": { @@ -990,7 +1032,7 @@ const docTemplate = `{ "tasks": { "type": "array", "items": { - "$ref": "#/definitions/api.GetTestRunTask" + "$ref": "#/definitions/web_api.GetTestRunTask" } }, "test_id": { @@ -998,7 +1040,7 @@ const docTemplate = `{ } } }, - "api.GetTestRunTask": { + "web_api.GetTestRunTask": { "type": "object", "properties": { "completed": { @@ -1022,7 +1064,7 @@ const docTemplate = `{ "result_files": { "type": "array", "items": { - "$ref": "#/definitions/api.GetTestRunTaskResult" + "$ref": "#/definitions/web_api.GetTestRunTaskResult" } }, "runtime": { @@ -1048,7 +1090,7 @@ const docTemplate = `{ } } }, - "api.GetTestRunTaskResult": { + "web_api.GetTestRunTaskResult": { "type": "object", "properties": { "index": { @@ -1068,7 +1110,7 @@ const docTemplate = `{ } } }, - "api.GetTestRunsResponse": { + "web_api.GetTestRunsResponse": { "type": "object", "properties": { "name": { @@ -1091,9 +1133,12 @@ const docTemplate = `{ } } }, - "api.GetTestsResponse": { + "web_api.GetTestsResponse": { "type": "object", "properties": { + "basePath": { + "type": "string" + }, "id": { "type": "string" }, @@ -1105,7 +1150,7 @@ const docTemplate = `{ } } }, - "api.PostTestRunCancelRequest": { + "web_api.PostTestRunCancelRequest": { "type": "object", "properties": { "skip_cleanup": { @@ -1116,7 +1161,7 @@ const docTemplate = `{ } } }, - "api.PostTestRunCancelResponse": { + "web_api.PostTestRunCancelResponse": { "type": "object", "properties": { "name": { @@ -1133,7 +1178,7 @@ const docTemplate = `{ } } }, - "api.PostTestRunsDeleteRequest": { + "web_api.PostTestRunsDeleteRequest": { "type": "object", "properties": { "test_runs": { @@ -1144,7 +1189,7 @@ const docTemplate = `{ } } }, - "api.PostTestRunsDeleteResponse": { + "web_api.PostTestRunsDeleteResponse": { "type": "object", "properties": { "deleted": { @@ -1161,7 +1206,7 @@ const docTemplate = `{ } } }, - "api.PostTestRunsScheduleRequest": { + "web_api.PostTestRunsScheduleRequest": { "type": "object", "properties": { "allow_duplicate": { @@ -1179,7 +1224,7 @@ const docTemplate = `{ } } }, - "api.PostTestRunsScheduleResponse": { + "web_api.PostTestRunsScheduleResponse": { "type": "object", "properties": { "config": { @@ -1197,7 +1242,7 @@ const docTemplate = `{ } } }, - "api.PostTestsDeleteRequest": { + "web_api.PostTestsDeleteRequest": { "type": "object", "properties": { "tests": { @@ -1208,7 +1253,7 @@ const docTemplate = `{ } } }, - "api.PostTestsDeleteResponse": { + "web_api.PostTestsDeleteResponse": { "type": "object", "properties": { "deleted": { @@ -1225,7 +1270,7 @@ const docTemplate = `{ } } }, - "api.PostTestsRegisterExternalRequest": { + "web_api.PostTestsRegisterExternalRequest": { "type": "object", "properties": { "config": { @@ -1252,7 +1297,7 @@ const docTemplate = `{ } } }, - "api.PostTestsRegisterExternalResponse": { + "web_api.PostTestsRegisterExternalResponse": { "type": "object", "properties": { "config": { @@ -1267,7 +1312,7 @@ const docTemplate = `{ } } }, - "api.PostTestsRegisterRequest": { + "web_api.PostTestsRegisterRequest": { "type": "object", "properties": { "cleanupTasks": { @@ -1306,7 +1351,7 @@ const docTemplate = `{ } } }, - "api.PostTestsRegisterResponse": { + "web_api.PostTestsRegisterResponse": { "type": "object", "properties": { "config": { @@ -1321,7 +1366,7 @@ const docTemplate = `{ } } }, - "github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response": { + "web_api.Response": { "type": "object", "properties": { "data": {}, @@ -1329,45 +1374,6 @@ const docTemplate = `{ "type": "string" } } - }, - "helper.RawMessage": { - "type": "object" - }, - "types.TestSchedule": { - "type": "object", - "properties": { - "cron": { - "type": "array", - "items": { - "type": "string" - } - }, - "skipQueue": { - "type": "boolean" - }, - "startup": { - "type": "boolean" - } - } - }, - "types.TestStatus": { - "type": "string", - "enum": [ - "pending", - "running", - "success", - "failure", - "skipped", - "aborted" - ], - "x-enum-varnames": [ - "TestStatusPending", - "TestStatusRunning", - "TestStatusSuccess", - "TestStatusFailure", - "TestStatusSkipped", - "TestStatusAborted" - ] } }, "tags": [ diff --git a/pkg/coordinator/web/api/docs/swagger.json b/pkg/web/api/docs/swagger.json similarity index 85% rename from pkg/coordinator/web/api/docs/swagger.json rename to pkg/web/api/docs/swagger.json index 962c3a0e..fd3951c2 100644 --- a/pkg/coordinator/web/api/docs/swagger.json +++ b/pkg/web/api/docs/swagger.json @@ -33,13 +33,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.GetTestResponse" + "$ref": "#/definitions/web_api.GetTestResponse" } } } @@ -49,13 +49,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -87,13 +87,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.GetTestRunResponse" + "$ref": "#/definitions/web_api.GetTestRunResponse" } } } @@ -103,13 +103,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -140,7 +140,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api.PostTestRunCancelRequest" + "$ref": "#/definitions/web_api.PostTestRunCancelRequest" } } ], @@ -150,13 +150,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.PostTestRunCancelResponse" + "$ref": "#/definitions/web_api.PostTestRunCancelResponse" } } } @@ -166,13 +166,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -204,13 +204,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.GetTestRunDetailsResponse" + "$ref": "#/definitions/web_api.GetTestRunDetailsResponse" } } } @@ -220,13 +220,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -258,7 +258,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", @@ -274,13 +274,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -319,13 +319,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.GetTestRunDetailedTask" + "$ref": "#/definitions/web_api.GetTestRunDetailedTask" } } } @@ -335,19 +335,19 @@ "400": { "description": "Bad Request", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "404": { "description": "Not Found", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -404,19 +404,19 @@ "400": { "description": "Bad Request", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "404": { "description": "Not Found", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -447,7 +447,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", @@ -455,7 +455,7 @@ "data": { "type": "array", "items": { - "$ref": "#/definitions/api.GetTestRunsResponse" + "$ref": "#/definitions/web_api.GetTestRunsResponse" } } } @@ -466,13 +466,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -500,7 +500,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api.PostTestRunsDeleteRequest" + "$ref": "#/definitions/web_api.PostTestRunsDeleteRequest" } } ], @@ -510,13 +510,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.PostTestRunsDeleteResponse" + "$ref": "#/definitions/web_api.PostTestRunsDeleteResponse" } } } @@ -526,13 +526,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -556,7 +556,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api.PostTestRunsScheduleRequest" + "$ref": "#/definitions/web_api.PostTestRunsScheduleRequest" } } ], @@ -566,13 +566,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.PostTestRunsScheduleResponse" + "$ref": "#/definitions/web_api.PostTestRunsScheduleResponse" } } } @@ -582,13 +582,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -611,7 +611,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", @@ -619,7 +619,7 @@ "data": { "type": "array", "items": { - "$ref": "#/definitions/api.GetTestsResponse" + "$ref": "#/definitions/web_api.GetTestsResponse" } } } @@ -630,13 +630,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -664,7 +664,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api.PostTestsDeleteRequest" + "$ref": "#/definitions/web_api.PostTestsDeleteRequest" } } ], @@ -674,13 +674,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.PostTestsDeleteResponse" + "$ref": "#/definitions/web_api.PostTestsDeleteResponse" } } } @@ -690,13 +690,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -724,7 +724,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api.PostTestsRegisterRequest" + "$ref": "#/definitions/web_api.PostTestsRegisterRequest" } } ], @@ -734,13 +734,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.PostTestsRegisterResponse" + "$ref": "#/definitions/web_api.PostTestsRegisterResponse" } } } @@ -750,13 +750,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -783,7 +783,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api.PostTestsRegisterExternalRequest" + "$ref": "#/definitions/web_api.PostTestsRegisterExternalRequest" } } ], @@ -793,13 +793,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api.PostTestsRegisterExternalResponse" + "$ref": "#/definitions/web_api.PostTestsRegisterExternalResponse" } } } @@ -809,13 +809,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response" + "$ref": "#/definitions/web_api.Response" } } } @@ -823,9 +823,51 @@ } }, "definitions": { - "api.GetTestResponse": { + "helper.RawMessage": { + "type": "object" + }, + "types.TestSchedule": { "type": "object", "properties": { + "cron": { + "type": "array", + "items": { + "type": "string" + } + }, + "skipQueue": { + "type": "boolean" + }, + "startup": { + "type": "boolean" + } + } + }, + "types.TestStatus": { + "type": "string", + "enum": [ + "pending", + "running", + "success", + "failure", + "skipped", + "aborted" + ], + "x-enum-varnames": [ + "TestStatusPending", + "TestStatusRunning", + "TestStatusSuccess", + "TestStatusFailure", + "TestStatusSkipped", + "TestStatusAborted" + ] + }, + "web_api.GetTestResponse": { + "type": "object", + "properties": { + "basePath": { + "type": "string" + }, "config": { "type": "object", "additionalProperties": {} @@ -853,7 +895,7 @@ } } }, - "api.GetTestRunDetailedTask": { + "web_api.GetTestRunDetailedTask": { "type": "object", "properties": { "completed": { @@ -868,7 +910,7 @@ "log": { "type": "array", "items": { - "$ref": "#/definitions/api.GetTestRunDetailedTaskLog" + "$ref": "#/definitions/web_api.GetTestRunDetailedTaskLog" } }, "name": { @@ -909,7 +951,7 @@ } } }, - "api.GetTestRunDetailedTaskLog": { + "web_api.GetTestRunDetailedTaskLog": { "type": "object", "properties": { "data": { @@ -932,7 +974,7 @@ } } }, - "api.GetTestRunDetailsResponse": { + "web_api.GetTestRunDetailsResponse": { "type": "object", "properties": { "name": { @@ -953,7 +995,7 @@ "tasks": { "type": "array", "items": { - "$ref": "#/definitions/api.GetTestRunDetailedTask" + "$ref": "#/definitions/web_api.GetTestRunDetailedTask" } }, "test_id": { @@ -961,7 +1003,7 @@ } } }, - "api.GetTestRunResponse": { + "web_api.GetTestRunResponse": { "type": "object", "properties": { "name": { @@ -982,7 +1024,7 @@ "tasks": { "type": "array", "items": { - "$ref": "#/definitions/api.GetTestRunTask" + "$ref": "#/definitions/web_api.GetTestRunTask" } }, "test_id": { @@ -990,7 +1032,7 @@ } } }, - "api.GetTestRunTask": { + "web_api.GetTestRunTask": { "type": "object", "properties": { "completed": { @@ -1014,7 +1056,7 @@ "result_files": { "type": "array", "items": { - "$ref": "#/definitions/api.GetTestRunTaskResult" + "$ref": "#/definitions/web_api.GetTestRunTaskResult" } }, "runtime": { @@ -1040,7 +1082,7 @@ } } }, - "api.GetTestRunTaskResult": { + "web_api.GetTestRunTaskResult": { "type": "object", "properties": { "index": { @@ -1060,7 +1102,7 @@ } } }, - "api.GetTestRunsResponse": { + "web_api.GetTestRunsResponse": { "type": "object", "properties": { "name": { @@ -1083,9 +1125,12 @@ } } }, - "api.GetTestsResponse": { + "web_api.GetTestsResponse": { "type": "object", "properties": { + "basePath": { + "type": "string" + }, "id": { "type": "string" }, @@ -1097,7 +1142,7 @@ } } }, - "api.PostTestRunCancelRequest": { + "web_api.PostTestRunCancelRequest": { "type": "object", "properties": { "skip_cleanup": { @@ -1108,7 +1153,7 @@ } } }, - "api.PostTestRunCancelResponse": { + "web_api.PostTestRunCancelResponse": { "type": "object", "properties": { "name": { @@ -1125,7 +1170,7 @@ } } }, - "api.PostTestRunsDeleteRequest": { + "web_api.PostTestRunsDeleteRequest": { "type": "object", "properties": { "test_runs": { @@ -1136,7 +1181,7 @@ } } }, - "api.PostTestRunsDeleteResponse": { + "web_api.PostTestRunsDeleteResponse": { "type": "object", "properties": { "deleted": { @@ -1153,7 +1198,7 @@ } } }, - "api.PostTestRunsScheduleRequest": { + "web_api.PostTestRunsScheduleRequest": { "type": "object", "properties": { "allow_duplicate": { @@ -1171,7 +1216,7 @@ } } }, - "api.PostTestRunsScheduleResponse": { + "web_api.PostTestRunsScheduleResponse": { "type": "object", "properties": { "config": { @@ -1189,7 +1234,7 @@ } } }, - "api.PostTestsDeleteRequest": { + "web_api.PostTestsDeleteRequest": { "type": "object", "properties": { "tests": { @@ -1200,7 +1245,7 @@ } } }, - "api.PostTestsDeleteResponse": { + "web_api.PostTestsDeleteResponse": { "type": "object", "properties": { "deleted": { @@ -1217,7 +1262,7 @@ } } }, - "api.PostTestsRegisterExternalRequest": { + "web_api.PostTestsRegisterExternalRequest": { "type": "object", "properties": { "config": { @@ -1244,7 +1289,7 @@ } } }, - "api.PostTestsRegisterExternalResponse": { + "web_api.PostTestsRegisterExternalResponse": { "type": "object", "properties": { "config": { @@ -1259,7 +1304,7 @@ } } }, - "api.PostTestsRegisterRequest": { + "web_api.PostTestsRegisterRequest": { "type": "object", "properties": { "cleanupTasks": { @@ -1298,7 +1343,7 @@ } } }, - "api.PostTestsRegisterResponse": { + "web_api.PostTestsRegisterResponse": { "type": "object", "properties": { "config": { @@ -1313,7 +1358,7 @@ } } }, - "github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response": { + "web_api.Response": { "type": "object", "properties": { "data": {}, @@ -1321,45 +1366,6 @@ "type": "string" } } - }, - "helper.RawMessage": { - "type": "object" - }, - "types.TestSchedule": { - "type": "object", - "properties": { - "cron": { - "type": "array", - "items": { - "type": "string" - } - }, - "skipQueue": { - "type": "boolean" - }, - "startup": { - "type": "boolean" - } - } - }, - "types.TestStatus": { - "type": "string", - "enum": [ - "pending", - "running", - "success", - "failure", - "skipped", - "aborted" - ], - "x-enum-varnames": [ - "TestStatusPending", - "TestStatusRunning", - "TestStatusSuccess", - "TestStatusFailure", - "TestStatusSkipped", - "TestStatusAborted" - ] } }, "tags": [ diff --git a/pkg/coordinator/web/api/docs/swagger.yaml b/pkg/web/api/docs/swagger.yaml similarity index 73% rename from pkg/coordinator/web/api/docs/swagger.yaml rename to pkg/web/api/docs/swagger.yaml index 6fba0800..99c0857b 100644 --- a/pkg/coordinator/web/api/docs/swagger.yaml +++ b/pkg/web/api/docs/swagger.yaml @@ -1,6 +1,37 @@ definitions: - api.GetTestResponse: + helper.RawMessage: + type: object + types.TestSchedule: + properties: + cron: + items: + type: string + type: array + skipQueue: + type: boolean + startup: + type: boolean + type: object + types.TestStatus: + enum: + - pending + - running + - success + - failure + - skipped + - aborted + type: string + x-enum-varnames: + - TestStatusPending + - TestStatusRunning + - TestStatusSuccess + - TestStatusFailure + - TestStatusSkipped + - TestStatusAborted + web_api.GetTestResponse: properties: + basePath: + type: string config: additionalProperties: {} type: object @@ -19,7 +50,7 @@ definitions: timeout: type: integer type: object - api.GetTestRunDetailedTask: + web_api.GetTestRunDetailedTask: properties: completed: type: boolean @@ -29,7 +60,7 @@ definitions: type: integer log: items: - $ref: '#/definitions/api.GetTestRunDetailedTaskLog' + $ref: '#/definitions/web_api.GetTestRunDetailedTaskLog' type: array name: type: string @@ -56,7 +87,7 @@ definitions: title: type: string type: object - api.GetTestRunDetailedTaskLog: + web_api.GetTestRunDetailedTaskLog: properties: data: additionalProperties: @@ -71,7 +102,7 @@ definitions: time: type: string type: object - api.GetTestRunDetailsResponse: + web_api.GetTestRunDetailsResponse: properties: name: type: string @@ -85,12 +116,12 @@ definitions: type: integer tasks: items: - $ref: '#/definitions/api.GetTestRunDetailedTask' + $ref: '#/definitions/web_api.GetTestRunDetailedTask' type: array test_id: type: string type: object - api.GetTestRunResponse: + web_api.GetTestRunResponse: properties: name: type: string @@ -104,12 +135,12 @@ definitions: type: integer tasks: items: - $ref: '#/definitions/api.GetTestRunTask' + $ref: '#/definitions/web_api.GetTestRunTask' type: array test_id: type: string type: object - api.GetTestRunTask: + web_api.GetTestRunTask: properties: completed: type: boolean @@ -125,7 +156,7 @@ definitions: type: string result_files: items: - $ref: '#/definitions/api.GetTestRunTaskResult' + $ref: '#/definitions/web_api.GetTestRunTaskResult' type: array runtime: type: integer @@ -142,7 +173,7 @@ definitions: title: type: string type: object - api.GetTestRunTaskResult: + web_api.GetTestRunTaskResult: properties: index: type: integer @@ -155,7 +186,7 @@ definitions: url: type: string type: object - api.GetTestRunsResponse: + web_api.GetTestRunsResponse: properties: name: type: string @@ -170,8 +201,10 @@ definitions: test_id: type: string type: object - api.GetTestsResponse: + web_api.GetTestsResponse: properties: + basePath: + type: string id: type: string name: @@ -179,14 +212,14 @@ definitions: source: type: string type: object - api.PostTestRunCancelRequest: + web_api.PostTestRunCancelRequest: properties: skip_cleanup: type: boolean test_id: type: string type: object - api.PostTestRunCancelResponse: + web_api.PostTestRunCancelResponse: properties: name: type: string @@ -197,14 +230,14 @@ definitions: test_id: type: string type: object - api.PostTestRunsDeleteRequest: + web_api.PostTestRunsDeleteRequest: properties: test_runs: items: type: integer type: array type: object - api.PostTestRunsDeleteResponse: + web_api.PostTestRunsDeleteResponse: properties: deleted: items: @@ -215,7 +248,7 @@ definitions: type: string type: array type: object - api.PostTestRunsScheduleRequest: + web_api.PostTestRunsScheduleRequest: properties: allow_duplicate: type: boolean @@ -227,7 +260,7 @@ definitions: test_id: type: string type: object - api.PostTestRunsScheduleResponse: + web_api.PostTestRunsScheduleResponse: properties: config: additionalProperties: {} @@ -239,14 +272,14 @@ definitions: test_id: type: string type: object - api.PostTestsDeleteRequest: + web_api.PostTestsDeleteRequest: properties: tests: items: type: string type: array type: object - api.PostTestsDeleteResponse: + web_api.PostTestsDeleteResponse: properties: deleted: items: @@ -257,7 +290,7 @@ definitions: type: string type: array type: object - api.PostTestsRegisterExternalRequest: + web_api.PostTestsRegisterExternalRequest: properties: config: additionalProperties: true @@ -275,7 +308,7 @@ definitions: timeout: type: integer type: object - api.PostTestsRegisterExternalResponse: + web_api.PostTestsRegisterExternalResponse: properties: config: additionalProperties: {} @@ -285,7 +318,7 @@ definitions: test_id: type: string type: object - api.PostTestsRegisterRequest: + web_api.PostTestsRegisterRequest: properties: cleanupTasks: items: @@ -311,7 +344,7 @@ definitions: timeout: type: string type: object - api.PostTestsRegisterResponse: + web_api.PostTestsRegisterResponse: properties: config: additionalProperties: {} @@ -321,41 +354,12 @@ definitions: test_id: type: string type: object - github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response: + web_api.Response: properties: data: {} status: type: string type: object - helper.RawMessage: - type: object - types.TestSchedule: - properties: - cron: - items: - type: string - type: array - skipQueue: - type: boolean - startup: - type: boolean - type: object - types.TestStatus: - enum: - - pending - - running - - success - - failure - - skipped - - aborted - type: string - x-enum-varnames: - - TestStatusPending - - TestStatusRunning - - TestStatusSuccess - - TestStatusFailure - - TestStatusSkipped - - TestStatusAborted info: contact: {} description: API for querying information about Assertoor tests @@ -379,19 +383,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + - $ref: '#/definitions/web_api.Response' - properties: data: - $ref: '#/definitions/api.GetTestResponse' + $ref: '#/definitions/web_api.GetTestResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' summary: Get test definition by test ID tags: - Test @@ -413,19 +417,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + - $ref: '#/definitions/web_api.Response' - properties: data: - $ref: '#/definitions/api.GetTestRunResponse' + $ref: '#/definitions/web_api.GetTestRunResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' summary: Get test run by run ID tags: - TestRun @@ -444,7 +448,7 @@ paths: name: cancelOptions required: true schema: - $ref: '#/definitions/api.PostTestRunCancelRequest' + $ref: '#/definitions/web_api.PostTestRunCancelRequest' produces: - application/json responses: @@ -452,19 +456,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + - $ref: '#/definitions/web_api.Response' - properties: data: - $ref: '#/definitions/api.PostTestRunCancelResponse' + $ref: '#/definitions/web_api.PostTestRunCancelResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' summary: Cancel test run by test ID tags: - TestRun @@ -486,19 +490,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + - $ref: '#/definitions/web_api.Response' - properties: data: - $ref: '#/definitions/api.GetTestRunDetailsResponse' + $ref: '#/definitions/web_api.GetTestRunDetailsResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' summary: Get detailed test run by run ID tags: - TestRun @@ -519,7 +523,7 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + - $ref: '#/definitions/web_api.Response' - properties: data: type: string @@ -527,11 +531,11 @@ paths: "400": description: Failure schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' summary: Get test run status by run ID tags: - TestRun @@ -558,23 +562,23 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + - $ref: '#/definitions/web_api.Response' - properties: data: - $ref: '#/definitions/api.GetTestRunDetailedTask' + $ref: '#/definitions/web_api.GetTestRunDetailedTask' type: object "400": description: Bad Request schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' "404": description: Not Found schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' summary: Get detailed task of a given test run tags: - TestRun @@ -613,15 +617,15 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' "404": description: Not Found schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' summary: Get task result file tags: - TestRun @@ -641,21 +645,21 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + - $ref: '#/definitions/web_api.Response' - properties: data: items: - $ref: '#/definitions/api.GetTestRunsResponse' + $ref: '#/definitions/web_api.GetTestRunsResponse' type: array type: object "400": description: Failure schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' summary: Get list of test runs tags: - TestRun @@ -672,7 +676,7 @@ paths: name: testConfig required: true schema: - $ref: '#/definitions/api.PostTestRunsDeleteRequest' + $ref: '#/definitions/web_api.PostTestRunsDeleteRequest' produces: - application/json responses: @@ -680,19 +684,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + - $ref: '#/definitions/web_api.Response' - properties: data: - $ref: '#/definitions/api.PostTestRunsDeleteResponse' + $ref: '#/definitions/web_api.PostTestRunsDeleteResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' summary: Delete test runs tags: - TestRun @@ -706,7 +710,7 @@ paths: name: runOptions required: true schema: - $ref: '#/definitions/api.PostTestRunsScheduleRequest' + $ref: '#/definitions/web_api.PostTestRunsScheduleRequest' produces: - application/json responses: @@ -714,19 +718,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + - $ref: '#/definitions/web_api.Response' - properties: data: - $ref: '#/definitions/api.PostTestRunsScheduleResponse' + $ref: '#/definitions/web_api.PostTestRunsScheduleResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' summary: Schedule new test run by test ID tags: - TestRun @@ -742,21 +746,21 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + - $ref: '#/definitions/web_api.Response' - properties: data: items: - $ref: '#/definitions/api.GetTestsResponse' + $ref: '#/definitions/web_api.GetTestsResponse' type: array type: object "400": description: Failure schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' summary: Get list of test definitions tags: - Test @@ -773,7 +777,7 @@ paths: name: testConfig required: true schema: - $ref: '#/definitions/api.PostTestsDeleteRequest' + $ref: '#/definitions/web_api.PostTestsDeleteRequest' produces: - application/json responses: @@ -781,19 +785,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + - $ref: '#/definitions/web_api.Response' - properties: data: - $ref: '#/definitions/api.PostTestsDeleteResponse' + $ref: '#/definitions/web_api.PostTestsDeleteResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' summary: Delete tests tags: - Test @@ -810,7 +814,7 @@ paths: name: testConfig required: true schema: - $ref: '#/definitions/api.PostTestsRegisterRequest' + $ref: '#/definitions/web_api.PostTestsRegisterRequest' produces: - application/json responses: @@ -818,19 +822,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + - $ref: '#/definitions/web_api.Response' - properties: data: - $ref: '#/definitions/api.PostTestsRegisterResponse' + $ref: '#/definitions/web_api.PostTestsRegisterResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' summary: Register new test via yaml configuration tags: - Test @@ -846,7 +850,7 @@ paths: name: externalTestConfig required: true schema: - $ref: '#/definitions/api.PostTestsRegisterExternalRequest' + $ref: '#/definitions/web_api.PostTestsRegisterExternalRequest' produces: - application/json responses: @@ -854,19 +858,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + - $ref: '#/definitions/web_api.Response' - properties: data: - $ref: '#/definitions/api.PostTestsRegisterExternalResponse' + $ref: '#/definitions/web_api.PostTestsRegisterExternalResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_coordinator_web_api.Response' + $ref: '#/definitions/web_api.Response' summary: Register new test via external test configuration tags: - Test diff --git a/pkg/coordinator/web/api/get_task_details_api.go b/pkg/web/api/get_task_details_api.go similarity index 98% rename from pkg/coordinator/web/api/get_task_details_api.go rename to pkg/web/api/get_task_details_api.go index 47ca3479..d0ada8bb 100644 --- a/pkg/coordinator/web/api/get_task_details_api.go +++ b/pkg/web/api/get_task_details_api.go @@ -6,7 +6,7 @@ import ( "strconv" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/gorilla/mux" "gopkg.in/yaml.v3" ) diff --git a/pkg/coordinator/web/api/get_task_result_api.go b/pkg/web/api/get_task_result_api.go similarity index 97% rename from pkg/coordinator/web/api/get_task_result_api.go rename to pkg/web/api/get_task_result_api.go index 0b984c4f..f687079d 100644 --- a/pkg/coordinator/web/api/get_task_result_api.go +++ b/pkg/web/api/get_task_result_api.go @@ -9,8 +9,8 @@ import ( "strings" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/db" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/gorilla/mux" ) diff --git a/pkg/coordinator/web/api/get_test_api.go b/pkg/web/api/get_test_api.go similarity index 97% rename from pkg/coordinator/web/api/get_test_api.go rename to pkg/web/api/get_test_api.go index b25b26f4..a0234923 100644 --- a/pkg/coordinator/web/api/get_test_api.go +++ b/pkg/web/api/get_test_api.go @@ -3,7 +3,7 @@ package api import ( "net/http" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/gorilla/mux" ) diff --git a/pkg/coordinator/web/api/get_test_run_api.go b/pkg/web/api/get_test_run_api.go similarity index 98% rename from pkg/coordinator/web/api/get_test_run_api.go rename to pkg/web/api/get_test_run_api.go index 7421bb3f..fa82e740 100644 --- a/pkg/coordinator/web/api/get_test_run_api.go +++ b/pkg/web/api/get_test_run_api.go @@ -6,8 +6,8 @@ import ( "strconv" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/db" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/gorilla/mux" ) diff --git a/pkg/coordinator/web/api/get_test_run_details_api.go b/pkg/web/api/get_test_run_details_api.go similarity index 99% rename from pkg/coordinator/web/api/get_test_run_details_api.go rename to pkg/web/api/get_test_run_details_api.go index b17460c7..8b807503 100644 --- a/pkg/coordinator/web/api/get_test_run_details_api.go +++ b/pkg/web/api/get_test_run_details_api.go @@ -6,7 +6,7 @@ import ( "strconv" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/gorilla/mux" "gopkg.in/yaml.v3" ) diff --git a/pkg/coordinator/web/api/get_test_run_status_api.go b/pkg/web/api/get_test_run_status_api.go similarity index 100% rename from pkg/coordinator/web/api/get_test_run_status_api.go rename to pkg/web/api/get_test_run_status_api.go diff --git a/pkg/coordinator/web/api/get_test_runs_api.go b/pkg/web/api/get_test_runs_api.go similarity index 96% rename from pkg/coordinator/web/api/get_test_runs_api.go rename to pkg/web/api/get_test_runs_api.go index f350c6ec..831552ef 100644 --- a/pkg/coordinator/web/api/get_test_runs_api.go +++ b/pkg/web/api/get_test_runs_api.go @@ -3,7 +3,7 @@ package api import ( "net/http" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" ) type GetTestRunsResponse struct { diff --git a/pkg/coordinator/web/api/get_tests_api.go b/pkg/web/api/get_tests_api.go similarity index 100% rename from pkg/coordinator/web/api/get_tests_api.go rename to pkg/web/api/get_tests_api.go diff --git a/pkg/coordinator/web/api/handler.go b/pkg/web/api/handler.go similarity index 96% rename from pkg/coordinator/web/api/handler.go rename to pkg/web/api/handler.go index 2418afc6..c5a22bc6 100644 --- a/pkg/coordinator/web/api/handler.go +++ b/pkg/web/api/handler.go @@ -4,7 +4,7 @@ import ( "encoding/json" "net/http" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/web/api/post_test_run_api.go b/pkg/web/api/post_test_run_api.go similarity index 97% rename from pkg/coordinator/web/api/post_test_run_api.go rename to pkg/web/api/post_test_run_api.go index be3a9543..6581b18c 100644 --- a/pkg/coordinator/web/api/post_test_run_api.go +++ b/pkg/web/api/post_test_run_api.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "gopkg.in/yaml.v3" ) diff --git a/pkg/coordinator/web/api/post_test_run_cancel_api.go b/pkg/web/api/post_test_run_cancel_api.go similarity index 100% rename from pkg/coordinator/web/api/post_test_run_cancel_api.go rename to pkg/web/api/post_test_run_cancel_api.go diff --git a/pkg/coordinator/web/api/post_test_runs_delete_api.go b/pkg/web/api/post_test_runs_delete_api.go similarity index 100% rename from pkg/coordinator/web/api/post_test_runs_delete_api.go rename to pkg/web/api/post_test_runs_delete_api.go diff --git a/pkg/coordinator/web/api/post_tests_delete_api.go b/pkg/web/api/post_tests_delete_api.go similarity index 100% rename from pkg/coordinator/web/api/post_tests_delete_api.go rename to pkg/web/api/post_tests_delete_api.go diff --git a/pkg/coordinator/web/api/post_tests_register_api.go b/pkg/web/api/post_tests_register_api.go similarity index 96% rename from pkg/coordinator/web/api/post_tests_register_api.go rename to pkg/web/api/post_tests_register_api.go index 91b68271..ba3a053d 100644 --- a/pkg/coordinator/web/api/post_tests_register_api.go +++ b/pkg/web/api/post_tests_register_api.go @@ -5,8 +5,8 @@ import ( "fmt" "net/http" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/helper" + "github.com/ethpandaops/assertoor/pkg/types" "gopkg.in/yaml.v3" ) diff --git a/pkg/coordinator/web/api/post_tests_register_external_api.go b/pkg/web/api/post_tests_register_external_api.go similarity index 96% rename from pkg/coordinator/web/api/post_tests_register_external_api.go rename to pkg/web/api/post_tests_register_external_api.go index 0e67909a..c8748311 100644 --- a/pkg/coordinator/web/api/post_tests_register_external_api.go +++ b/pkg/web/api/post_tests_register_external_api.go @@ -6,8 +6,8 @@ import ( "net/http" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/helper" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/helper" + "github.com/ethpandaops/assertoor/pkg/types" "gopkg.in/yaml.v3" ) diff --git a/pkg/coordinator/web/handlers/clients.go b/pkg/web/handlers/clients.go similarity index 95% rename from pkg/coordinator/web/handlers/clients.go rename to pkg/web/handlers/clients.go index 79ed43ea..8fedf71e 100644 --- a/pkg/coordinator/web/handlers/clients.go +++ b/pkg/web/handlers/clients.go @@ -4,9 +4,9 @@ import ( "net/http" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/consensus" - "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution" + "github.com/ethpandaops/assertoor/pkg/clients" + "github.com/ethpandaops/assertoor/pkg/clients/consensus" + "github.com/ethpandaops/assertoor/pkg/clients/execution" ) type ClientsPage struct { diff --git a/pkg/coordinator/web/handlers/handler.go b/pkg/web/handlers/handler.go similarity index 95% rename from pkg/coordinator/web/handlers/handler.go rename to pkg/web/handlers/handler.go index 6a7eca01..5351bfe4 100644 --- a/pkg/coordinator/web/handlers/handler.go +++ b/pkg/web/handlers/handler.go @@ -11,11 +11,11 @@ import ( "syscall" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/buildinfo" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/web/static" - "github.com/ethpandaops/assertoor/pkg/coordinator/web/templates" - "github.com/ethpandaops/assertoor/pkg/coordinator/web/utils" + "github.com/ethpandaops/assertoor/pkg/buildinfo" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/web/static" + "github.com/ethpandaops/assertoor/pkg/web/templates" + "github.com/ethpandaops/assertoor/pkg/web/utils" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/web/handlers/index.go b/pkg/web/handlers/index.go similarity index 99% rename from pkg/coordinator/web/handlers/index.go rename to pkg/web/handlers/index.go index 21b29efc..51cd7aee 100644 --- a/pkg/coordinator/web/handlers/index.go +++ b/pkg/web/handlers/index.go @@ -7,7 +7,7 @@ import ( "strconv" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/web/handlers/logs.go b/pkg/web/handlers/logs.go similarity index 100% rename from pkg/coordinator/web/handlers/logs.go rename to pkg/web/handlers/logs.go diff --git a/pkg/coordinator/web/handlers/registry.go b/pkg/web/handlers/registry.go similarity index 98% rename from pkg/coordinator/web/handlers/registry.go rename to pkg/web/handlers/registry.go index 5318ce01..de697b05 100644 --- a/pkg/coordinator/web/handlers/registry.go +++ b/pkg/web/handlers/registry.go @@ -7,8 +7,8 @@ import ( "strconv" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/db" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/web/handlers/sidebar.go b/pkg/web/handlers/sidebar.go similarity index 97% rename from pkg/coordinator/web/handlers/sidebar.go rename to pkg/web/handlers/sidebar.go index a85c5e5a..106f5759 100644 --- a/pkg/coordinator/web/handlers/sidebar.go +++ b/pkg/web/handlers/sidebar.go @@ -1,6 +1,6 @@ package handlers -import "github.com/ethpandaops/assertoor/pkg/coordinator/buildinfo" +import "github.com/ethpandaops/assertoor/pkg/buildinfo" type SidebarData struct { ClientCount uint64 `json:"client_count"` diff --git a/pkg/coordinator/web/handlers/test.go b/pkg/web/handlers/test.go similarity index 98% rename from pkg/coordinator/web/handlers/test.go rename to pkg/web/handlers/test.go index 015cc2b1..bf9a370c 100644 --- a/pkg/coordinator/web/handlers/test.go +++ b/pkg/web/handlers/test.go @@ -7,7 +7,7 @@ import ( "net/http" "strconv" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/ethpandaops/assertoor/pkg/types" "github.com/gorilla/mux" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/web/handlers/test_run.go b/pkg/web/handlers/test_run.go similarity index 97% rename from pkg/coordinator/web/handlers/test_run.go rename to pkg/web/handlers/test_run.go index 5d10a5fe..7a0603f1 100644 --- a/pkg/coordinator/web/handlers/test_run.go +++ b/pkg/web/handlers/test_run.go @@ -7,9 +7,9 @@ import ( "strconv" "time" - "github.com/ethpandaops/assertoor/pkg/coordinator/db" - "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/web/api" + "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/web/api" "github.com/gorilla/mux" "github.com/sirupsen/logrus" ) diff --git a/pkg/coordinator/web/server.go b/pkg/web/server.go similarity index 95% rename from pkg/coordinator/web/server.go rename to pkg/web/server.go index 98333e09..5a534118 100644 --- a/pkg/coordinator/web/server.go +++ b/pkg/web/server.go @@ -7,17 +7,17 @@ import ( "net/http" "strings" - coordinator_types "github.com/ethpandaops/assertoor/pkg/coordinator/types" - "github.com/ethpandaops/assertoor/pkg/coordinator/web/api" - "github.com/ethpandaops/assertoor/pkg/coordinator/web/handlers" - "github.com/ethpandaops/assertoor/pkg/coordinator/web/types" + coordinator_types "github.com/ethpandaops/assertoor/pkg/types" + "github.com/ethpandaops/assertoor/pkg/web/api" + "github.com/ethpandaops/assertoor/pkg/web/handlers" + "github.com/ethpandaops/assertoor/pkg/web/types" "github.com/gorilla/mux" "github.com/sirupsen/logrus" httpSwagger "github.com/swaggo/http-swagger" "github.com/urfave/negroni" // import swagger docs - _ "github.com/ethpandaops/assertoor/pkg/coordinator/web/api/docs" + _ "github.com/ethpandaops/assertoor/pkg/web/api/docs" // import pprof //nolint:gosec // ignore diff --git a/pkg/coordinator/web/static/css/bootstrap.min.css b/pkg/web/static/css/bootstrap.min.css similarity index 100% rename from pkg/coordinator/web/static/css/bootstrap.min.css rename to pkg/web/static/css/bootstrap.min.css diff --git a/pkg/coordinator/web/static/css/bootstrap.min.css.map b/pkg/web/static/css/bootstrap.min.css.map similarity index 100% rename from pkg/coordinator/web/static/css/bootstrap.min.css.map rename to pkg/web/static/css/bootstrap.min.css.map diff --git a/pkg/coordinator/web/static/css/fontawesome-all.min.css b/pkg/web/static/css/fontawesome-all.min.css similarity index 100% rename from pkg/coordinator/web/static/css/fontawesome-all.min.css rename to pkg/web/static/css/fontawesome-all.min.css diff --git a/pkg/coordinator/web/static/css/fontawesome.min.css b/pkg/web/static/css/fontawesome.min.css similarity index 100% rename from pkg/coordinator/web/static/css/fontawesome.min.css rename to pkg/web/static/css/fontawesome.min.css diff --git a/pkg/coordinator/web/static/css/layout.css b/pkg/web/static/css/layout.css similarity index 100% rename from pkg/coordinator/web/static/css/layout.css rename to pkg/web/static/css/layout.css diff --git a/pkg/coordinator/web/static/embed.go b/pkg/web/static/embed.go similarity index 100% rename from pkg/coordinator/web/static/embed.go rename to pkg/web/static/embed.go diff --git a/pkg/coordinator/web/static/favicon.ico b/pkg/web/static/favicon.ico similarity index 100% rename from pkg/coordinator/web/static/favicon.ico rename to pkg/web/static/favicon.ico diff --git a/pkg/coordinator/web/static/js/ace-1.5.0/ace.js b/pkg/web/static/js/ace-1.5.0/ace.js similarity index 100% rename from pkg/coordinator/web/static/js/ace-1.5.0/ace.js rename to pkg/web/static/js/ace-1.5.0/ace.js diff --git a/pkg/coordinator/web/static/js/ace-1.5.0/mode-yaml.js b/pkg/web/static/js/ace-1.5.0/mode-yaml.js similarity index 100% rename from pkg/coordinator/web/static/js/ace-1.5.0/mode-yaml.js rename to pkg/web/static/js/ace-1.5.0/mode-yaml.js diff --git a/pkg/coordinator/web/static/js/assertoor.js b/pkg/web/static/js/assertoor.js similarity index 100% rename from pkg/coordinator/web/static/js/assertoor.js rename to pkg/web/static/js/assertoor.js diff --git a/pkg/coordinator/web/static/js/bootstrap.bundle.min.js b/pkg/web/static/js/bootstrap.bundle.min.js similarity index 100% rename from pkg/coordinator/web/static/js/bootstrap.bundle.min.js rename to pkg/web/static/js/bootstrap.bundle.min.js diff --git a/pkg/coordinator/web/static/js/bootstrap.bundle.min.js.map b/pkg/web/static/js/bootstrap.bundle.min.js.map similarity index 100% rename from pkg/coordinator/web/static/js/bootstrap.bundle.min.js.map rename to pkg/web/static/js/bootstrap.bundle.min.js.map diff --git a/pkg/coordinator/web/static/js/clipboard.min.js b/pkg/web/static/js/clipboard.min.js similarity index 100% rename from pkg/coordinator/web/static/js/clipboard.min.js rename to pkg/web/static/js/clipboard.min.js diff --git a/pkg/coordinator/web/static/js/color-modes.js b/pkg/web/static/js/color-modes.js similarity index 100% rename from pkg/coordinator/web/static/js/color-modes.js rename to pkg/web/static/js/color-modes.js diff --git a/pkg/coordinator/web/static/js/jquery.min.js b/pkg/web/static/js/jquery.min.js similarity index 100% rename from pkg/coordinator/web/static/js/jquery.min.js rename to pkg/web/static/js/jquery.min.js diff --git a/pkg/coordinator/web/static/js/yaml-0.3.0.min.js b/pkg/web/static/js/yaml-0.3.0.min.js similarity index 100% rename from pkg/coordinator/web/static/js/yaml-0.3.0.min.js rename to pkg/web/static/js/yaml-0.3.0.min.js diff --git a/pkg/coordinator/web/static/webfonts/fa-brands-400.ttf b/pkg/web/static/webfonts/fa-brands-400.ttf similarity index 100% rename from pkg/coordinator/web/static/webfonts/fa-brands-400.ttf rename to pkg/web/static/webfonts/fa-brands-400.ttf diff --git a/pkg/coordinator/web/static/webfonts/fa-brands-400.woff2 b/pkg/web/static/webfonts/fa-brands-400.woff2 similarity index 100% rename from pkg/coordinator/web/static/webfonts/fa-brands-400.woff2 rename to pkg/web/static/webfonts/fa-brands-400.woff2 diff --git a/pkg/coordinator/web/static/webfonts/fa-regular-400.ttf b/pkg/web/static/webfonts/fa-regular-400.ttf similarity index 100% rename from pkg/coordinator/web/static/webfonts/fa-regular-400.ttf rename to pkg/web/static/webfonts/fa-regular-400.ttf diff --git a/pkg/coordinator/web/static/webfonts/fa-regular-400.woff2 b/pkg/web/static/webfonts/fa-regular-400.woff2 similarity index 100% rename from pkg/coordinator/web/static/webfonts/fa-regular-400.woff2 rename to pkg/web/static/webfonts/fa-regular-400.woff2 diff --git a/pkg/coordinator/web/static/webfonts/fa-solid-900.ttf b/pkg/web/static/webfonts/fa-solid-900.ttf similarity index 100% rename from pkg/coordinator/web/static/webfonts/fa-solid-900.ttf rename to pkg/web/static/webfonts/fa-solid-900.ttf diff --git a/pkg/coordinator/web/static/webfonts/fa-solid-900.woff2 b/pkg/web/static/webfonts/fa-solid-900.woff2 similarity index 100% rename from pkg/coordinator/web/static/webfonts/fa-solid-900.woff2 rename to pkg/web/static/webfonts/fa-solid-900.woff2 diff --git a/pkg/coordinator/web/static/webfonts/fa-v4compatibility.ttf b/pkg/web/static/webfonts/fa-v4compatibility.ttf similarity index 100% rename from pkg/coordinator/web/static/webfonts/fa-v4compatibility.ttf rename to pkg/web/static/webfonts/fa-v4compatibility.ttf diff --git a/pkg/coordinator/web/static/webfonts/fa-v4compatibility.woff2 b/pkg/web/static/webfonts/fa-v4compatibility.woff2 similarity index 100% rename from pkg/coordinator/web/static/webfonts/fa-v4compatibility.woff2 rename to pkg/web/static/webfonts/fa-v4compatibility.woff2 diff --git a/pkg/coordinator/web/templates/_layout/404.html b/pkg/web/templates/_layout/404.html similarity index 100% rename from pkg/coordinator/web/templates/_layout/404.html rename to pkg/web/templates/_layout/404.html diff --git a/pkg/coordinator/web/templates/_layout/500.html b/pkg/web/templates/_layout/500.html similarity index 100% rename from pkg/coordinator/web/templates/_layout/500.html rename to pkg/web/templates/_layout/500.html diff --git a/pkg/coordinator/web/templates/_layout/blank.html b/pkg/web/templates/_layout/blank.html similarity index 100% rename from pkg/coordinator/web/templates/_layout/blank.html rename to pkg/web/templates/_layout/blank.html diff --git a/pkg/coordinator/web/templates/_layout/footer.html b/pkg/web/templates/_layout/footer.html similarity index 100% rename from pkg/coordinator/web/templates/_layout/footer.html rename to pkg/web/templates/_layout/footer.html diff --git a/pkg/coordinator/web/templates/_layout/header.html b/pkg/web/templates/_layout/header.html similarity index 100% rename from pkg/coordinator/web/templates/_layout/header.html rename to pkg/web/templates/_layout/header.html diff --git a/pkg/coordinator/web/templates/_layout/layout.html b/pkg/web/templates/_layout/layout.html similarity index 100% rename from pkg/coordinator/web/templates/_layout/layout.html rename to pkg/web/templates/_layout/layout.html diff --git a/pkg/coordinator/web/templates/clients/clients.html b/pkg/web/templates/clients/clients.html similarity index 100% rename from pkg/coordinator/web/templates/clients/clients.html rename to pkg/web/templates/clients/clients.html diff --git a/pkg/coordinator/web/templates/index/index.html b/pkg/web/templates/index/index.html similarity index 100% rename from pkg/coordinator/web/templates/index/index.html rename to pkg/web/templates/index/index.html diff --git a/pkg/coordinator/web/templates/registry/registry.html b/pkg/web/templates/registry/registry.html similarity index 100% rename from pkg/coordinator/web/templates/registry/registry.html rename to pkg/web/templates/registry/registry.html diff --git a/pkg/coordinator/web/templates/sidebar/sidebar.html b/pkg/web/templates/sidebar/sidebar.html similarity index 100% rename from pkg/coordinator/web/templates/sidebar/sidebar.html rename to pkg/web/templates/sidebar/sidebar.html diff --git a/pkg/coordinator/web/templates/templates.go b/pkg/web/templates/templates.go similarity index 100% rename from pkg/coordinator/web/templates/templates.go rename to pkg/web/templates/templates.go diff --git a/pkg/coordinator/web/templates/test/test.html b/pkg/web/templates/test/test.html similarity index 100% rename from pkg/coordinator/web/templates/test/test.html rename to pkg/web/templates/test/test.html diff --git a/pkg/coordinator/web/templates/test/test_runs.html b/pkg/web/templates/test/test_runs.html similarity index 100% rename from pkg/coordinator/web/templates/test/test_runs.html rename to pkg/web/templates/test/test_runs.html diff --git a/pkg/coordinator/web/templates/test_run/test_run.html b/pkg/web/templates/test_run/test_run.html similarity index 100% rename from pkg/coordinator/web/templates/test_run/test_run.html rename to pkg/web/templates/test_run/test_run.html diff --git a/pkg/coordinator/web/types/config.go b/pkg/web/types/config.go similarity index 100% rename from pkg/coordinator/web/types/config.go rename to pkg/web/types/config.go diff --git a/pkg/coordinator/web/utils/templateFucs.go b/pkg/web/utils/templateFucs.go similarity index 100% rename from pkg/coordinator/web/utils/templateFucs.go rename to pkg/web/utils/templateFucs.go From ea268af5ebd8641ed87e2b8deff3c0131dc902dd Mon Sep 17 00:00:00 2001 From: pk910 Date: Sun, 1 Feb 2026 03:39:44 +0100 Subject: [PATCH 02/32] [RF-1] implement event bus & progress tracking --- pkg/assertoor/coordinator.go | 23 +- pkg/events/bus.go | 227 +++++++++++++ pkg/events/event.go | 124 +++++++ pkg/events/sse.go | 303 ++++++++++++++++++ pkg/logger/eventbushook.go | 48 +++ pkg/logger/logscope.go | 12 + pkg/scheduler/services.go | 9 +- pkg/scheduler/task_execution.go | 92 ++++++ pkg/scheduler/task_state.go | 43 ++- pkg/tasks/check_clients_are_healthy/task.go | 43 ++- .../check_consensus_attestation_stats/task.go | 62 +++- .../check_consensus_block_proposals/task.go | 28 +- pkg/tasks/check_consensus_finality/task.go | 26 +- pkg/tasks/check_consensus_forks/task.go | 34 +- pkg/tasks/check_consensus_identity/task.go | 43 ++- .../check_consensus_proposer_duty/task.go | 32 +- pkg/tasks/check_consensus_reorgs/task.go | 22 +- pkg/tasks/check_consensus_slot_range/task.go | 50 ++- pkg/tasks/check_consensus_sync_status/task.go | 25 +- .../check_consensus_validator_status/task.go | 28 +- pkg/tasks/check_eth_call/task.go | 30 +- pkg/tasks/check_eth_config/task.go | 12 +- pkg/tasks/check_execution_sync_status/task.go | 25 +- pkg/tasks/generate_attestations/task.go | 32 ++ pkg/tasks/generate_blob_transactions/task.go | 13 + pkg/tasks/generate_bls_changes/task.go | 35 +- pkg/tasks/generate_child_wallet/task.go | 17 +- pkg/tasks/generate_consolidations/task.go | 32 +- pkg/tasks/generate_deposits/task.go | 40 ++- pkg/tasks/generate_eoa_transactions/task.go | 13 + pkg/tasks/generate_exits/task.go | 22 ++ pkg/tasks/generate_slashings/task.go | 19 ++ pkg/tasks/generate_transaction/task.go | 36 ++- .../generate_withdrawal_requests/task.go | 27 +- pkg/tasks/get_consensus_specs/task.go | 12 +- pkg/tasks/get_consensus_validators/task.go | 27 +- pkg/tasks/get_execution_block/task.go | 12 +- pkg/tasks/get_pubkeys_from_mnemonic/task.go | 12 +- pkg/tasks/get_random_mnemonic/task.go | 12 +- pkg/tasks/get_wallet_details/task.go | 27 +- pkg/tasks/run_command/task.go | 18 +- pkg/tasks/run_external_tasks/task.go | 13 + pkg/tasks/run_shell/task.go | 4 + pkg/tasks/run_task_background/task.go | 8 + pkg/tasks/run_task_matrix/task.go | 11 + pkg/tasks/run_task_options/task.go | 7 + pkg/tasks/run_tasks/task.go | 9 + pkg/tasks/run_tasks_concurrent/task.go | 11 + pkg/tasks/schema.go | 185 +++++++++++ pkg/tasks/sleep/task.go | 40 ++- pkg/types/coordinator.go | 2 + pkg/types/scheduler.go | 2 + pkg/types/task.go | 53 +-- pkg/web/api/get_task_descriptors_api.go | 42 +++ pkg/web/server.go | 25 ++ 55 files changed, 2046 insertions(+), 113 deletions(-) create mode 100644 pkg/events/bus.go create mode 100644 pkg/events/event.go create mode 100644 pkg/events/sse.go create mode 100644 pkg/logger/eventbushook.go create mode 100644 pkg/tasks/schema.go create mode 100644 pkg/web/api/get_task_descriptors_api.go diff --git a/pkg/assertoor/coordinator.go b/pkg/assertoor/coordinator.go index bd8524b7..f4a09adb 100644 --- a/pkg/assertoor/coordinator.go +++ b/pkg/assertoor/coordinator.go @@ -15,6 +15,7 @@ import ( "github.com/ethpandaops/assertoor/pkg/clients" "github.com/ethpandaops/assertoor/pkg/clients/consensus" "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/events" "github.com/ethpandaops/assertoor/pkg/logger" "github.com/ethpandaops/assertoor/pkg/names" "github.com/ethpandaops/assertoor/pkg/test" @@ -41,6 +42,7 @@ type Coordinator struct { validatorNames *names.ValidatorNames globalVars types.Variables metricsPort int + eventBus *events.EventBus registry *TestRegistry runner *TestRunner @@ -146,6 +148,19 @@ func (c *Coordinator) Run(ctx context.Context) error { c.globalVars.SetVar(name, value) } + // init event bus + c.eventBus = events.NewEventBus(c.log.GetLogger()) + + err = c.eventBus.Start(ctx) + if err != nil { + return fmt.Errorf("failed to start event bus: %w", err) + } + + defer func() { + //nolint:errcheck // ignore error on shutdown + c.eventBus.Stop() + }() + // init webserver if c.Config.Web != nil { if c.Config.Web.Server != nil { @@ -154,7 +169,7 @@ func (c *Coordinator) Run(ctx context.Context) error { return err } - err = c.webserver.ConfigureRoutes(c.Config.Web.Frontend, c.Config.Web.API, c, false) + err = c.webserver.ConfigureRoutesWithEventBus(c.Config.Web.Frontend, c.Config.Web.API, c, false, c.eventBus) if err != nil { return err } @@ -166,7 +181,7 @@ func (c *Coordinator) Run(ctx context.Context) error { return err } - err = c.publicWebserver.ConfigureRoutes(c.Config.Web.Frontend, nil, c, true) + err = c.publicWebserver.ConfigureRoutesWithEventBus(c.Config.Web.Frontend, nil, c, true, nil) if err != nil { return err } @@ -237,6 +252,10 @@ func (c *Coordinator) TestRegistry() types.TestRegistry { return c.registry } +func (c *Coordinator) EventBus() *events.EventBus { + return c.eventBus +} + func (c *Coordinator) GetTestByRunID(runID uint64) types.Test { testRef := c.runner.GetTestByRunID(runID) if testRef != nil { diff --git a/pkg/events/bus.go b/pkg/events/bus.go new file mode 100644 index 00000000..b41ef6d7 --- /dev/null +++ b/pkg/events/bus.go @@ -0,0 +1,227 @@ +package events + +import ( + "context" + "sync" + "sync/atomic" + + "github.com/sirupsen/logrus" +) + +// FilterFunc is a function that filters events for a subscriber. +type FilterFunc func(*Event) bool + +// Subscriber represents a subscriber to the event bus. +type Subscriber struct { + id uint64 + filter FilterFunc + channel chan *Event + closed atomic.Bool +} + +// Channel returns the channel for receiving events. +func (s *Subscriber) Channel() <-chan *Event { + return s.channel +} + +// Bus is the interface for the event bus. +type Bus interface { + Start(ctx context.Context) error + Stop() error + Publish(event *Event) + Subscribe(filter FilterFunc) *Subscriber + Unsubscribe(sub *Subscriber) +} + +// EventBus is the default implementation of the Bus interface. +type EventBus struct { + logger logrus.FieldLogger + ctx context.Context + cancel context.CancelFunc + wg sync.WaitGroup + eventChan chan *Event + subscribersMu sync.RWMutex + subscribers map[uint64]*Subscriber + nextSubID atomic.Uint64 + nextEventID atomic.Uint64 + bufferSize int + subscriberBuf int +} + +// NewEventBus creates a new event bus. +func NewEventBus(logger logrus.FieldLogger) *EventBus { + return &EventBus{ + logger: logger.WithField("component", "eventbus"), + eventChan: make(chan *Event, 1000), + subscribers: make(map[uint64]*Subscriber, 16), + bufferSize: 1000, + subscriberBuf: 100, + } +} + +// Start starts the event bus processing loop. +func (eb *EventBus) Start(ctx context.Context) error { + eb.ctx, eb.cancel = context.WithCancel(ctx) + + eb.wg.Add(1) + + go eb.processEvents() + + eb.logger.Info("event bus started") + + return nil +} + +// Stop stops the event bus. +func (eb *EventBus) Stop() error { + if eb.cancel != nil { + eb.cancel() + } + + eb.wg.Wait() + + // Close all subscriber channels + eb.subscribersMu.Lock() + + for _, sub := range eb.subscribers { + if sub.closed.CompareAndSwap(false, true) { + close(sub.channel) + } + } + + eb.subscribers = make(map[uint64]*Subscriber, 16) + eb.subscribersMu.Unlock() + + eb.logger.Info("event bus stopped") + + return nil +} + +// Publish publishes an event to all subscribers. +func (eb *EventBus) Publish(event *Event) { + if event == nil { + return + } + + // Assign event ID + event.ID = eb.nextEventID.Add(1) + + select { + case eb.eventChan <- event: + default: + eb.logger.Warn("event channel full, dropping event") + } +} + +// Subscribe creates a new subscription with an optional filter. +func (eb *EventBus) Subscribe(filter FilterFunc) *Subscriber { + sub := &Subscriber{ + id: eb.nextSubID.Add(1), + filter: filter, + channel: make(chan *Event, eb.subscriberBuf), + } + + eb.subscribersMu.Lock() + eb.subscribers[sub.id] = sub + eb.subscribersMu.Unlock() + + eb.logger.WithField("subscriber_id", sub.id).Debug("new subscriber added") + + return sub +} + +// Unsubscribe removes a subscriber from the event bus. +func (eb *EventBus) Unsubscribe(sub *Subscriber) { + if sub == nil { + return + } + + eb.subscribersMu.Lock() + + if _, exists := eb.subscribers[sub.id]; exists { + delete(eb.subscribers, sub.id) + + if sub.closed.CompareAndSwap(false, true) { + close(sub.channel) + } + } + + eb.subscribersMu.Unlock() + + eb.logger.WithField("subscriber_id", sub.id).Debug("subscriber removed") +} + +// processEvents is the main event processing loop. +func (eb *EventBus) processEvents() { + defer eb.wg.Done() + + for { + select { + case <-eb.ctx.Done(): + return + case event := <-eb.eventChan: + eb.dispatchEvent(event) + } + } +} + +// dispatchEvent sends an event to all matching subscribers. +func (eb *EventBus) dispatchEvent(event *Event) { + eb.subscribersMu.RLock() + defer eb.subscribersMu.RUnlock() + + for _, sub := range eb.subscribers { + // Skip if filter doesn't match + if sub.filter != nil && !sub.filter(event) { + continue + } + + // Try to send event, drop if subscriber channel is full + select { + case sub.channel <- event: + default: + eb.logger.WithFields(logrus.Fields{ + "subscriber_id": sub.id, + "event_type": event.Type, + }).Debug("subscriber channel full, dropping event") + } + } +} + +// CreateTestRunFilter creates a filter for a specific test run. +func CreateTestRunFilter(testRunID uint64) FilterFunc { + return func(e *Event) bool { + return e.TestRunID == testRunID + } +} + +// CreateEventTypeFilter creates a filter for specific event types. +func CreateEventTypeFilter(eventTypes ...EventType) FilterFunc { + typeSet := make(map[EventType]struct{}, len(eventTypes)) + for _, t := range eventTypes { + typeSet[t] = struct{}{} + } + + return func(e *Event) bool { + _, ok := typeSet[e.Type] + return ok + } +} + +// CombineFilters combines multiple filters with AND logic. +func CombineFilters(filters ...FilterFunc) FilterFunc { + return func(e *Event) bool { + for _, f := range filters { + if f != nil && !f(e) { + return false + } + } + + return true + } +} + +// NewCustomEvent creates a new event with a custom event type string. +func (eb *EventBus) NewCustomEvent(eventType string, testRunID, taskIndex uint64, data any) (*Event, error) { + return NewCustomEvent(eventType, testRunID, taskIndex, data) +} diff --git a/pkg/events/event.go b/pkg/events/event.go new file mode 100644 index 00000000..cbd06af6 --- /dev/null +++ b/pkg/events/event.go @@ -0,0 +1,124 @@ +package events + +import ( + "encoding/json" + "time" +) + +// EventType represents the type of event being published. +type EventType string + +// Event types for test lifecycle. +const ( + EventTestStarted EventType = "test.started" + EventTestCompleted EventType = "test.completed" + EventTestFailed EventType = "test.failed" +) + +// Event types for task lifecycle. +const ( + EventTaskStarted EventType = "task.started" + EventTaskProgress EventType = "task.progress" + EventTaskCompleted EventType = "task.completed" + EventTaskFailed EventType = "task.failed" + EventTaskLog EventType = "task.log" +) + +// Event represents a single event in the system. +type Event struct { + ID uint64 `json:"id"` + Type EventType `json:"type"` + Timestamp time.Time `json:"timestamp"` + TestRunID uint64 `json:"testRunId,omitempty"` + TaskIndex uint64 `json:"taskIndex,omitempty"` + Data json.RawMessage `json:"data,omitempty"` +} + +// TestStartedData contains data for test.started events. +type TestStartedData struct { + TestID string `json:"testId"` + TestName string `json:"testName"` +} + +// TestCompletedData contains data for test.completed events. +type TestCompletedData struct { + TestID string `json:"testId"` + TestName string `json:"testName"` + Status string `json:"status"` +} + +// TestFailedData contains data for test.failed events. +type TestFailedData struct { + TestID string `json:"testId"` + TestName string `json:"testName"` + Error string `json:"error,omitempty"` +} + +// TaskStartedData contains data for task.started events. +type TaskStartedData struct { + TaskName string `json:"taskName"` + TaskTitle string `json:"taskTitle"` + TaskID string `json:"taskId,omitempty"` +} + +// TaskProgressData contains data for task.progress events. +type TaskProgressData struct { + TaskName string `json:"taskName"` + TaskTitle string `json:"taskTitle"` + TaskID string `json:"taskId,omitempty"` + Progress float64 `json:"progress"` + Message string `json:"message,omitempty"` +} + +// TaskCompletedData contains data for task.completed events. +type TaskCompletedData struct { + TaskName string `json:"taskName"` + TaskTitle string `json:"taskTitle"` + TaskID string `json:"taskId,omitempty"` + Result string `json:"result"` +} + +// TaskFailedData contains data for task.failed events. +type TaskFailedData struct { + TaskName string `json:"taskName"` + TaskTitle string `json:"taskTitle"` + TaskID string `json:"taskId,omitempty"` + Error string `json:"error,omitempty"` +} + +// TaskLogData contains data for task.log events. +type TaskLogData struct { + TaskName string `json:"taskName"` + TaskID string `json:"taskId,omitempty"` + Level string `json:"level"` + Message string `json:"message"` + Fields map[string]any `json:"fields,omitempty"` + Timestamp time.Time `json:"timestamp"` +} + +// NewEvent creates a new event with the given type and data. +func NewEvent(eventType EventType, testRunID, taskIndex uint64, data any) (*Event, error) { + var rawData json.RawMessage + + if data != nil { + var err error + + rawData, err = json.Marshal(data) + if err != nil { + return nil, err + } + } + + return &Event{ + Type: eventType, + Timestamp: time.Now(), + TestRunID: testRunID, + TaskIndex: taskIndex, + Data: rawData, + }, nil +} + +// NewCustomEvent creates a new event with a custom event type string. +func NewCustomEvent(eventType string, testRunID, taskIndex uint64, data any) (*Event, error) { + return NewEvent(EventType(eventType), testRunID, taskIndex, data) +} diff --git a/pkg/events/sse.go b/pkg/events/sse.go new file mode 100644 index 00000000..e149562c --- /dev/null +++ b/pkg/events/sse.go @@ -0,0 +1,303 @@ +package events + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/sirupsen/logrus" +) + +// SSEHandler handles Server-Sent Events connections. +type SSEHandler struct { + logger logrus.FieldLogger + eventBus *EventBus +} + +// NewSSEHandler creates a new SSE handler. +func NewSSEHandler(logger logrus.FieldLogger, eventBus *EventBus) *SSEHandler { + return &SSEHandler{ + logger: logger.WithField("component", "sse"), + eventBus: eventBus, + } +} + +// HandleGlobalStream handles the global event stream endpoint. +func (h *SSEHandler) HandleGlobalStream(w http.ResponseWriter, r *http.Request) { + h.handleSSE(w, r, nil) +} + +// HandleTestRunStream handles the per-test event stream endpoint. +func (h *SSEHandler) HandleTestRunStream(w http.ResponseWriter, r *http.Request, testRunID uint64) { + filter := CreateTestRunFilter(testRunID) + h.handleSSE(w, r, filter) +} + +// handleSSE is the common SSE handling logic. +func (h *SSEHandler) handleSSE(w http.ResponseWriter, r *http.Request, filter FilterFunc) { + // Check if the client supports SSE + flusher, ok := w.(http.Flusher) + if !ok { + http.Error(w, "SSE not supported", http.StatusInternalServerError) + return + } + + // Set SSE headers + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Connection", "keep-alive") + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("X-Accel-Buffering", "no") + + // Parse optional lastEventId for reconnection + lastEventIDStr := r.Header.Get("Last-Event-ID") + if lastEventIDStr == "" { + lastEventIDStr = r.URL.Query().Get("lastEventId") + } + + var lastEventID uint64 + + if lastEventIDStr != "" { + var err error + + lastEventID, err = strconv.ParseUint(lastEventIDStr, 10, 64) + if err != nil { + h.logger.WithError(err).Warn("invalid Last-Event-ID") + } + } + + // Subscribe to events + sub := h.eventBus.Subscribe(filter) + defer h.eventBus.Unsubscribe(sub) + + ctx := r.Context() + + h.logger.WithField("last_event_id", lastEventID).Debug("SSE client connected") + + // Send initial connection event + h.sendEvent(w, flusher, &Event{ + ID: 0, + Type: "connected", + Timestamp: time.Now(), + }) + + // Keep-alive ticker + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + h.logger.Debug("SSE client disconnected") + return + + case event := <-sub.Channel(): + if event == nil { + return + } + + // Skip events before lastEventID for reconnection support + if lastEventID > 0 && event.ID <= lastEventID { + continue + } + + h.sendEvent(w, flusher, event) + + case <-ticker.C: + // Send keep-alive comment + h.sendKeepAlive(w, flusher) + } + } +} + +// sendEvent sends an SSE event to the client. +func (h *SSEHandler) sendEvent(w http.ResponseWriter, flusher http.Flusher, event *Event) { + data, err := json.Marshal(event) + if err != nil { + h.logger.WithError(err).Error("failed to marshal event") + return + } + + // Write event ID for reconnection support + if event.ID > 0 { + fmt.Fprintf(w, "id: %d\n", event.ID) + } + + // Write event type + fmt.Fprintf(w, "event: %s\n", event.Type) + + // Write data + fmt.Fprintf(w, "data: %s\n\n", data) + + flusher.Flush() +} + +// sendKeepAlive sends a keep-alive comment to prevent connection timeout. +func (h *SSEHandler) sendKeepAlive(w http.ResponseWriter, flusher http.Flusher) { + fmt.Fprint(w, ": keep-alive\n\n") + flusher.Flush() +} + +// SSEMiddleware wraps an SSE handler with common functionality. +type SSEMiddleware struct { + handler *SSEHandler + eventBus *EventBus +} + +// NewSSEMiddleware creates a new SSE middleware. +func NewSSEMiddleware(logger logrus.FieldLogger, eventBus *EventBus) *SSEMiddleware { + return &SSEMiddleware{ + handler: NewSSEHandler(logger, eventBus), + eventBus: eventBus, + } +} + +// GlobalStreamHandler returns an http.HandlerFunc for the global event stream. +func (m *SSEMiddleware) GlobalStreamHandler() http.HandlerFunc { + return m.handler.HandleGlobalStream +} + +// TestRunStreamHandler returns a function that creates handlers for test run streams. +func (m *SSEMiddleware) TestRunStreamHandler(testRunID uint64) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + m.handler.HandleTestRunStream(w, r, testRunID) + } +} + +// PublishTestStarted publishes a test started event. +func (eb *EventBus) PublishTestStarted(testRunID uint64, testID, testName string) { + event, err := NewEvent(EventTestStarted, testRunID, 0, &TestStartedData{ + TestID: testID, + TestName: testName, + }) + if err != nil { + return + } + + eb.Publish(event) +} + +// PublishTestCompleted publishes a test completed event. +func (eb *EventBus) PublishTestCompleted(testRunID uint64, testID, testName, status string) { + event, err := NewEvent(EventTestCompleted, testRunID, 0, &TestCompletedData{ + TestID: testID, + TestName: testName, + Status: status, + }) + if err != nil { + return + } + + eb.Publish(event) +} + +// PublishTestFailed publishes a test failed event. +func (eb *EventBus) PublishTestFailed(testRunID uint64, testID, testName, errMsg string) { + event, err := NewEvent(EventTestFailed, testRunID, 0, &TestFailedData{ + TestID: testID, + TestName: testName, + Error: errMsg, + }) + if err != nil { + return + } + + eb.Publish(event) +} + +// PublishTaskStarted publishes a task started event. +func (eb *EventBus) PublishTaskStarted( + testRunID, taskIndex uint64, + taskName, taskTitle, taskID string, +) { + event, err := NewEvent(EventTaskStarted, testRunID, taskIndex, &TaskStartedData{ + TaskName: taskName, + TaskTitle: taskTitle, + TaskID: taskID, + }) + if err != nil { + return + } + + eb.Publish(event) +} + +// PublishTaskProgress publishes a task progress event. +func (eb *EventBus) PublishTaskProgress( + testRunID, taskIndex uint64, + taskName, taskTitle, taskID string, + progress float64, + message string, +) { + event, err := NewEvent(EventTaskProgress, testRunID, taskIndex, &TaskProgressData{ + TaskName: taskName, + TaskTitle: taskTitle, + TaskID: taskID, + Progress: progress, + Message: message, + }) + if err != nil { + return + } + + eb.Publish(event) +} + +// PublishTaskCompleted publishes a task completed event. +func (eb *EventBus) PublishTaskCompleted( + testRunID, taskIndex uint64, + taskName, taskTitle, taskID, result string, +) { + event, err := NewEvent(EventTaskCompleted, testRunID, taskIndex, &TaskCompletedData{ + TaskName: taskName, + TaskTitle: taskTitle, + TaskID: taskID, + Result: result, + }) + if err != nil { + return + } + + eb.Publish(event) +} + +// PublishTaskFailed publishes a task failed event. +func (eb *EventBus) PublishTaskFailed( + testRunID, taskIndex uint64, + taskName, taskTitle, taskID, errMsg string, +) { + event, err := NewEvent(EventTaskFailed, testRunID, taskIndex, &TaskFailedData{ + TaskName: taskName, + TaskTitle: taskTitle, + TaskID: taskID, + Error: errMsg, + }) + if err != nil { + return + } + + eb.Publish(event) +} + +// PublishTaskLog publishes a task log event. +func (eb *EventBus) PublishTaskLog( + testRunID, taskIndex uint64, + taskName, taskID, level, message string, + fields map[string]any, +) { + event, err := NewEvent(EventTaskLog, testRunID, taskIndex, &TaskLogData{ + TaskName: taskName, + TaskID: taskID, + Level: level, + Message: message, + Fields: fields, + Timestamp: time.Now(), + }) + if err != nil { + return + } + + eb.Publish(event) +} diff --git a/pkg/logger/eventbushook.go b/pkg/logger/eventbushook.go new file mode 100644 index 00000000..8f984c81 --- /dev/null +++ b/pkg/logger/eventbushook.go @@ -0,0 +1,48 @@ +package logger + +import ( + "maps" + + "github.com/sirupsen/logrus" +) + +type logEventBusHook struct { + logger *LogScope + eventBus EventBusPublisher +} + +func newLogEventBusHook(logger *LogScope, eventBus EventBusPublisher) *logEventBusHook { + return &logEventBusHook{ + logger: logger, + eventBus: eventBus, + } +} + +func (h *logEventBusHook) Levels() []logrus.Level { + return logrus.AllLevels +} + +func (h *logEventBusHook) Fire(entry *logrus.Entry) error { + if h.eventBus == nil { + return nil + } + + // Convert logrus fields to map[string]any + var fields map[string]any + if len(entry.Data) > 0 { + fields = make(map[string]any, len(entry.Data)) + maps.Copy(fields, entry.Data) + } + + h.eventBus.PublishTaskLog( + h.logger.options.TestRunID, + h.logger.options.TaskID, + h.logger.options.TaskName, + h.logger.options.TaskRefID, + entry.Level.String(), + entry.Message, + fields, + ) + + return nil +} diff --git a/pkg/logger/logscope.go b/pkg/logger/logscope.go index 5abf86dd..19437438 100644 --- a/pkg/logger/logscope.go +++ b/pkg/logger/logscope.go @@ -22,8 +22,16 @@ type ScopeOptions struct { BufferSize uint64 FlushDelay time.Duration Database *db.Database + EventBus EventBusPublisher TestRunID uint64 TaskID uint64 + TaskName string + TaskRefID string +} + +// EventBusPublisher defines the interface for publishing log events. +type EventBusPublisher interface { + PublishTaskLog(testRunID, taskIndex uint64, taskName, taskID, level, message string, fields map[string]any) } type logForwarder struct { @@ -64,6 +72,10 @@ func NewLogger(options *ScopeOptions) *LogScope { logger.logger.AddHook(logger.memBuffer) } + if options.EventBus != nil { + logger.logger.AddHook(newLogEventBusHook(logger, options.EventBus)) + } + return logger } diff --git a/pkg/scheduler/services.go b/pkg/scheduler/services.go index fe8dec2b..a83960bd 100644 --- a/pkg/scheduler/services.go +++ b/pkg/scheduler/services.go @@ -3,6 +3,7 @@ package scheduler import ( "github.com/ethpandaops/assertoor/pkg/clients" "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/events" "github.com/ethpandaops/assertoor/pkg/names" "github.com/ethpandaops/assertoor/pkg/types" "github.com/ethpandaops/assertoor/pkg/wallet" @@ -13,14 +14,16 @@ type servicesProvider struct { clientPool *clients.ClientPool walletManager *wallet.Manager validatorNames *names.ValidatorNames + eventBus *events.EventBus } -func NewServicesProvider(database *db.Database, clientPool *clients.ClientPool, walletManager *wallet.Manager, validatorNames *names.ValidatorNames) types.TaskServices { +func NewServicesProvider(database *db.Database, clientPool *clients.ClientPool, walletManager *wallet.Manager, validatorNames *names.ValidatorNames, eventBus *events.EventBus) types.TaskServices { return &servicesProvider{ database: database, clientPool: clientPool, walletManager: walletManager, validatorNames: validatorNames, + eventBus: eventBus, } } @@ -39,3 +42,7 @@ func (p *servicesProvider) WalletManager() *wallet.Manager { func (p *servicesProvider) ValidatorNames() *names.ValidatorNames { return p.validatorNames } + +func (p *servicesProvider) EventBus() *events.EventBus { + return p.eventBus +} diff --git a/pkg/scheduler/task_execution.go b/pkg/scheduler/task_execution.go index c0068939..5c1c3eb9 100644 --- a/pkg/scheduler/task_execution.go +++ b/pkg/scheduler/task_execution.go @@ -34,6 +34,9 @@ func (ts *TaskScheduler) ExecuteTask(ctx context.Context, taskIndex types.TaskIn taskLogger.Errorf("task state update on db failed: %v", err) } + // emit task started event + ts.emitTaskStarted(taskState) + defer func() { taskState.isRunning = false taskState.stopTime = time.Now() @@ -89,6 +92,18 @@ func (ts *TaskScheduler) ExecuteTask(ctx context.Context, taskIndex types.TaskIn SetResult: func(result types.TaskResult) { taskState.setTaskResult(result, true) }, + ReportProgress: func(percent float64, message string) { + taskState.SetProgress(percent, message) + ts.emitTaskProgress(taskState, percent, message) + }, + EmitEvent: func(eventType string, data any) { + if eventBus := ts.services.EventBus(); eventBus != nil { + event, err := ts.services.EventBus().NewCustomEvent(eventType, ts.testRunID, uint64(taskState.index), data) + if err == nil { + eventBus.Publish(event) + } + } + }, } // create task instance @@ -174,14 +189,91 @@ func (ts *TaskScheduler) ExecuteTask(ctx context.Context, taskIndex types.TaskIn if taskState.taskResult == types.TaskResultFailure { taskLogger.Warnf("task failed with failure result: %v", taskState.taskError) + ts.emitTaskFailed(taskState) + return fmt.Errorf("task failed: %w", taskState.taskError) } taskLogger.Infof("task completed") + ts.emitTaskCompleted(taskState) return nil } +func (ts *TaskScheduler) emitTaskStarted(taskState *taskState) { + eventBus := ts.services.EventBus() + if eventBus == nil { + return + } + + eventBus.PublishTaskStarted( + ts.testRunID, + uint64(taskState.index), + taskState.options.Name, + taskState.Title(), + taskState.options.ID, + ) +} + +func (ts *TaskScheduler) emitTaskCompleted(taskState *taskState) { + eventBus := ts.services.EventBus() + if eventBus == nil { + return + } + + resultStr := "success" + if taskState.taskResult == types.TaskResultNone { + resultStr = "none" + } + + eventBus.PublishTaskCompleted( + ts.testRunID, + uint64(taskState.index), + taskState.options.Name, + taskState.Title(), + taskState.options.ID, + resultStr, + ) +} + +func (ts *TaskScheduler) emitTaskFailed(taskState *taskState) { + eventBus := ts.services.EventBus() + if eventBus == nil { + return + } + + errMsg := "" + if taskState.taskError != nil { + errMsg = taskState.taskError.Error() + } + + eventBus.PublishTaskFailed( + ts.testRunID, + uint64(taskState.index), + taskState.options.Name, + taskState.Title(), + taskState.options.ID, + errMsg, + ) +} + +func (ts *TaskScheduler) emitTaskProgress(taskState *taskState, percent float64, message string) { + eventBus := ts.services.EventBus() + if eventBus == nil { + return + } + + eventBus.PublishTaskProgress( + ts.testRunID, + uint64(taskState.index), + taskState.options.Name, + taskState.Title(), + taskState.options.ID, + percent, + message, + ) +} + func (ts *TaskScheduler) WatchTaskPass(ctx context.Context, cancelFn context.CancelFunc, taskIndex types.TaskIndex) { taskState := ts.GetTaskState(taskIndex) diff --git a/pkg/scheduler/task_state.go b/pkg/scheduler/task_state.go index e05b5c8b..b119dc58 100644 --- a/pkg/scheduler/task_state.go +++ b/pkg/scheduler/task_state.go @@ -33,7 +33,7 @@ type taskState struct { startTime time.Time stopTime time.Time - taskConfig interface{} + taskConfig any taskOutputs types.Variables taskStatusVars types.Variables @@ -43,6 +43,9 @@ type taskState struct { resultNotifyChan chan bool resultMutex sync.RWMutex + progress float64 + progressMessage string + dbTaskState *db.TaskState } @@ -78,8 +81,11 @@ func (ts *TaskScheduler) newTaskState(options *types.TaskOptions, parentState *t Parent: ts.logger.WithField("task", options.Name).WithField("taskidx", taskIdx), BufferSize: 1000, Database: ts.services.Database(), + EventBus: ts.services.EventBus(), TestRunID: ts.testRunID, TaskID: uint64(taskIdx), + TaskName: options.Name, + TaskRefID: options.ID, }), taskOutputs: vars.NewVariables(nil), taskStatusVars: vars.NewVariables(nil), @@ -265,17 +271,20 @@ func (ts *taskState) setTaskResult(result types.TaskResult, setUpdated bool) { func (ts *taskState) GetTaskStatus() *types.TaskStatus { taskStatus := &types.TaskStatus{ - Index: ts.index, - ParentIndex: 0, - IsStarted: ts.isStarted, - IsRunning: ts.isRunning, - IsSkipped: ts.isSkipped, - StartTime: ts.startTime, - StopTime: ts.stopTime, - Result: ts.taskResult, - Error: ts.taskError, - Logger: ts.logger, + Index: ts.index, + ParentIndex: 0, + IsStarted: ts.isStarted, + IsRunning: ts.isRunning, + IsSkipped: ts.isSkipped, + StartTime: ts.startTime, + StopTime: ts.stopTime, + Result: ts.taskResult, + Error: ts.taskError, + Logger: ts.logger, + Progress: ts.progress, + ProgressMessage: ts.progressMessage, } + if ts.parentState != nil { taskStatus.ParentIndex = ts.parentState.index } @@ -283,6 +292,16 @@ func (ts *taskState) GetTaskStatus() *types.TaskStatus { return taskStatus } +func (ts *taskState) SetProgress(percent float64, message string) { + ts.resultMutex.Lock() + defer ts.resultMutex.Unlock() + + ts.progress = percent + ts.progressMessage = message + ts.taskStatusVars.SetVar("progress", percent) + ts.taskStatusVars.SetVar("progressMessage", message) +} + func (ts *taskState) GetTaskStatusVars() types.Variables { return ts.taskStatusVars } @@ -343,7 +362,7 @@ func (ts *taskState) Description() string { return ts.descriptor.Description } -func (ts *taskState) Config() interface{} { +func (ts *taskState) Config() any { if ts.task != nil { return ts.task.Config() } diff --git a/pkg/tasks/check_clients_are_healthy/task.go b/pkg/tasks/check_clients_are_healthy/task.go index 67bcdfe9..794fa0da 100644 --- a/pkg/tasks/check_clients_are_healthy/task.go +++ b/pkg/tasks/check_clients_are_healthy/task.go @@ -18,8 +18,36 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Checks if clients are healthy.", + Category: "utility", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "goodClients", + Type: "array", + Description: "Array of healthy client info objects.", + }, + { + Name: "failedClients", + Type: "array", + Description: "Array of unhealthy client info objects.", + }, + { + Name: "totalCount", + Type: "int", + Description: "Total number of clients checked.", + }, + { + Name: "failedCount", + Type: "int", + Description: "Number of clients that failed health check.", + }, + { + Name: "goodCount", + Type: "int", + Description: "Number of clients that passed health check.", + }, + }, + NewTask: NewTask, } ) @@ -79,19 +107,21 @@ func (t *Task) LoadConfig() error { } func (t *Task) Execute(ctx context.Context) error { - t.processCheck() + checkCount := 0 for { + checkCount++ + t.processCheck(checkCount) + select { case <-time.After(t.config.PollInterval.Duration): - t.processCheck() case <-ctx.Done(): return nil } } } -func (t *Task) processCheck() { +func (t *Task) processCheck(checkCount int) { expectedResult := !t.config.ExpectUnhealthy passResultCount := 0 totalClientCount := 0 @@ -150,16 +180,21 @@ func (t *Task) processCheck() { case t.config.MaxUnhealthyCount > -1 && len(failedClients) > t.config.MaxUnhealthyCount: if t.config.FailOnCheckMiss { t.ctx.SetResult(types.TaskResultFailure) + t.ctx.ReportProgress(0, fmt.Sprintf("Too many unhealthy clients: %d (attempt %d)", len(failedClients), checkCount)) } else { t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Too many unhealthy clients: %d (attempt %d)", len(failedClients), checkCount)) } case resultPass: t.ctx.SetResult(types.TaskResultSuccess) + t.ctx.ReportProgress(100, fmt.Sprintf("All clients healthy: %d/%d", passResultCount, totalClientCount)) default: if t.config.FailOnCheckMiss { t.ctx.SetResult(types.TaskResultFailure) + t.ctx.ReportProgress(0, fmt.Sprintf("Unhealthy clients: %v (attempt %d)", failedClientNames, checkCount)) } else { t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for healthy clients... %d/%d (attempt %d)", passResultCount, totalClientCount, checkCount)) } } } diff --git a/pkg/tasks/check_consensus_attestation_stats/task.go b/pkg/tasks/check_consensus_attestation_stats/task.go index 91bfd485..23863a58 100644 --- a/pkg/tasks/check_consensus_attestation_stats/task.go +++ b/pkg/tasks/check_consensus_attestation_stats/task.go @@ -19,8 +19,56 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Check attestation stats for consensus chain.", + Category: "consensus", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "lastCheckedEpoch", + Type: "uint64", + Description: "The last epoch that was checked for attestation stats.", + }, + { + Name: "validatorCount", + Type: "uint64", + Description: "Number of active validators in the epoch.", + }, + { + Name: "validatorBalance", + Type: "uint64", + Description: "Total effective balance of active validators.", + }, + { + Name: "targetVotes", + Type: "uint64", + Description: "Number of correct target votes.", + }, + { + Name: "targetVotesPercent", + Type: "float64", + Description: "Percentage of correct target votes.", + }, + { + Name: "headVotes", + Type: "uint64", + Description: "Number of correct head votes.", + }, + { + Name: "headVotesPercent", + Type: "float64", + Description: "Percentage of correct head votes.", + }, + { + Name: "totalVotes", + Type: "uint64", + Description: "Total number of attestation votes.", + }, + { + Name: "totalVotesPercent", + Type: "float64", + Description: "Percentage of total attestation participation.", + }, + }, + NewTask: NewTask, } ) @@ -115,6 +163,8 @@ func (t *Task) Execute(ctx context.Context) error { specs := consensusPool.GetBlockCache().GetSpecs() consensusPool.GetBlockCache().SetMinFollowDistance(specs.SlotsPerEpoch * 4) + checkCount := 0 + for { select { case block := <-blockSubscription.Channel(): @@ -128,7 +178,8 @@ func (t *Task) Execute(ctx context.Context) error { break } - t.runAttestationStatsCheck(ctx, checkEpoch) + checkCount++ + t.runAttestationStatsCheck(ctx, checkEpoch, checkCount) lastCheckedEpoch = checkEpoch @@ -212,7 +263,7 @@ func (t *Task) processBlock(ctx context.Context, block *consensus.Block) { t.attesterDutyMap[currentBlockEpoch][parentBlock.Root] = attesterDuties } -func (t *Task) runAttestationStatsCheck(ctx context.Context, epoch uint64) { +func (t *Task) runAttestationStatsCheck(ctx context.Context, epoch uint64, checkCount int) { consensusPool := t.ctx.Scheduler.GetServices().ClientPool().GetConsensusPool() canonicalFork := consensusPool.GetCanonicalFork(1) @@ -231,13 +282,18 @@ func (t *Task) runAttestationStatsCheck(ctx context.Context, epoch uint64) { t.passedEpochs++ if t.passedEpochs >= t.config.MinCheckedEpochs { t.ctx.SetResult(types.TaskResultSuccess) + t.ctx.ReportProgress(100, fmt.Sprintf("Attestation stats check passed for epoch %d", epoch)) + } else { + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for attestation stats... %d/%d (attempt %d)", t.passedEpochs, t.config.MinCheckedEpochs, checkCount)) } } else { t.passedEpochs = 0 if t.config.FailOnCheckMiss { t.ctx.SetResult(types.TaskResultFailure) + t.ctx.ReportProgress(0, fmt.Sprintf("Attestation stats check failed for epoch %d (attempt %d)", epoch, checkCount)) } else { t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for attestation stats... (attempt %d)", checkCount)) } } diff --git a/pkg/tasks/check_consensus_block_proposals/task.go b/pkg/tasks/check_consensus_block_proposals/task.go index 798da5f7..dfc88a2d 100644 --- a/pkg/tasks/check_consensus_block_proposals/task.go +++ b/pkg/tasks/check_consensus_block_proposals/task.go @@ -24,8 +24,26 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Check for consensus block proposals that meet specific criteria.", + Category: "consensus", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "matchingBlockRoots", + Type: "array", + Description: "Array of block roots that match the criteria.", + }, + { + Name: "matchingBlockHeaders", + Type: "array", + Description: "Array of block headers that match the criteria.", + }, + { + Name: "matchingBlockBodies", + Type: "array", + Description: "Array of block bodies that match the criteria.", + }, + }, + NewTask: NewTask, } ) @@ -86,8 +104,11 @@ func (t *Task) Execute(ctx context.Context) error { totalMatches := 0 matchingBlocks := []*consensus.Block{} + checkCount := 0 checkBlockMatch := func(block *consensus.Block) bool { + checkCount++ + matches := t.checkBlock(ctx, block) if matches { matchingBlocks = append(matchingBlocks, block) @@ -100,14 +121,19 @@ func (t *Task) Execute(ctx context.Context) error { if totalMatches >= t.config.BlockCount { t.setMatchingBlocksOutput(matchingBlocks) t.ctx.SetResult(types.TaskResultSuccess) + t.ctx.ReportProgress(100, fmt.Sprintf("Found %d matching blocks", totalMatches)) return true } + + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for block proposals... %d/%d (attempt %d)", totalMatches, t.config.BlockCount, checkCount)) } else { if matches { t.ctx.SetResult(types.TaskResultSuccess) + t.ctx.ReportProgress(100, fmt.Sprintf("Found matching block at slot %d", block.Slot)) } else { t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for matching block... (attempt %d)", checkCount)) } } diff --git a/pkg/tasks/check_consensus_finality/task.go b/pkg/tasks/check_consensus_finality/task.go index 6462d0cc..70bfc7f0 100644 --- a/pkg/tasks/check_consensus_finality/task.go +++ b/pkg/tasks/check_consensus_finality/task.go @@ -14,8 +14,26 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Check finality status for consensus chain.", + Category: "consensus", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "finalizedEpoch", + Type: "uint64", + Description: "The latest finalized epoch number.", + }, + { + Name: "finalizedRoot", + Type: "string", + Description: "The root hash of the finalized checkpoint.", + }, + { + Name: "unfinalizedEpochs", + Type: "uint64", + Description: "Number of epochs since the last finalized checkpoint.", + }, + }, + NewTask: NewTask, } ) @@ -79,16 +97,22 @@ func (t *Task) Execute(ctx context.Context) error { checkpointSubscription := consensusPool.GetBlockCache().SubscribeFinalizedEvent(10) defer checkpointSubscription.Unsubscribe() + checkCount := 0 + for { + checkCount++ checkResult := t.runFinalityCheck() switch { case checkResult: t.ctx.SetResult(types.TaskResultSuccess) + t.ctx.ReportProgress(100, "Finality check passed") case t.config.FailOnCheckMiss: t.ctx.SetResult(types.TaskResultFailure) + t.ctx.ReportProgress(0, fmt.Sprintf("Finality check failed (attempt %d)", checkCount)) default: t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for finality... (attempt %d)", checkCount)) } select { diff --git a/pkg/tasks/check_consensus_forks/task.go b/pkg/tasks/check_consensus_forks/task.go index 97be7e00..029f74c9 100644 --- a/pkg/tasks/check_consensus_forks/task.go +++ b/pkg/tasks/check_consensus_forks/task.go @@ -15,8 +15,16 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Check for consensus layer forks.", + Category: "consensus", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "forks", + Type: "array", + Description: "Array of fork info objects with head slot, root, and clients.", + }, + }, + NewTask: NewTask, } ) @@ -88,18 +96,20 @@ func (t *Task) Execute(ctx context.Context) error { } t.startEpoch = currentEpoch.Number() + checkCount := 0 for { select { case <-blockSubscription.Channel(): - t.ctx.SetResult(t.runCheck()) + checkCount++ + t.processCheck(checkCount) case <-ctx.Done(): return ctx.Err() } } } -func (t *Task) runCheck() types.TaskResult { +func (t *Task) processCheck(checkCount int) { consensusPool := t.ctx.Scheduler.GetServices().ClientPool().GetConsensusPool() headForks := consensusPool.GetHeadForks(t.config.MaxForkDistance) headForkInfo := make([]*ForkInfo, len(headForks)) @@ -135,21 +145,31 @@ func (t *Task) runCheck() types.TaskResult { t.logger.Infof("Fork #%v: %v [0x%x] (%v clients: [%v])", idx, fork.Slot, fork.Root, len(fork.AllClients), clients) } - return types.TaskResultFailure + t.ctx.SetResult(types.TaskResultFailure) + t.ctx.ReportProgress(0, fmt.Sprintf("Too many forks: %d (attempt %d)", len(headForks)-1, checkCount)) + + return } _, currentEpoch, err := consensusPool.GetBlockCache().GetWallclock().Now() if err != nil { t.logger.Warnf("check missed: could not get current epoch from wall clock") - return types.TaskResultNone + t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for fork check... (attempt %d)", checkCount)) + + return } epochCount := currentEpoch.Number() - t.startEpoch if t.config.MinCheckEpochCount > 0 && epochCount < t.config.MinCheckEpochCount { t.logger.Warnf("Check missed: checked %v epochs, but need >= %v", epochCount, t.config.MinCheckEpochCount) - return types.TaskResultNone + t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for fork check... %d/%d epochs (attempt %d)", epochCount, t.config.MinCheckEpochCount, checkCount)) + + return } - return types.TaskResultSuccess + t.ctx.SetResult(types.TaskResultSuccess) + t.ctx.ReportProgress(100, fmt.Sprintf("Fork check passed after %d epochs", epochCount)) } diff --git a/pkg/tasks/check_consensus_identity/task.go b/pkg/tasks/check_consensus_identity/task.go index ff89cded..3b089c69 100644 --- a/pkg/tasks/check_consensus_identity/task.go +++ b/pkg/tasks/check_consensus_identity/task.go @@ -23,8 +23,36 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Checks consensus client node identity information including CGC extraction from ENR.", + Category: "consensus", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "matchingClients", + Type: "array", + Description: "List of clients that passed identity checks.", + }, + { + Name: "failedClients", + Type: "array", + Description: "List of clients that failed identity checks.", + }, + { + Name: "totalCount", + Type: "int", + Description: "Total number of clients checked.", + }, + { + Name: "matchingCount", + Type: "int", + Description: "Number of clients that passed checks.", + }, + { + Name: "failedCount", + Type: "int", + Description: "Number of clients that failed checks.", + }, + }, + NewTask: NewTask, } ) @@ -90,19 +118,21 @@ func (t *Task) LoadConfig() error { } func (t *Task) Execute(ctx context.Context) error { - t.processCheck() + checkCount := 0 for { + checkCount++ + t.processCheck(checkCount) + select { case <-time.After(t.config.PollInterval.Duration): - t.processCheck() case <-ctx.Done(): return nil } } } -func (t *Task) processCheck() { +func (t *Task) processCheck(checkCount int) { passResultCount := 0 totalClientCount := 0 matchingClients := []*IdentityCheckResult{} @@ -191,20 +221,25 @@ func (t *Task) processCheck() { if t.config.FailOnCheckMiss { t.logger.Infof("Setting result to FAILURE (too many failures: %d > %d)", len(failedClients), t.config.MaxFailCount) t.ctx.SetResult(types.TaskResultFailure) + t.ctx.ReportProgress(0, fmt.Sprintf("Too many failures: %d (attempt %d)", len(failedClients), checkCount)) } else { t.logger.Infof("Setting result to PENDING (too many failures but failOnCheckMiss=false)") t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for identity check... %d/%d (attempt %d)", passResultCount, requiredPassCount, checkCount)) } case resultPass: t.logger.Infof("Setting result to SUCCESS (requirements met)") t.ctx.SetResult(types.TaskResultSuccess) + t.ctx.ReportProgress(100, fmt.Sprintf("Identity check passed: %d/%d clients", passResultCount, totalClientCount)) default: if t.config.FailOnCheckMiss { t.logger.Infof("Setting result to FAILURE (requirements not met and failOnCheckMiss=true)") t.ctx.SetResult(types.TaskResultFailure) + t.ctx.ReportProgress(0, fmt.Sprintf("Identity check failed: %d/%d (attempt %d)", passResultCount, requiredPassCount, checkCount)) } else { t.logger.Infof("Setting result to PENDING (requirements not met but failOnCheckMiss=false)") t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for identity check... %d/%d (attempt %d)", passResultCount, requiredPassCount, checkCount)) } } } diff --git a/pkg/tasks/check_consensus_proposer_duty/task.go b/pkg/tasks/check_consensus_proposer_duty/task.go index ce309271..cfb0ee2b 100644 --- a/pkg/tasks/check_consensus_proposer_duty/task.go +++ b/pkg/tasks/check_consensus_proposer_duty/task.go @@ -17,7 +17,9 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Check consensus chain proposer duties.", + Category: "consensus", Config: DefaultConfig(), + Outputs: []types.TaskOutputDefinition{}, NewTask: NewTask, } ) @@ -89,28 +91,38 @@ func (t *Task) Execute(ctx context.Context) error { // load current epoch duties t.loadEpochDuties(ctx, currentEpoch.Number()) + checkCount := 0 + for { select { case currentEpoch := <-wallclockEpochSubscription.Channel(): t.loadEpochDuties(ctx, currentEpoch.Number()) case currentSlot := <-wallclockSlotSubscription.Channel(): - checkResult := t.runProposerDutyCheck(currentSlot.Number()) - - switch { - case checkResult: - t.ctx.SetResult(types.TaskResultSuccess) - case t.config.FailOnCheckMiss: - t.ctx.SetResult(types.TaskResultFailure) - default: - t.ctx.SetResult(types.TaskResultNone) - } + checkCount++ + t.processCheck(currentSlot.Number(), checkCount) case <-ctx.Done(): return ctx.Err() } } } +func (t *Task) processCheck(slot uint64, checkCount int) { + checkResult := t.runProposerDutyCheck(slot) + + switch { + case checkResult: + t.ctx.SetResult(types.TaskResultSuccess) + t.ctx.ReportProgress(100, fmt.Sprintf("Proposer duty check passed at slot %d", slot)) + case t.config.FailOnCheckMiss: + t.ctx.SetResult(types.TaskResultFailure) + t.ctx.ReportProgress(0, fmt.Sprintf("Proposer duty check failed at slot %d", slot)) + default: + t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for proposer duty... (attempt %d)", checkCount)) + } +} + func (t *Task) loadEpochDuties(ctx context.Context, epoch uint64) { client := t.ctx.Scheduler.GetServices().ClientPool().GetConsensusPool().AwaitReadyEndpoint(ctx, consensus.AnyClient) if client == nil { diff --git a/pkg/tasks/check_consensus_reorgs/task.go b/pkg/tasks/check_consensus_reorgs/task.go index 3cf3b71a..ab0b8f56 100644 --- a/pkg/tasks/check_consensus_reorgs/task.go +++ b/pkg/tasks/check_consensus_reorgs/task.go @@ -16,7 +16,9 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Check for consensus layer reorgs.", + Category: "consensus", Config: DefaultConfig(), + Outputs: []types.TaskOutputDefinition{}, NewTask: NewTask, } ) @@ -88,6 +90,8 @@ func (t *Task) Execute(ctx context.Context) error { var lastBlock *consensus.Block + checkCount := 0 + for { select { case block := <-blockSubscription.Channel(): @@ -102,13 +106,15 @@ func (t *Task) Execute(ctx context.Context) error { if t.config.MaxReorgDistance > 0 && t.maxReorgDistance > t.config.MaxReorgDistance { t.ctx.SetResult(types.TaskResultFailure) + t.ctx.ReportProgress(0, fmt.Sprintf("Max reorg distance exceeded: %d", t.maxReorgDistance)) t.logger.Infof("task failed: max reorg distance (%v) exceeded, reorg distance around slot %v: %v", t.config.MaxReorgDistance, block.Slot, t.maxReorgDistance) return fmt.Errorf("max reorg distance (%v) exceeded: %v -> %v (%v)", t.config.MaxReorgDistance, lastBlock.Root.String(), block.Root.String(), t.maxReorgDistance) } } - t.ctx.SetResult(t.runCheck()) + checkCount++ + t.processCheck(checkCount) lastBlock = block case <-ctx.Done(): @@ -117,6 +123,20 @@ func (t *Task) Execute(ctx context.Context) error { } } +func (t *Task) processCheck(checkCount int) { + result := t.runCheck() + t.ctx.SetResult(result) + + switch result { + case types.TaskResultSuccess: + t.ctx.ReportProgress(100, fmt.Sprintf("Reorg check passed (reorgs: %d, max distance: %d)", t.totalReorgs, t.maxReorgDistance)) + case types.TaskResultFailure: + t.ctx.ReportProgress(0, fmt.Sprintf("Reorg check failed (reorgs: %d, max distance: %d)", t.totalReorgs, t.maxReorgDistance)) + case types.TaskResultNone: + t.ctx.ReportProgress(0, fmt.Sprintf("Checking for reorgs... (attempt %d)", checkCount)) + } +} + func (t *Task) runCheck() types.TaskResult { consensusPool := t.ctx.Scheduler.GetServices().ClientPool().GetConsensusPool() diff --git a/pkg/tasks/check_consensus_slot_range/task.go b/pkg/tasks/check_consensus_slot_range/task.go index f5c6fc30..f204552b 100644 --- a/pkg/tasks/check_consensus_slot_range/task.go +++ b/pkg/tasks/check_consensus_slot_range/task.go @@ -14,8 +14,26 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Check if consensus wallclock is in a specific range.", + Category: "consensus", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "genesisTime", + Type: "int64", + Description: "The genesis timestamp in Unix seconds.", + }, + { + Name: "currentSlot", + Type: "uint64", + Description: "The current wallclock slot number.", + }, + { + Name: "currentEpoch", + Type: "uint64", + Description: "The current wallclock epoch number.", + }, + }, + NewTask: NewTask, } ) @@ -76,17 +94,11 @@ func (t *Task) Execute(ctx context.Context) error { wallclockSubscription := consensusPool.GetBlockCache().SubscribeWallclockSlotEvent(10) defer wallclockSubscription.Unsubscribe() + checkCount := 0 + for { - checkResult, isLower := t.runRangeCheck() - - switch { - case checkResult: - t.ctx.SetResult(types.TaskResultSuccess) - case !isLower || t.config.FailIfLower: - t.ctx.SetResult(types.TaskResultFailure) - default: - t.ctx.SetResult(types.TaskResultNone) - } + checkCount++ + t.processCheck(checkCount) select { case slot := <-wallclockSubscription.Channel(): @@ -97,6 +109,22 @@ func (t *Task) Execute(ctx context.Context) error { } } +func (t *Task) processCheck(checkCount int) { + checkResult, isLower := t.runRangeCheck() + + switch { + case checkResult: + t.ctx.SetResult(types.TaskResultSuccess) + t.ctx.ReportProgress(100, "Slot range check passed") + case !isLower || t.config.FailIfLower: + t.ctx.SetResult(types.TaskResultFailure) + t.ctx.ReportProgress(0, "Slot range check failed") + default: + t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for slot range... (attempt %d)", checkCount)) + } +} + func (t *Task) runRangeCheck() (checkResult, isLower bool) { consensusPool := t.ctx.Scheduler.GetServices().ClientPool().GetConsensusPool() genesis := consensusPool.GetBlockCache().GetGenesis() diff --git a/pkg/tasks/check_consensus_sync_status/task.go b/pkg/tasks/check_consensus_sync_status/task.go index b8676edb..1996606b 100644 --- a/pkg/tasks/check_consensus_sync_status/task.go +++ b/pkg/tasks/check_consensus_sync_status/task.go @@ -17,8 +17,21 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Checks consensus clients for their sync status.", + Category: "consensus", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "goodClients", + Type: "array", + Description: "Array of clients that meet sync criteria.", + }, + { + Name: "failedClients", + Type: "array", + Description: "Array of clients that do not meet sync criteria.", + }, + }, + NewTask: NewTask, } ) @@ -82,19 +95,21 @@ func (t *Task) LoadConfig() error { } func (t *Task) Execute(ctx context.Context) error { - t.processCheck(ctx) + checkCount := 0 for { + checkCount++ + t.processCheck(ctx, checkCount) + select { case <-time.After(t.config.PollInterval.Duration): - t.processCheck(ctx) case <-ctx.Done(): return nil } } } -func (t *Task) processCheck(ctx context.Context) { +func (t *Task) processCheck(ctx context.Context, checkCount int) { allResultsPass := true goodClients := []*ClientInfo{} failedClients := []*ClientInfo{} @@ -144,8 +159,10 @@ func (t *Task) processCheck(ctx context.Context) { if allResultsPass { t.ctx.SetResult(types.TaskResultSuccess) + t.ctx.ReportProgress(100, fmt.Sprintf("All clients synced: %d", len(goodClients))) } else { t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for sync... %d/%d (attempt %d)", len(goodClients), len(goodClients)+len(failedClients), checkCount)) } } diff --git a/pkg/tasks/check_consensus_validator_status/task.go b/pkg/tasks/check_consensus_validator_status/task.go index cb0c63f4..61a16e86 100644 --- a/pkg/tasks/check_consensus_validator_status/task.go +++ b/pkg/tasks/check_consensus_validator_status/task.go @@ -20,8 +20,21 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Check validator status on consensus chain.", + Category: "consensus", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "validator", + Type: "object", + Description: "The validator information object.", + }, + { + Name: "pubkey", + Type: "string", + Description: "The validator's public key.", + }, + }, + NewTask: NewTask, } ) @@ -80,20 +93,24 @@ func (t *Task) Execute(ctx context.Context) error { wallclockEpochSubscription := consensusPool.GetBlockCache().SubscribeWallclockEpochEvent(10) defer wallclockEpochSubscription.Unsubscribe() + checkCount := 0 + // load current epoch duties - t.runCheck() + checkCount++ + t.processCheck(checkCount) for { select { case <-wallclockEpochSubscription.Channel(): - t.runCheck() + checkCount++ + t.processCheck(checkCount) case <-ctx.Done(): return ctx.Err() } } } -func (t *Task) runCheck() { +func (t *Task) processCheck(checkCount int) { checkResult := t.runValidatorStatusCheck() _, epoch, _ := t.ctx.Scheduler.GetServices().ClientPool().GetConsensusPool().GetBlockCache().GetWallclock().Now() @@ -102,10 +119,13 @@ func (t *Task) runCheck() { switch { case checkResult: t.ctx.SetResult(types.TaskResultSuccess) + t.ctx.ReportProgress(100, fmt.Sprintf("Validator status check passed at epoch %d", epoch.Number())) case t.config.FailOnCheckMiss: t.ctx.SetResult(types.TaskResultFailure) + t.ctx.ReportProgress(0, fmt.Sprintf("Validator status check failed at epoch %d", epoch.Number())) default: t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for validator status... (attempt %d)", checkCount)) } } diff --git a/pkg/tasks/check_eth_call/task.go b/pkg/tasks/check_eth_call/task.go index c6441f73..a5a1d71e 100644 --- a/pkg/tasks/check_eth_call/task.go +++ b/pkg/tasks/check_eth_call/task.go @@ -20,8 +20,16 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Checks the response of an eth_call transaction", + Category: "execution", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "callResult", + Type: "string", + Description: "The result of the eth_call as a hex string.", + }, + }, + NewTask: NewTask, } ) @@ -96,13 +104,18 @@ func (t *Task) Execute(ctx context.Context) error { } } + checkCount := 0 + // Run the check with the latest block if latestBlock != nil { if t.config.BlockNumber == 0 { - t.runCheck(ctx, latestBlock.Number, latestBlock) + checkCount++ + t.runCheck(ctx, latestBlock.Number, latestBlock, checkCount) } else if latestBlock.Number >= t.config.BlockNumber { // if the block we're looking for already passed, run the check immediately and return - t.runCheck(ctx, t.config.BlockNumber, nil) + checkCount++ + t.runCheck(ctx, t.config.BlockNumber, nil, checkCount) + return nil } } @@ -112,9 +125,11 @@ func (t *Task) Execute(ctx context.Context) error { for { select { case block := <-blockSubscription.Channel(): + checkCount++ + if t.config.BlockNumber == 0 { // Run the check for all blocks - t.runCheck(ctx, block.Number, block) + t.runCheck(ctx, block.Number, block, checkCount) } else if block.Number >= t.config.BlockNumber { // Run the check once for the block we're looking for if block.Number != t.config.BlockNumber { @@ -122,7 +137,7 @@ func (t *Task) Execute(ctx context.Context) error { block = nil } - t.runCheck(ctx, t.config.BlockNumber, block) + t.runCheck(ctx, t.config.BlockNumber, block, checkCount) return nil } @@ -132,7 +147,7 @@ func (t *Task) Execute(ctx context.Context) error { } } -func (t *Task) runCheck(ctx context.Context, blockNumber uint64, block *execution.Block) { +func (t *Task) runCheck(ctx context.Context, blockNumber uint64, block *execution.Block, checkCount int) { // Set up the call message address := common.HexToAddress(t.config.CallAddress) callMsg := ðereum.CallMsg{ @@ -217,6 +232,7 @@ func (t *Task) runCheck(ctx context.Context, blockNumber uint64, block *executio t.ctx.SetResult(types.TaskResultFailure) } else { t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("result mismatch (attempt %d)", checkCount)) } return @@ -236,6 +252,7 @@ func (t *Task) runCheck(ctx context.Context, blockNumber uint64, block *executio t.ctx.SetResult(types.TaskResultFailure) } else { t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("expected result mismatch (attempt %d)", checkCount)) } return @@ -249,6 +266,7 @@ func (t *Task) runCheck(ctx context.Context, blockNumber uint64, block *executio if checkedClients > 0 { t.ctx.SetResult(types.TaskResultSuccess) + t.ctx.ReportProgress(100, fmt.Sprintf("eth_call successful on %d clients", checkedClients)) } } diff --git a/pkg/tasks/check_eth_config/task.go b/pkg/tasks/check_eth_config/task.go index 697c643b..bff4325e 100644 --- a/pkg/tasks/check_eth_config/task.go +++ b/pkg/tasks/check_eth_config/task.go @@ -17,8 +17,16 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Checks that all execution clients return the same eth_config (EIP-7910)", + Category: "execution", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "ethConfig", + Type: "string", + Description: "The eth_config JSON returned by clients.", + }, + }, + NewTask: NewTask, } ) @@ -230,6 +238,7 @@ func (t *Task) Execute(ctx context.Context) error { t.ctx.SetResult(types.TaskResultFailure) } else { t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, "eth_config mismatch detected") } return nil @@ -240,6 +249,7 @@ func (t *Task) Execute(ctx context.Context) error { t.logger.Infof("all %d clients returned consistent eth_config", len(results)) t.logger.Debugf("consistent eth_config:\n%s", referenceConfig) t.ctx.SetResult(types.TaskResultSuccess) + t.ctx.ReportProgress(100, fmt.Sprintf("eth_config consistent on %d clients", len(results))) } return nil diff --git a/pkg/tasks/check_execution_sync_status/task.go b/pkg/tasks/check_execution_sync_status/task.go index e11ac8be..5f305e85 100644 --- a/pkg/tasks/check_execution_sync_status/task.go +++ b/pkg/tasks/check_execution_sync_status/task.go @@ -17,8 +17,21 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Checks execution clients for their sync status.", + Category: "execution", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "goodClients", + Type: "array", + Description: "Array of clients that meet sync criteria.", + }, + { + Name: "failedClients", + Type: "array", + Description: "Array of clients that do not meet sync criteria.", + }, + }, + NewTask: NewTask, } ) @@ -81,19 +94,21 @@ func (t *Task) LoadConfig() error { } func (t *Task) Execute(ctx context.Context) error { - t.processCheck(ctx) + checkCount := 0 for { + checkCount++ + t.processCheck(ctx, checkCount) + select { case <-time.After(t.config.PollInterval.Duration): - t.processCheck(ctx) case <-ctx.Done(): return nil } } } -func (t *Task) processCheck(ctx context.Context) { +func (t *Task) processCheck(ctx context.Context, checkCount int) { allResultsPass := true goodClients := []*ClientInfo{} failedClients := []*ClientInfo{} @@ -143,8 +158,10 @@ func (t *Task) processCheck(ctx context.Context) { if allResultsPass { t.ctx.SetResult(types.TaskResultSuccess) + t.ctx.ReportProgress(100, fmt.Sprintf("All clients synced: %d", len(goodClients))) } else { t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for sync... %d/%d (attempt %d)", len(goodClients), len(goodClients)+len(failedClients), checkCount)) } } diff --git a/pkg/tasks/generate_attestations/task.go b/pkg/tasks/generate_attestations/task.go index e80c6ddc..ba34968d 100644 --- a/pkg/tasks/generate_attestations/task.go +++ b/pkg/tasks/generate_attestations/task.go @@ -28,7 +28,9 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Generates valid attestations and sends them to the network", + Category: "validator", Config: DefaultConfig(), + Outputs: []types.TaskOutputDefinition{}, NewTask: NewTask, } ) @@ -140,6 +142,8 @@ func (t *Task) Execute(ctx context.Context) error { totalAttestations := 0 processedEpochs := 0 + t.ctx.ReportProgress(0, "Generating attestations...") + for { select { case <-ctx.Done(): @@ -163,11 +167,25 @@ func (t *Task) Execute(ctx context.Context) error { t.ctx.SetResult(types.TaskResultSuccess) t.logger.Infof("sent %d attestations for slot %d (total: %d)", count, slot.Number(), totalAttestations) + + // Report progress based on limits + switch { + case t.config.LimitTotal > 0: + progress := float64(totalAttestations) / float64(t.config.LimitTotal) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Generated %d/%d attestations", totalAttestations, t.config.LimitTotal)) + case t.config.LimitEpochs > 0: + progress := float64(processedEpochs) / float64(t.config.LimitEpochs) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Generated %d attestations (%d/%d epochs)", totalAttestations, processedEpochs, t.config.LimitEpochs)) + default: + t.ctx.ReportProgress(0, fmt.Sprintf("Generated %d attestations", totalAttestations)) + } } // Check limits if t.config.LimitTotal > 0 && totalAttestations >= t.config.LimitTotal { t.logger.Infof("reached total attestation limit: %d", totalAttestations) + t.ctx.ReportProgress(100, fmt.Sprintf("Completed: generated %d attestations", totalAttestations)) + return nil } @@ -214,9 +232,23 @@ func (t *Task) Execute(ctx context.Context) error { processedEpochs++ t.logger.Infof("completed epoch %d, processed epochs: %d", epochNum-1, processedEpochs) + // Report progress based on limits + switch { + case t.config.LimitTotal > 0: + progress := float64(totalAttestations) / float64(t.config.LimitTotal) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Generated %d/%d attestations", totalAttestations, t.config.LimitTotal)) + case t.config.LimitEpochs > 0: + progress := float64(processedEpochs) / float64(t.config.LimitEpochs) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Generated %d attestations (%d/%d epochs)", totalAttestations, processedEpochs, t.config.LimitEpochs)) + default: + t.ctx.ReportProgress(0, fmt.Sprintf("Generated %d attestations (%d epochs)", totalAttestations, processedEpochs)) + } + // Check epoch limit if t.config.LimitEpochs > 0 && processedEpochs >= t.config.LimitEpochs { t.logger.Infof("reached epoch limit: %d", processedEpochs) + t.ctx.ReportProgress(100, fmt.Sprintf("Completed: generated %d attestations", totalAttestations)) + return nil } } diff --git a/pkg/tasks/generate_blob_transactions/task.go b/pkg/tasks/generate_blob_transactions/task.go index 3892e671..956a7e71 100644 --- a/pkg/tasks/generate_blob_transactions/task.go +++ b/pkg/tasks/generate_blob_transactions/task.go @@ -24,7 +24,9 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Generates blob transactions and sends them to the network", + Category: "transaction", Config: DefaultConfig(), + Outputs: []types.TaskOutputDefinition{}, NewTask: NewTask, } ) @@ -159,6 +161,8 @@ func (t *Task) Execute(ctx context.Context) error { perBlockCount := 0 totalCount := 0 + t.ctx.ReportProgress(0, "Generating blob transactions...") + for { txIndex := t.txIndex t.txIndex++ @@ -194,9 +198,18 @@ func (t *Task) Execute(ctx context.Context) error { } else { perBlockCount++ totalCount++ + + if t.config.LimitTotal > 0 { + progress := float64(totalCount) / float64(t.config.LimitTotal) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Generated %d/%d blob transactions", totalCount, t.config.LimitTotal)) + } else { + t.ctx.ReportProgress(0, fmt.Sprintf("Generated %d blob transactions", totalCount)) + } } if t.config.LimitTotal > 0 && totalCount >= t.config.LimitTotal { + t.ctx.ReportProgress(100, fmt.Sprintf("Completed: generated %d blob transactions", totalCount)) + break } diff --git a/pkg/tasks/generate_bls_changes/task.go b/pkg/tasks/generate_bls_changes/task.go index 260a7e18..601ba846 100644 --- a/pkg/tasks/generate_bls_changes/task.go +++ b/pkg/tasks/generate_bls_changes/task.go @@ -27,8 +27,21 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Generates bls changes and sends them to the network", + Category: "validator", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "blsChanges", + Type: "array", + Description: "Array of generated BLS change operations.", + }, + { + Name: "latestBlsChange", + Type: "object", + Description: "The most recently generated BLS change operation.", + }, + }, + NewTask: NewTask, } ) @@ -114,6 +127,16 @@ func (t *Task) Execute(ctx context.Context) error { totalCount := 0 blsChangesList := []interface{}{} + // Calculate target count for progress reporting + targetCount := 0 + if t.config.LimitTotal > 0 { + targetCount = t.config.LimitTotal + } else if t.lastIndex > 0 { + targetCount = int(t.lastIndex - t.nextIndex) //nolint:gosec // no overflow possible + } + + t.ctx.ReportProgress(0, "Starting BLS change generation") + for { accountIdx := t.nextIndex t.nextIndex++ @@ -128,6 +151,14 @@ func (t *Task) Execute(ctx context.Context) error { perSlotCount++ totalCount++ + + // Report progress + if targetCount > 0 { + progress := float64(totalCount) / float64(targetCount) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Generated %d/%d BLS changes", totalCount, targetCount)) + } else { + t.ctx.ReportProgress(0, fmt.Sprintf("Generated %d BLS changes", totalCount)) + } } if t.lastIndex > 0 && t.nextIndex >= t.lastIndex { @@ -154,6 +185,8 @@ func (t *Task) Execute(ctx context.Context) error { t.ctx.Outputs.SetVar("blsChanges", blsChangesList) + t.ctx.ReportProgress(100, fmt.Sprintf("Completed generating %d BLS changes", totalCount)) + return nil } diff --git a/pkg/tasks/generate_child_wallet/task.go b/pkg/tasks/generate_child_wallet/task.go index 2259c33f..96833223 100644 --- a/pkg/tasks/generate_child_wallet/task.go +++ b/pkg/tasks/generate_child_wallet/task.go @@ -18,8 +18,16 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Generates a funded child wallet.", + Category: "wallet", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "childWallet", + Type: "object", + Description: "Summary of the generated child wallet including address and balance.", + }, + }, + NewTask: NewTask, } ) @@ -85,12 +93,15 @@ func (t *Task) LoadConfig() error { } func (t *Task) Execute(ctx context.Context) error { + t.ctx.ReportProgress(0, "Waiting for root wallet...") + err := t.wallet.AwaitReady(ctx) if err != nil { return err } t.logger.Infof("root wallet: %v [nonce: %v] %v ETH", t.wallet.GetAddress().Hex(), t.wallet.GetNonce(), t.wallet.GetReadableBalance(18, 0, 4, false, false)) + t.ctx.ReportProgress(25, "Generating child wallet...") walletSeed := t.config.WalletSeed if t.config.RandomSeed { @@ -102,6 +113,8 @@ func (t *Task) Execute(ctx context.Context) error { return err } + t.ctx.ReportProgress(50, "Ensuring wallet funding...") + err = walletPool.EnsureFunding(ctx, t.config.PrefundMinBalance, t.config.PrefundAmount, t.config.PrefundFeeCap, t.config.PrefundTipCap, 1) if err != nil { return err @@ -125,6 +138,8 @@ func (t *Task) Execute(ctx context.Context) error { t.ctx.Vars.SetVar(t.config.WalletPrivateKeyResultVar, fmt.Sprintf("%x", crypto.FromECDSA(childWallet.GetPrivateKey()))) } + t.ctx.ReportProgress(100, "Child wallet generated") + return ctx.Err() } diff --git a/pkg/tasks/generate_consolidations/task.go b/pkg/tasks/generate_consolidations/task.go index 1b59e1b4..3ac18137 100644 --- a/pkg/tasks/generate_consolidations/task.go +++ b/pkg/tasks/generate_consolidations/task.go @@ -31,8 +31,21 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Generates consolidations and sends them to the network", + Category: "validator", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "transactionHashes", + Type: "array", + Description: "Array of consolidation transaction hashes.", + }, + { + Name: "transactionReceipts", + Type: "array", + Description: "Array of consolidation transaction receipts.", + }, + }, + NewTask: NewTask, } ) @@ -131,6 +144,8 @@ func (t *Task) Execute(ctx context.Context) error { perSlotCount := 0 totalCount := 0 + t.ctx.ReportProgress(0, "Generating consolidations...") + consolidationTransactions := []string{} consolidationReceipts := map[string]*ethtypes.Receipt{} receiptsMapMutex := sync.Mutex{} @@ -185,6 +200,19 @@ func (t *Task) Execute(ctx context.Context) error { totalCount++ consolidationTransactions = append(consolidationTransactions, tx.Hash().Hex()) + + // Report progress based on total limit or index count + switch { + case t.config.LimitTotal > 0: + progress := float64(totalCount) / float64(t.config.LimitTotal) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Generated %d/%d consolidations", totalCount, t.config.LimitTotal)) + case t.lastIndex > 0: + indexTotal := t.lastIndex - uint64(t.config.SourceStartIndex) //nolint:gosec // no overflow possible + progress := float64(totalCount) / float64(indexTotal) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Generated %d/%d consolidations", totalCount, indexTotal)) + default: + t.ctx.ReportProgress(0, fmt.Sprintf("Generated %d consolidations", totalCount)) + } } if t.lastIndex > 0 && t.nextIndex >= t.lastIndex { @@ -213,6 +241,8 @@ func (t *Task) Execute(ctx context.Context) error { pendingWg.Wait() } + t.ctx.ReportProgress(100, fmt.Sprintf("Completed: generated %d consolidations", totalCount)) + t.ctx.Outputs.SetVar("transactionHashes", consolidationTransactions) receiptList := []interface{}{} diff --git a/pkg/tasks/generate_deposits/task.go b/pkg/tasks/generate_deposits/task.go index ba223a4b..749e1d02 100644 --- a/pkg/tasks/generate_deposits/task.go +++ b/pkg/tasks/generate_deposits/task.go @@ -38,8 +38,26 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Generates deposits and sends them to the network", + Category: "validator", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "validatorPubkeys", + Type: "array", + Description: "Array of validator public keys for the deposits.", + }, + { + Name: "depositTransactions", + Type: "array", + Description: "Array of deposit transaction hashes.", + }, + { + Name: "depositReceipts", + Type: "array", + Description: "Array of deposit transaction receipts.", + }, + }, + NewTask: NewTask, } ) @@ -139,6 +157,16 @@ func (t *Task) Execute(ctx context.Context) error { perSlotCount := 0 totalCount := 0 + // Calculate target count for progress reporting + targetCount := 0 + if t.config.LimitTotal > 0 { + targetCount = t.config.LimitTotal + } else if t.lastIndex > 0 { + targetCount = int(t.lastIndex - t.nextIndex) //nolint:gosec // no overflow possible + } + + t.ctx.ReportProgress(0, "Starting deposit generation") + depositTransactions := []string{} validatorPubkeys := []string{} depositReceipts := map[string]*ethtypes.Receipt{} @@ -196,6 +224,14 @@ func (t *Task) Execute(ctx context.Context) error { validatorPubkeys = append(validatorPubkeys, pubkey.String()) depositTransactions = append(depositTransactions, tx.Hash().Hex()) + + // Report progress + if targetCount > 0 { + progress := float64(totalCount) / float64(targetCount) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Generated %d/%d deposits", totalCount, targetCount)) + } else { + t.ctx.ReportProgress(0, fmt.Sprintf("Generated %d deposits", totalCount)) + } } if t.lastIndex > 0 && t.nextIndex >= t.lastIndex { @@ -269,6 +305,8 @@ func (t *Task) Execute(ctx context.Context) error { t.ctx.Outputs.SetVar("depositReceipts", receiptList) + t.ctx.ReportProgress(100, fmt.Sprintf("Completed generating %d deposits", totalCount)) + if t.config.FailOnReject { for _, txhash := range depositTransactions { if depositReceipts[txhash] == nil { diff --git a/pkg/tasks/generate_eoa_transactions/task.go b/pkg/tasks/generate_eoa_transactions/task.go index 35d5730e..1c26de90 100644 --- a/pkg/tasks/generate_eoa_transactions/task.go +++ b/pkg/tasks/generate_eoa_transactions/task.go @@ -24,7 +24,9 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Generates normal eoa transactions and sends them to the network", + Category: "transaction", Config: DefaultConfig(), + Outputs: []types.TaskOutputDefinition{}, NewTask: NewTask, } ) @@ -165,6 +167,8 @@ func (t *Task) Execute(ctx context.Context) error { revertCount := 0 unknownCount := 0 + t.ctx.ReportProgress(0, "Generating EOA transactions...") + for { txIndex := t.txIndex t.txIndex++ @@ -216,9 +220,18 @@ func (t *Task) Execute(ctx context.Context) error { } else { perBlockCount++ totalCount++ + + if t.config.LimitTotal > 0 { + progress := float64(totalCount) / float64(t.config.LimitTotal) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Generated %d/%d EOA transactions", totalCount, t.config.LimitTotal)) + } else { + t.ctx.ReportProgress(0, fmt.Sprintf("Generated %d EOA transactions", totalCount)) + } } if t.config.LimitTotal > 0 && totalCount >= t.config.LimitTotal { + t.ctx.ReportProgress(100, fmt.Sprintf("Completed: generated %d EOA transactions", totalCount)) + break } diff --git a/pkg/tasks/generate_exits/task.go b/pkg/tasks/generate_exits/task.go index 331716f0..1dbd12ac 100644 --- a/pkg/tasks/generate_exits/task.go +++ b/pkg/tasks/generate_exits/task.go @@ -25,7 +25,9 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Generates voluntary exits and sends them to the network", + Category: "validator", Config: DefaultConfig(), + Outputs: []types.TaskOutputDefinition{}, NewTask: NewTask, } ) @@ -110,6 +112,16 @@ func (t *Task) Execute(ctx context.Context) error { perSlotCount := 0 totalCount := 0 + // Calculate target count for progress reporting + targetCount := 0 + if t.config.LimitTotal > 0 { + targetCount = t.config.LimitTotal + } else if t.lastIndex > 0 { + targetCount = int(t.lastIndex - t.nextIndex) //nolint:gosec // no overflow possible + } + + t.ctx.ReportProgress(0, "Starting voluntary exit generation") + for { accountIdx := t.nextIndex t.nextIndex++ @@ -122,6 +134,14 @@ func (t *Task) Execute(ctx context.Context) error { perSlotCount++ totalCount++ + + // Report progress + if targetCount > 0 { + progress := float64(totalCount) / float64(targetCount) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Generated %d/%d voluntary exits", totalCount, targetCount)) + } else { + t.ctx.ReportProgress(0, fmt.Sprintf("Generated %d voluntary exits", totalCount)) + } } if t.lastIndex > 0 && t.nextIndex >= t.lastIndex { @@ -150,6 +170,8 @@ func (t *Task) Execute(ctx context.Context) error { t.ctx.SetResult(types.TaskResultFailure) } + t.ctx.ReportProgress(100, fmt.Sprintf("Completed generating %d voluntary exits", totalCount)) + return nil } diff --git a/pkg/tasks/generate_slashings/task.go b/pkg/tasks/generate_slashings/task.go index c873f1c1..85ae85d9 100644 --- a/pkg/tasks/generate_slashings/task.go +++ b/pkg/tasks/generate_slashings/task.go @@ -27,7 +27,9 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Generates slashable attestations / proposals and sends them to the network", + Category: "validator", Config: DefaultConfig(), + Outputs: []types.TaskOutputDefinition{}, NewTask: NewTask, } ) @@ -112,6 +114,8 @@ func (t *Task) Execute(ctx context.Context) error { perSlotCount := 0 totalCount := 0 + t.ctx.ReportProgress(0, "Generating slashings...") + for { accountIdx := t.nextIndex t.nextIndex++ @@ -124,6 +128,19 @@ func (t *Task) Execute(ctx context.Context) error { perSlotCount++ totalCount++ + + // Report progress based on total limit or index count + switch { + case t.config.LimitTotal > 0: + progress := float64(totalCount) / float64(t.config.LimitTotal) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Generated %d/%d slashings", totalCount, t.config.LimitTotal)) + case t.lastIndex > 0: + indexTotal := t.lastIndex - uint64(t.config.StartIndex) //nolint:gosec // no overflow possible + progress := float64(totalCount) / float64(indexTotal) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Generated %d/%d slashings", totalCount, indexTotal)) + default: + t.ctx.ReportProgress(0, fmt.Sprintf("Generated %d slashings", totalCount)) + } } if t.lastIndex > 0 && t.nextIndex >= t.lastIndex { @@ -148,6 +165,8 @@ func (t *Task) Execute(ctx context.Context) error { } } + t.ctx.ReportProgress(100, fmt.Sprintf("Completed: generated %d slashings", totalCount)) + return nil } diff --git a/pkg/tasks/generate_transaction/task.go b/pkg/tasks/generate_transaction/task.go index cbebe4b0..1f4c73cd 100644 --- a/pkg/tasks/generate_transaction/task.go +++ b/pkg/tasks/generate_transaction/task.go @@ -28,8 +28,36 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Generates normal transaction, sends it to the network and checks the receipt", + Category: "transaction", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "transaction", + Type: "object", + Description: "The generated transaction object.", + }, + { + Name: "transactionHex", + Type: "string", + Description: "The transaction encoded as hex.", + }, + { + Name: "transactionHash", + Type: "string", + Description: "The transaction hash.", + }, + { + Name: "contractAddress", + Type: "string", + Description: "The deployed contract address (if contract deployment).", + }, + { + Name: "receipt", + Type: "object", + Description: "The transaction receipt (if awaitReceipt is enabled).", + }, + }, + NewTask: NewTask, } ) @@ -132,6 +160,8 @@ func (t *Task) LoadConfig() error { //nolint:gocyclo // ignore func (t *Task) Execute(ctx context.Context) error { + t.ctx.ReportProgress(0, "Preparing wallet...") + err := t.wallet.AwaitReady(ctx) if err != nil { return fmt.Errorf("cannot load wallet state: %w", err) @@ -148,6 +178,8 @@ func (t *Task) Execute(ctx context.Context) error { t.logger.Infof("wallet: %v [nonce: %v] %v ETH", t.wallet.GetAddress().Hex(), t.wallet.GetNonce(), t.wallet.GetReadableBalance(18, 0, 4, false, false)) + t.ctx.ReportProgress(0, "Generating transaction...") + tx, err := t.generateTransaction(ctx) if err != nil { return err @@ -287,6 +319,8 @@ func (t *Task) Execute(ctx context.Context) error { authorizationWallet.ResyncState() } + t.ctx.ReportProgress(100, fmt.Sprintf("Transaction completed: %s", tx.Hash().Hex())) + return nil } diff --git a/pkg/tasks/generate_withdrawal_requests/task.go b/pkg/tasks/generate_withdrawal_requests/task.go index f555369e..d8a962b2 100644 --- a/pkg/tasks/generate_withdrawal_requests/task.go +++ b/pkg/tasks/generate_withdrawal_requests/task.go @@ -32,8 +32,21 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Generates withdrawal requests and sends them to the network", + Category: "validator", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "transactionHashes", + Type: "array", + Description: "Array of withdrawal transaction hashes.", + }, + { + Name: "transactionReceipts", + Type: "array", + Description: "Array of withdrawal transaction receipts.", + }, + }, + NewTask: NewTask, } ) @@ -126,6 +139,8 @@ func (t *Task) Execute(ctx context.Context) error { perSlotCount := 0 totalCount := 0 + t.ctx.ReportProgress(0, "Generating withdrawal requests...") + withdrawalTransactions := []string{} receiptsMapMutex := sync.Mutex{} withdrawalReceipts := map[string]*ethtypes.Receipt{} @@ -180,6 +195,14 @@ func (t *Task) Execute(ctx context.Context) error { totalCount++ withdrawalTransactions = append(withdrawalTransactions, tx.Hash().Hex()) + + // Report progress based on total limit + if t.config.LimitTotal > 0 { + progress := float64(totalCount) / float64(t.config.LimitTotal) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Generated %d/%d withdrawal requests", totalCount, t.config.LimitTotal)) + } else { + t.ctx.ReportProgress(0, fmt.Sprintf("Generated %d withdrawal requests", totalCount)) + } } if t.config.LimitTotal > 0 && totalCount >= t.config.LimitTotal { @@ -204,6 +227,8 @@ func (t *Task) Execute(ctx context.Context) error { pendingWg.Wait() } + t.ctx.ReportProgress(100, fmt.Sprintf("Completed: generated %d withdrawal requests", totalCount)) + t.ctx.Outputs.SetVar("transactionHashes", withdrawalTransactions) receiptList := []interface{}{} diff --git a/pkg/tasks/get_consensus_specs/task.go b/pkg/tasks/get_consensus_specs/task.go index e393c9d3..fafd65e8 100644 --- a/pkg/tasks/get_consensus_specs/task.go +++ b/pkg/tasks/get_consensus_specs/task.go @@ -14,8 +14,16 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Get consensus chain specs.", + Category: "consensus", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "specs", + Type: "object", + Description: "The consensus chain specs object.", + }, + }, + NewTask: NewTask, } ) @@ -87,5 +95,7 @@ func (t *Task) Execute(ctx context.Context) error { t.ctx.Outputs.SetVar("specs", specs) + t.ctx.ReportProgress(100, "Consensus specs retrieved") + return nil } diff --git a/pkg/tasks/get_consensus_validators/task.go b/pkg/tasks/get_consensus_validators/task.go index 78930d6b..285e2488 100644 --- a/pkg/tasks/get_consensus_validators/task.go +++ b/pkg/tasks/get_consensus_validators/task.go @@ -17,8 +17,31 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Retrieves validators from the consensus layer matching specified criteria.", + Category: "consensus", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "validators", + Type: "array", + Description: "Array of validator info objects (when outputFormat is 'full').", + }, + { + Name: "pubkeys", + Type: "array", + Description: "Array of validator public keys (when outputFormat is 'pubkeys').", + }, + { + Name: "indices", + Type: "array", + Description: "Array of validator indices (when outputFormat is 'indices').", + }, + { + Name: "count", + Type: "int", + Description: "Number of matching validators found.", + }, + }, + NewTask: NewTask, } ) @@ -249,5 +272,7 @@ func (t *Task) Execute(_ context.Context) error { t.ctx.SetResult(types.TaskResultNone) } + t.ctx.ReportProgress(100, fmt.Sprintf("Retrieved %d validators", len(matchingValidators))) + return nil } diff --git a/pkg/tasks/get_execution_block/task.go b/pkg/tasks/get_execution_block/task.go index 2840fa76..b133e569 100644 --- a/pkg/tasks/get_execution_block/task.go +++ b/pkg/tasks/get_execution_block/task.go @@ -17,8 +17,16 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Gets the latest execution block.", + Category: "execution", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "header", + Type: "object", + Description: "The execution block header.", + }, + }, + NewTask: NewTask, } ) @@ -93,5 +101,7 @@ func (t *Task) Execute(_ context.Context) error { t.ctx.Outputs.SetVar("header", headerData) } + t.ctx.ReportProgress(100, fmt.Sprintf("Execution block %v retrieved", block.Number())) + return nil } diff --git a/pkg/tasks/get_pubkeys_from_mnemonic/task.go b/pkg/tasks/get_pubkeys_from_mnemonic/task.go index f5a94b65..0520949a 100644 --- a/pkg/tasks/get_pubkeys_from_mnemonic/task.go +++ b/pkg/tasks/get_pubkeys_from_mnemonic/task.go @@ -18,8 +18,16 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Get public keys from mnemonic", + Category: "validator", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "pubkeys", + Type: "array", + Description: "Array of generated public keys.", + }, + }, + NewTask: NewTask, } ) @@ -99,5 +107,7 @@ func (t *Task) Execute(_ context.Context) error { t.ctx.Outputs.SetVar("pubkeys", pubkeys) + t.ctx.ReportProgress(100, fmt.Sprintf("Generated %d public keys", len(pubkeys))) + return nil } diff --git a/pkg/tasks/get_random_mnemonic/task.go b/pkg/tasks/get_random_mnemonic/task.go index 2b5e403a..7855a8b3 100644 --- a/pkg/tasks/get_random_mnemonic/task.go +++ b/pkg/tasks/get_random_mnemonic/task.go @@ -16,8 +16,16 @@ var ( Name: TaskName, Aliases: []string{"generate_random_mnemonic"}, Description: "Get random mnemonic.", + Category: "utility", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "mnemonic", + Type: "string", + Description: "The randomly generated mnemonic.", + }, + }, + NewTask: NewTask, } ) @@ -89,5 +97,7 @@ func (t *Task) Execute(_ context.Context) error { t.ctx.Outputs.SetVar("mnemonic", mnemonic) + t.ctx.ReportProgress(100, "Random mnemonic generated") + return nil } diff --git a/pkg/tasks/get_wallet_details/task.go b/pkg/tasks/get_wallet_details/task.go index f1422c97..e6d057fb 100644 --- a/pkg/tasks/get_wallet_details/task.go +++ b/pkg/tasks/get_wallet_details/task.go @@ -17,8 +17,31 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Get wallet details.", + Category: "wallet", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "address", + Type: "string", + Description: "The wallet address.", + }, + { + Name: "balance", + Type: "string", + Description: "The wallet balance in wei as a string.", + }, + { + Name: "nonce", + Type: "uint64", + Description: "The current nonce of the wallet.", + }, + { + Name: "summary", + Type: "object", + Description: "Summary object with wallet details.", + }, + }, + NewTask: NewTask, } ) @@ -99,5 +122,7 @@ func (t *Task) Execute(ctx context.Context) error { t.ctx.Outputs.SetVar("nonce", wal.GetNonce()) t.ctx.Outputs.SetVar("summary", wal.GetSummary()) + t.ctx.ReportProgress(100, "Wallet details retrieved") + return nil } diff --git a/pkg/tasks/run_command/task.go b/pkg/tasks/run_command/task.go index 8b7ad695..f21f8aa1 100644 --- a/pkg/tasks/run_command/task.go +++ b/pkg/tasks/run_command/task.go @@ -15,8 +15,21 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Runs a shell command.", + Category: "utility", Config: DefaultConfig(), - NewTask: NewTask, + Outputs: []types.TaskOutputDefinition{ + { + Name: "stdout", + Type: "string", + Description: "The combined stdout/stderr output from the command.", + }, + { + Name: "error", + Type: "string", + Description: "The error message if the command failed.", + }, + }, + NewTask: NewTask, } ) @@ -71,6 +84,7 @@ func (t *Task) LoadConfig() error { func (t *Task) Execute(ctx context.Context) error { if len(t.config.Command) == 0 { + t.ctx.ReportProgress(100, "No command to run") return nil } @@ -82,6 +96,7 @@ func (t *Task) Execute(ctx context.Context) error { } t.logger.WithField("cmd", com).WithField("args", args).WithField("allowed_to_failed", t.config.AllowedToFail).Info("running command") + t.ctx.ReportProgress(0, "Running command...") command := exec.CommandContext(ctx, com, args...) @@ -100,6 +115,7 @@ func (t *Task) Execute(ctx context.Context) error { } t.logger.WithField("stdout", string(stdOut)).Info("command run successfully") + t.ctx.ReportProgress(100, "Command completed") return nil } diff --git a/pkg/tasks/run_external_tasks/task.go b/pkg/tasks/run_external_tasks/task.go index 130b1cd0..187182be 100644 --- a/pkg/tasks/run_external_tasks/task.go +++ b/pkg/tasks/run_external_tasks/task.go @@ -22,7 +22,9 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Run external test playbook.", + Category: "flow-control", Config: DefaultConfig(), + Outputs: []types.TaskOutputDefinition{}, NewTask: NewTask, } ) @@ -77,6 +79,8 @@ func (t *Task) LoadConfig() error { } func (t *Task) Execute(ctx context.Context) error { + t.ctx.ReportProgress(0, "Loading external test configuration...") + // get task base path taskBasePath, ok := t.ctx.Vars.GetVar("taskBasePath").(string) if !ok { @@ -152,12 +156,17 @@ func (t *Task) Execute(ctx context.Context) error { // execute child tasks var resError error + totalTasks := len(tasks) + taskLoop: for i, task := range tasks { if ctx.Err() != nil { return ctx.Err() } + progress := float64(i) / float64(totalTasks) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Executing task %d/%d...", i+1, totalTasks)) + err := t.ctx.Scheduler.ExecuteTask(ctx, task, t.ctx.Scheduler.WatchTaskPass) switch { @@ -190,9 +199,13 @@ taskLoop: return fmt.Errorf("test should have failed, but succeeded") } + t.ctx.ReportProgress(100, "External tasks completed (expected failure)") + return nil } + t.ctx.ReportProgress(100, "External tasks completed") + return resError } diff --git a/pkg/tasks/run_shell/task.go b/pkg/tasks/run_shell/task.go index 27510001..2d85dc9d 100644 --- a/pkg/tasks/run_shell/task.go +++ b/pkg/tasks/run_shell/task.go @@ -24,7 +24,9 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Runs commands in a shell.", + Category: "utility", Config: DefaultConfig(), + Outputs: []types.TaskOutputDefinition{}, NewTask: NewTask, } ) @@ -81,6 +83,7 @@ func (t *Task) LoadConfig() error { func (t *Task) Execute(ctx context.Context) error { cmdLogger := t.logger.WithField("shell", t.config.Shell) cmdLogger.Info("running command") + t.ctx.ReportProgress(0, "Running shell command...") // create temp dir for task taskDir, err := os.MkdirTemp(os.TempDir(), fmt.Sprintf("assertoor_%v_%v_", t.ctx.Scheduler.GetTestRunID(), t.ctx.Index)) @@ -240,6 +243,7 @@ cmdloop: } cmdLogger.Info("command run successfully") + t.ctx.ReportProgress(100, "Shell command completed") return nil } diff --git a/pkg/tasks/run_task_background/task.go b/pkg/tasks/run_task_background/task.go index 243b6a80..aeb3d8eb 100644 --- a/pkg/tasks/run_task_background/task.go +++ b/pkg/tasks/run_task_background/task.go @@ -16,7 +16,9 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Runs foreground and background task with configurable dependencies.", + Category: "flow-control", Config: DefaultConfig(), + Outputs: []types.TaskOutputDefinition{}, NewTask: NewTask, } ) @@ -122,9 +124,13 @@ func (t *Task) Execute(ctx context.Context) error { childCtx, cancel := context.WithCancel(ctx) if t.backgroundTask != 0 { + t.ctx.ReportProgress(0, "Starting background task...") + go t.execBackgroundTask(childCtx) } + t.ctx.ReportProgress(0, "Running foreground task...") + go t.execForegroundTask(childCtx) result := <-t.resultChan @@ -137,6 +143,8 @@ func (t *Task) Execute(ctx context.Context) error { <-t.foregroundChan + t.ctx.ReportProgress(100, "Task completed") + return result.err } diff --git a/pkg/tasks/run_task_matrix/task.go b/pkg/tasks/run_task_matrix/task.go index 2449152f..04281a93 100644 --- a/pkg/tasks/run_task_matrix/task.go +++ b/pkg/tasks/run_task_matrix/task.go @@ -16,7 +16,9 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Run a task multiple times based on an input array.", + Category: "flow-control", Config: DefaultConfig(), + Outputs: []types.TaskOutputDefinition{}, NewTask: NewTask, } ) @@ -225,6 +227,12 @@ func (t *Task) Execute(ctx context.Context) error { if !taskComplete { t.logger.Debugf("result update (%v success, %v failure)", successCount, failureCount) + + // Report progress based on completed tasks + completedTasks := successCount + failureCount + totalTasks := uint64(len(t.tasks)) + progress := float64(completedTasks) / float64(totalTasks) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Task %d/%d completed", completedTasks, totalTasks)) } case <-completeChan: if !taskComplete { @@ -237,6 +245,9 @@ func (t *Task) Execute(ctx context.Context) error { } t.logger.Infof("all child tasks completed (%v success, %v failure)", successCount, failureCount) + + // Report 100% progress when all tasks complete + t.ctx.ReportProgress(100, fmt.Sprintf("All %d tasks completed", len(t.tasks))) } } } diff --git a/pkg/tasks/run_task_options/task.go b/pkg/tasks/run_task_options/task.go index 17eb1967..d3144c80 100644 --- a/pkg/tasks/run_task_options/task.go +++ b/pkg/tasks/run_task_options/task.go @@ -15,7 +15,9 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Runs task with configurable behaviour.", + Category: "flow-control", Config: DefaultConfig(), + Outputs: []types.TaskOutputDefinition{}, NewTask: NewTask, } ) @@ -75,6 +77,8 @@ func (t *Task) Execute(ctx context.Context) error { retryCount := uint(0) + t.ctx.ReportProgress(0, "Running child task...") + for { // init child task taskOpts, err := t.ctx.Scheduler.ParseTaskOptions(t.config.Task) @@ -105,6 +109,7 @@ func (t *Task) Execute(ctx context.Context) error { retryCount++ t.logger.Warnf("child task failed: %w (retrying)", taskErr) + t.ctx.ReportProgress(0, fmt.Sprintf("Retrying child task (attempt %d/%d)...", retryCount+1, t.config.MaxRetryCount+1)) continue } @@ -131,6 +136,8 @@ func (t *Task) Execute(ctx context.Context) error { break } + t.ctx.ReportProgress(100, "Task completed") + return taskErr } diff --git a/pkg/tasks/run_tasks/task.go b/pkg/tasks/run_tasks/task.go index 99dd30c9..9055f10d 100644 --- a/pkg/tasks/run_tasks/task.go +++ b/pkg/tasks/run_tasks/task.go @@ -14,7 +14,9 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Run tasks sequentially.", + Category: "flow-control", Config: DefaultConfig(), + Outputs: []types.TaskOutputDefinition{}, NewTask: NewTask, } ) @@ -96,6 +98,8 @@ func (t *Task) LoadConfig() error { } func (t *Task) Execute(ctx context.Context) error { + totalTasks := len(t.tasks) + for i, task := range t.tasks { err := t.ctx.Scheduler.ExecuteTask(ctx, task, func(ctx context.Context, cancelFn context.CancelFunc, task types.TaskIndex) { if t.config.StopChildOnResult { @@ -117,6 +121,11 @@ func (t *Task) Execute(ctx context.Context) error { return fmt.Errorf("child task #%v failed: %w", i+1, err) } } + + // Report progress after each task completes + completedTasks := i + 1 + progress := float64(completedTasks) / float64(totalTasks) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Task %d/%d completed", completedTasks, totalTasks)) } return nil diff --git a/pkg/tasks/run_tasks_concurrent/task.go b/pkg/tasks/run_tasks_concurrent/task.go index a296b41f..44b38d16 100644 --- a/pkg/tasks/run_tasks_concurrent/task.go +++ b/pkg/tasks/run_tasks_concurrent/task.go @@ -16,7 +16,9 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Runs multiple tasks in parallel.", + Category: "flow-control", Config: DefaultConfig(), + Outputs: []types.TaskOutputDefinition{}, NewTask: NewTask, } ) @@ -203,6 +205,12 @@ func (t *Task) Execute(ctx context.Context) error { if !taskComplete { t.logger.Debugf("result update (%v success, %v failure)", successCount, failureCount) + + // Report progress based on completed tasks + completedTasks := successCount + failureCount + totalTasks := uint64(len(t.tasks)) + progress := float64(completedTasks) / float64(totalTasks) * 100 + t.ctx.ReportProgress(progress, fmt.Sprintf("Task %d/%d completed", completedTasks, totalTasks)) } case <-completeChan: if !taskComplete { @@ -215,6 +223,9 @@ func (t *Task) Execute(ctx context.Context) error { } t.logger.Infof("all child tasks completed (%v success, %v failure)", successCount, failureCount) + + // Report 100% progress when all tasks complete + t.ctx.ReportProgress(100, fmt.Sprintf("All %d tasks completed", len(t.tasks))) } } } diff --git a/pkg/tasks/schema.go b/pkg/tasks/schema.go new file mode 100644 index 00000000..aa88b747 --- /dev/null +++ b/pkg/tasks/schema.go @@ -0,0 +1,185 @@ +package tasks + +import ( + "encoding/json" + "reflect" + "strings" + + "github.com/ethpandaops/assertoor/pkg/types" +) + +// TaskDescriptorAPI represents a task descriptor for API responses. +type TaskDescriptorAPI struct { + Name string `json:"name"` + Aliases []string `json:"aliases,omitempty"` + Description string `json:"description"` + Category string `json:"category,omitempty"` + ConfigSchema json.RawMessage `json:"configSchema"` + Outputs []types.TaskOutputDefinition `json:"outputs,omitempty"` +} + +// GetAllTaskDescriptorsAPI returns all task descriptors formatted for API responses. +func GetAllTaskDescriptorsAPI() []TaskDescriptorAPI { + descriptors := make([]TaskDescriptorAPI, 0, len(AvailableTaskDescriptors)) + + for _, desc := range AvailableTaskDescriptors { + apiDesc := TaskDescriptorAPI{ + Name: desc.Name, + Aliases: desc.Aliases, + Description: desc.Description, + Category: desc.Category, + Outputs: desc.Outputs, + } + + if desc.Config != nil { + schema, err := GenerateJSONSchema(desc.Config) + if err == nil { + apiDesc.ConfigSchema = schema + } + } + + descriptors = append(descriptors, apiDesc) + } + + return descriptors +} + +// GetTaskDescriptorAPI returns a single task descriptor formatted for API response. +func GetTaskDescriptorAPI(name string) *TaskDescriptorAPI { + desc := GetTaskDescriptor(name) + if desc == nil { + return nil + } + + apiDesc := &TaskDescriptorAPI{ + Name: desc.Name, + Aliases: desc.Aliases, + Description: desc.Description, + Category: desc.Category, + Outputs: desc.Outputs, + } + + if desc.Config != nil { + schema, err := GenerateJSONSchema(desc.Config) + if err == nil { + apiDesc.ConfigSchema = schema + } + } + + return apiDesc +} + +// JSONSchema represents a JSON Schema object. +type JSONSchema struct { + Type string `json:"type,omitempty"` + Description string `json:"description,omitempty"` + Properties map[string]*JSONSchema `json:"properties,omitempty"` + Required []string `json:"required,omitempty"` + Items *JSONSchema `json:"items,omitempty"` + Default any `json:"default,omitempty"` + Enum []any `json:"enum,omitempty"` + AdditionalProperties *JSONSchema `json:"additionalProperties,omitempty"` +} + +// GenerateJSONSchema generates a JSON Schema from a Go struct via reflection. +func GenerateJSONSchema(v any) (json.RawMessage, error) { + schema := generateSchemaFromType(reflect.TypeOf(v)) + + return json.Marshal(schema) +} + +func generateSchemaFromType(t reflect.Type) *JSONSchema { + // Handle pointer types + if t.Kind() == reflect.Pointer { + t = t.Elem() + } + + schema := &JSONSchema{} + + switch t.Kind() { + case reflect.Struct: + schema.Type = "object" + schema.Properties = make(map[string]*JSONSchema, t.NumField()) + + for i := range t.NumField() { + field := t.Field(i) + + // Skip unexported fields + if !field.IsExported() { + continue + } + + // Get JSON tag for field name + jsonTag := field.Tag.Get("json") + if jsonTag == "-" { + continue + } + + fieldName := getJSONFieldName(&field, jsonTag) + + // Generate schema for field type + fieldSchema := generateSchemaFromType(field.Type) + + // Add description from yaml tag or field name + if yamlTag := field.Tag.Get("yaml"); yamlTag != "" { + parts := strings.Split(yamlTag, ",") + if len(parts) > 0 && parts[0] != "" { + fieldSchema.Description = "Config field: " + parts[0] + } + } + + schema.Properties[fieldName] = fieldSchema + } + + case reflect.Slice, reflect.Array: + schema.Type = "array" + schema.Items = generateSchemaFromType(t.Elem()) + + case reflect.Map: + schema.Type = "object" + schema.AdditionalProperties = generateSchemaFromType(t.Elem()) + + case reflect.String: + schema.Type = "string" + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + schema.Type = "integer" + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + schema.Type = "integer" + + case reflect.Float32, reflect.Float64: + schema.Type = "number" + + case reflect.Bool: + schema.Type = "boolean" + + case reflect.Interface: + // For interface{}/any, allow any type + schema.Type = "" + + case reflect.Complex64, reflect.Complex128: + schema.Type = "string" + + case reflect.Chan, reflect.Func, reflect.UnsafePointer, reflect.Invalid: + // These types are not representable in JSON Schema + schema.Type = "" + + case reflect.Pointer: + // Already handled above, but included for exhaustiveness + return generateSchemaFromType(t.Elem()) + } + + return schema +} + +func getJSONFieldName(field *reflect.StructField, jsonTag string) string { + if jsonTag != "" { + parts := strings.Split(jsonTag, ",") + if parts[0] != "" { + return parts[0] + } + } + + return field.Name +} diff --git a/pkg/tasks/sleep/task.go b/pkg/tasks/sleep/task.go index b95f9494..b6cdce0f 100644 --- a/pkg/tasks/sleep/task.go +++ b/pkg/tasks/sleep/task.go @@ -14,7 +14,9 @@ var ( TaskDescriptor = &types.TaskDescriptor{ Name: TaskName, Description: "Sleeps for a specified duration.", + Category: "utility", Config: DefaultConfig(), + Outputs: []types.TaskOutputDefinition{}, NewTask: NewTask, } ) @@ -69,10 +71,40 @@ func (t *Task) LoadConfig() error { } func (t *Task) Execute(ctx context.Context) error { - select { - case <-time.After(t.config.Duration.Duration): + duration := t.config.Duration.Duration + if duration <= 0 { return nil - case <-ctx.Done(): - return ctx.Err() + } + + startTime := time.Now() + ticker := time.NewTicker(1 * time.Second) + + defer ticker.Stop() + + t.ctx.ReportProgress(0, fmt.Sprintf("Sleeping for %v", duration)) + + for { + select { + case <-ticker.C: + elapsed := time.Since(startTime) + progress := float64(elapsed) / float64(duration) * 100 + + if progress > 100 { + progress = 100 + } + + remaining := duration - elapsed + if remaining < 0 { + remaining = 0 + } + + t.ctx.ReportProgress(progress, fmt.Sprintf("Sleeping... %v remaining", remaining.Round(time.Second))) + case <-time.After(time.Until(startTime.Add(duration))): + t.ctx.ReportProgress(100, "Sleep completed") + + return nil + case <-ctx.Done(): + return ctx.Err() + } } } diff --git a/pkg/types/coordinator.go b/pkg/types/coordinator.go index 9660fd35..09873c80 100644 --- a/pkg/types/coordinator.go +++ b/pkg/types/coordinator.go @@ -5,6 +5,7 @@ import ( "github.com/ethpandaops/assertoor/pkg/clients" "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/events" "github.com/ethpandaops/assertoor/pkg/logger" "github.com/ethpandaops/assertoor/pkg/names" "github.com/ethpandaops/assertoor/pkg/wallet" @@ -20,6 +21,7 @@ type Coordinator interface { ValidatorNames() *names.ValidatorNames GlobalVariables() Variables TestRegistry() TestRegistry + EventBus() *events.EventBus GetTestByRunID(runID uint64) Test GetTestQueue() []Test diff --git a/pkg/types/scheduler.go b/pkg/types/scheduler.go index 6d43ee24..62f70bee 100644 --- a/pkg/types/scheduler.go +++ b/pkg/types/scheduler.go @@ -5,6 +5,7 @@ import ( "github.com/ethpandaops/assertoor/pkg/clients" "github.com/ethpandaops/assertoor/pkg/db" + "github.com/ethpandaops/assertoor/pkg/events" "github.com/ethpandaops/assertoor/pkg/helper" "github.com/ethpandaops/assertoor/pkg/names" "github.com/ethpandaops/assertoor/pkg/wallet" @@ -33,4 +34,5 @@ type TaskServices interface { ClientPool() *clients.ClientPool WalletManager() *wallet.Manager ValidatorNames() *names.ValidatorNames + EventBus() *events.EventBus } diff --git a/pkg/types/task.go b/pkg/types/task.go index 01fe65eb..06c30a0a 100644 --- a/pkg/types/task.go +++ b/pkg/types/task.go @@ -8,11 +8,20 @@ import ( "github.com/ethpandaops/assertoor/pkg/logger" ) +// TaskOutputDefinition describes an output that a task can produce. +type TaskOutputDefinition struct { + Name string `json:"name"` + Type string `json:"type"` + Description string `json:"description"` +} + type TaskDescriptor struct { Name string Aliases []string Description string - Config interface{} + Category string + Config any + Outputs []TaskOutputDefinition NewTask func(ctx *TaskContext, options *TaskOptions) (Task, error) } @@ -43,7 +52,7 @@ const ( ) type Task interface { - Config() interface{} + Config() any Timeout() time.Duration LoadConfig() error @@ -57,7 +66,7 @@ type TaskState interface { Name() string Title() string Description() string - Config() interface{} + Config() any Timeout() time.Duration GetTaskStatus() *TaskStatus GetTaskStatusVars() Variables @@ -66,24 +75,28 @@ type TaskState interface { } type TaskStatus struct { - Index TaskIndex - ParentIndex TaskIndex - IsStarted bool - IsRunning bool - IsSkipped bool - StartTime time.Time - StopTime time.Time - Result TaskResult - Error error - Logger logger.LogReader + Index TaskIndex + ParentIndex TaskIndex + IsStarted bool + IsRunning bool + IsSkipped bool + StartTime time.Time + StopTime time.Time + Result TaskResult + Error error + Logger logger.LogReader + Progress float64 + ProgressMessage string } type TaskContext struct { - Scheduler TaskSchedulerRunner - Index TaskIndex - Vars Variables - Outputs Variables - Logger *logger.LogScope - NewTask func(options *TaskOptions, variables Variables) (TaskIndex, error) - SetResult func(result TaskResult) + Scheduler TaskSchedulerRunner + Index TaskIndex + Vars Variables + Outputs Variables + Logger *logger.LogScope + NewTask func(options *TaskOptions, variables Variables) (TaskIndex, error) + SetResult func(result TaskResult) + ReportProgress func(percent float64, message string) + EmitEvent func(eventType string, data any) } diff --git a/pkg/web/api/get_task_descriptors_api.go b/pkg/web/api/get_task_descriptors_api.go new file mode 100644 index 00000000..bf3fdda1 --- /dev/null +++ b/pkg/web/api/get_task_descriptors_api.go @@ -0,0 +1,42 @@ +package api + +import ( + "net/http" + + "github.com/ethpandaops/assertoor/pkg/tasks" + "github.com/gorilla/mux" +) + +// GetTaskDescriptors godoc +// @Summary Get all task descriptors +// @Description Returns a list of all available task descriptors with their JSON schemas +// @Tags Task +// @Produce json +// @Success 200 {object} Response{data=[]tasks.TaskDescriptorAPI} +// @Router /api/v1/task_descriptors [get] +func (ah *APIHandler) GetTaskDescriptors(w http.ResponseWriter, r *http.Request) { + descriptors := tasks.GetAllTaskDescriptorsAPI() + ah.sendOKResponse(w, r.URL.String(), descriptors) +} + +// GetTaskDescriptor godoc +// @Summary Get a specific task descriptor +// @Description Returns a single task descriptor by name with its JSON schema +// @Tags Task +// @Produce json +// @Param name path string true "Task name" +// @Success 200 {object} Response{data=tasks.TaskDescriptorAPI} +// @Failure 404 {object} Response +// @Router /api/v1/task_descriptor/{name} [get] +func (ah *APIHandler) GetTaskDescriptor(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + name := vars["name"] + + descriptor := tasks.GetTaskDescriptorAPI(name) + if descriptor == nil { + ah.sendErrorResponse(w, r.URL.String(), "task not found", http.StatusNotFound) + return + } + + ah.sendOKResponse(w, r.URL.String(), descriptor) +} diff --git a/pkg/web/server.go b/pkg/web/server.go index 5a534118..d1711bc3 100644 --- a/pkg/web/server.go +++ b/pkg/web/server.go @@ -5,8 +5,10 @@ import ( "html/template" "net" "net/http" + "strconv" "strings" + "github.com/ethpandaops/assertoor/pkg/events" coordinator_types "github.com/ethpandaops/assertoor/pkg/types" "github.com/ethpandaops/assertoor/pkg/web/api" "github.com/ethpandaops/assertoor/pkg/web/handlers" @@ -74,6 +76,10 @@ func NewWebServer(config *types.ServerConfig, logger logrus.FieldLogger) (*Serve } func (ws *Server) ConfigureRoutes(frontendConfig *types.FrontendConfig, apiConfig *types.APIConfig, coordinator coordinator_types.Coordinator, securityTrimmed bool) error { + return ws.ConfigureRoutesWithEventBus(frontendConfig, apiConfig, coordinator, securityTrimmed, nil) +} + +func (ws *Server) ConfigureRoutesWithEventBus(frontendConfig *types.FrontendConfig, apiConfig *types.APIConfig, coordinator coordinator_types.Coordinator, securityTrimmed bool, eventBus *events.EventBus) error { isAPIEnabled := apiConfig != nil && apiConfig.Enabled if isAPIEnabled { // register api routes @@ -85,6 +91,25 @@ func (ws *Server) ConfigureRoutes(frontendConfig *types.FrontendConfig, apiConfi ws.router.HandleFunc("/api/v1/test_runs", apiHandler.GetTestRuns).Methods("GET") ws.router.HandleFunc("/api/v1/test_run/{runId}", apiHandler.GetTestRun).Methods("GET") ws.router.HandleFunc("/api/v1/test_run/{runId}/status", apiHandler.GetTestRunStatus).Methods("GET") + ws.router.HandleFunc("/api/v1/task_descriptors", apiHandler.GetTaskDescriptors).Methods("GET") + ws.router.HandleFunc("/api/v1/task_descriptor/{name}", apiHandler.GetTaskDescriptor).Methods("GET") + + // SSE event stream endpoints + if eventBus != nil { + sseHandler := events.NewSSEHandler(ws.logger.WithField("module", "sse"), eventBus) + ws.router.HandleFunc("/api/v1/events/stream", sseHandler.HandleGlobalStream).Methods("GET") + ws.router.HandleFunc("/api/v1/test_run/{runId}/events", func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + runID, err := strconv.ParseUint(vars["runId"], 10, 64) + if err != nil { + http.Error(w, "Invalid run ID", http.StatusBadRequest) + return + } + + sseHandler.HandleTestRunStream(w, r, runID) + }).Methods("GET") + } // private apis if !securityTrimmed { From c3e888659f4764b983da2be68e00441735efadec Mon Sep 17 00:00:00 2001 From: pk910 Date: Sun, 1 Feb 2026 04:43:08 +0100 Subject: [PATCH 03/32] [RF-2] simplify task lifecycle & result handling --- pkg/tasks/check_clients_are_healthy/README.md | 29 ++++-- pkg/tasks/check_clients_are_healthy/config.go | 4 + pkg/tasks/check_clients_are_healthy/task.go | 31 ++++-- .../README.md | 30 ++++-- .../config.go | 4 + .../check_consensus_attestation_stats/task.go | 25 ++++- pkg/tasks/check_consensus_finality/README.md | 22 ++++- pkg/tasks/check_consensus_finality/config.go | 4 + pkg/tasks/check_consensus_finality/task.go | 6 ++ pkg/tasks/check_consensus_forks/README.md | 21 +++- pkg/tasks/check_consensus_forks/config.go | 4 + pkg/tasks/check_consensus_forks/task.go | 19 +++- pkg/tasks/check_consensus_identity/README.md | 36 +++---- pkg/tasks/check_consensus_identity/config.go | 4 + pkg/tasks/check_consensus_identity/task.go | 39 +++++--- .../check_consensus_proposer_duty/README.md | 4 + .../check_consensus_proposer_duty/config.go | 4 + .../check_consensus_proposer_duty/task.go | 17 +++- pkg/tasks/check_consensus_reorgs/README.md | 21 +++- pkg/tasks/check_consensus_reorgs/config.go | 4 + pkg/tasks/check_consensus_reorgs/task.go | 15 ++- .../check_consensus_slot_range/README.md | 4 + .../check_consensus_slot_range/config.go | 4 + pkg/tasks/check_consensus_slot_range/task.go | 17 +++- .../check_consensus_sync_status/README.md | 27 +++-- .../check_consensus_sync_status/config.go | 4 + pkg/tasks/check_consensus_sync_status/task.go | 21 ++-- .../README.md | 39 ++++++-- .../config.go | 4 + .../check_consensus_validator_status/task.go | 22 ++++- pkg/tasks/check_eth_call/README.md | 45 +++++---- pkg/tasks/check_eth_call/config.go | 4 + pkg/tasks/check_eth_call/task.go | 52 ++++++---- .../check_execution_sync_status/README.md | 25 +++-- .../check_execution_sync_status/config.go | 4 + pkg/tasks/check_execution_sync_status/task.go | 21 ++-- pkg/tasks/run_task_background/README.md | 22 +++-- pkg/tasks/run_task_background/config.go | 15 +-- pkg/tasks/run_task_matrix/README.md | 57 +++++++---- pkg/tasks/run_task_matrix/config.go | 35 +++---- pkg/tasks/run_task_matrix/task.go | 57 ++++++++--- pkg/tasks/run_task_options/README.md | 41 ++++---- pkg/tasks/run_task_options/config.go | 21 ++-- pkg/tasks/run_task_options/task.go | 98 +++++-------------- pkg/tasks/run_tasks/README.md | 29 +++--- pkg/tasks/run_tasks/config.go | 14 +-- pkg/tasks/run_tasks/task.go | 41 ++++---- pkg/tasks/run_tasks_concurrent/README.md | 46 ++++++--- pkg/tasks/run_tasks_concurrent/config.go | 33 ++++--- pkg/tasks/run_tasks_concurrent/task.go | 57 ++++++++--- 50 files changed, 787 insertions(+), 415 deletions(-) diff --git a/pkg/tasks/check_clients_are_healthy/README.md b/pkg/tasks/check_clients_are_healthy/README.md index 4f1086ef..678e0e2c 100644 --- a/pkg/tasks/check_clients_are_healthy/README.md +++ b/pkg/tasks/check_clients_are_healthy/README.md @@ -3,31 +3,39 @@ ### Description The `check_clients_are_healthy` task is designed to ensure the health of specified clients. It verifies if the clients are reachable and synchronized on the same network. +#### Task Behavior +- The task polls clients at regular intervals to check their health status. +- By default, the task returns immediately when the health criteria are met. +- Use `continueOnPass: true` to keep monitoring even after success (useful for continuous health monitoring). + ### Configuration Parameters - **`clientPattern`**:\ A regular expression pattern used to specify which clients to check. This allows for targeted health checks of specific clients or groups of clients within the network. A blank pattern targets all clients. - **`pollInterval`**:\ - The interval at which the health check is performed. Set this to define how frequently the task should check the clients' health. + The interval at which the health check is performed. Set this to define how frequently the task should check the clients' health. Default: `5s`. - **`skipConsensusCheck`**:\ - A boolean value that, when set to `true`, skips the health check for consensus clients. Useful if you only want to focus on execution clients. + A boolean value that, when set to `true`, skips the health check for consensus clients. Useful if you only want to focus on execution clients. Default: `false`. - **`skipExecutionCheck`**:\ - A boolean value that, when set to `true`, skips the health check for execution clients. Use this to exclusively check the health of consensus clients. + A boolean value that, when set to `true`, skips the health check for execution clients. Use this to exclusively check the health of consensus clients. Default: `false`. - **`expectUnhealthy`**:\ - A boolean value that inverts the expected result of the health check. When `true`, the task succeeds if the clients are not ready or unhealthy. This can be useful in test scenarios where client unavailability is expected or being tested. + A boolean value that inverts the expected result of the health check. When `true`, the task succeeds if the clients are not ready or unhealthy. This can be useful in test scenarios where client unavailability is expected or being tested. Default: `false`. - **`minClientCount`**:\ - The minimum number of clients that must match the `clientNamePatterns` and pass the health checks for the task to succeed. A value of 0 indicates that all matching clients need to pass the health check. Use this to set a threshold for the number of healthy clients required by your test scenario. + The minimum number of clients that must match the `clientPattern` and pass the health checks for the task to succeed. A value of `0` indicates that all matching clients need to pass the health check. Default: `0`. - **`maxUnhealthyCount`**:\ - Specifies the maximum number of unhealthy clients allowed before the health check fails. A value of 0 means that any unhealthy client will cause the health check to fail, enforcing strict health criteria. + Specifies the maximum number of unhealthy clients allowed before the health check fails. A value of `-1` means unlimited unhealthy clients are allowed. A value of `0` means that any unhealthy client will cause the health check to fail. Default: `-1`. + +- **`failOnCheckMiss`**:\ + Determines the task's behavior when a health check fails. If `true`, the task reports a failure upon the first unsuccessful health check. If `false`, the task continues to poll the clients until a successful check occurs. Default: `false`. -- **`failOnCheckMiss`**: \ - Determines the task's behavior when a health check fails. If true, the task reports a failure upon the first unsuccessful health check. If false, the task continues to poll the clients until a successful check occurs, allowing for temporary issues to be resolved without immediate failure. +- **`continueOnPass`**:\ + If set to `true`, the task continues monitoring client health even after the criteria are met. This is useful for continuous health monitoring during long-running tests. If `false` (default), the task exits immediately on success. ### Defaults @@ -44,4 +52,9 @@ These are the default settings for the `check_clients_are_healthy` task: minClientCount: 0 maxUnhealthyCount: -1 failOnCheckMiss: false + continueOnPass: false ``` + +### Outputs + +This task does not produce any outputs. diff --git a/pkg/tasks/check_clients_are_healthy/config.go b/pkg/tasks/check_clients_are_healthy/config.go index da73ebda..8f54eb23 100644 --- a/pkg/tasks/check_clients_are_healthy/config.go +++ b/pkg/tasks/check_clients_are_healthy/config.go @@ -17,6 +17,10 @@ type Config struct { FailOnCheckMiss bool `yaml:"failOnCheckMiss" json:"failOnCheckMiss"` ExecutionRPCResultVar string `yaml:"executionRpcResultVar" json:"executionRpcResultVar"` ConsensusRPCResultVar string `yaml:"consensusRpcResultVar" json:"consensusRpcResultVar"` + // ContinueOnPass keeps the task running after the check passes. + // When false (default), the task exits immediately on success. + // When true, the task continues monitoring and may report failure if clients become unhealthy. + ContinueOnPass bool `yaml:"continueOnPass" json:"continueOnPass"` } func DefaultConfig() Config { diff --git a/pkg/tasks/check_clients_are_healthy/task.go b/pkg/tasks/check_clients_are_healthy/task.go index 794fa0da..67f200d9 100644 --- a/pkg/tasks/check_clients_are_healthy/task.go +++ b/pkg/tasks/check_clients_are_healthy/task.go @@ -111,17 +111,20 @@ func (t *Task) Execute(ctx context.Context) error { for { checkCount++ - t.processCheck(checkCount) + + if done, err := t.processCheck(checkCount); done { + return err + } select { case <-time.After(t.config.PollInterval.Duration): case <-ctx.Done(): - return nil + return ctx.Err() } } } -func (t *Task) processCheck(checkCount int) { +func (t *Task) processCheck(checkCount int) (bool, error) { expectedResult := !t.config.ExpectUnhealthy passResultCount := 0 totalClientCount := 0 @@ -181,22 +184,32 @@ func (t *Task) processCheck(checkCount int) { if t.config.FailOnCheckMiss { t.ctx.SetResult(types.TaskResultFailure) t.ctx.ReportProgress(0, fmt.Sprintf("Too many unhealthy clients: %d (attempt %d)", len(failedClients), checkCount)) - } else { - t.ctx.SetResult(types.TaskResultNone) - t.ctx.ReportProgress(0, fmt.Sprintf("Too many unhealthy clients: %d (attempt %d)", len(failedClients), checkCount)) + + return true, fmt.Errorf("too many unhealthy clients: %d", len(failedClients)) } + + t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Too many unhealthy clients: %d (attempt %d)", len(failedClients), checkCount)) case resultPass: t.ctx.SetResult(types.TaskResultSuccess) t.ctx.ReportProgress(100, fmt.Sprintf("All clients healthy: %d/%d", passResultCount, totalClientCount)) + + if !t.config.ContinueOnPass { + return true, nil + } default: if t.config.FailOnCheckMiss { t.ctx.SetResult(types.TaskResultFailure) t.ctx.ReportProgress(0, fmt.Sprintf("Unhealthy clients: %v (attempt %d)", failedClientNames, checkCount)) - } else { - t.ctx.SetResult(types.TaskResultNone) - t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for healthy clients... %d/%d (attempt %d)", passResultCount, totalClientCount, checkCount)) + + return true, fmt.Errorf("unhealthy clients: %v", failedClientNames) } + + t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for healthy clients... %d/%d (attempt %d)", passResultCount, totalClientCount, checkCount)) } + + return false, nil } func (t *Task) getClientInfo(client *clients.PoolClient) *ClientInfo { diff --git a/pkg/tasks/check_consensus_attestation_stats/README.md b/pkg/tasks/check_consensus_attestation_stats/README.md index 24f34a5c..7a38538e 100644 --- a/pkg/tasks/check_consensus_attestation_stats/README.md +++ b/pkg/tasks/check_consensus_attestation_stats/README.md @@ -3,32 +3,39 @@ ### Description The `check_consensus_attestation_stats` task is designed to monitor attestation voting statistics on the consensus chain, ensuring that voting patterns align with specified criteria. +#### Task Behavior +- The task monitors attestation statistics over a specified number of epochs. +- By default, the task returns immediately when the attestation criteria are met. +- Use `continueOnPass: true` to keep monitoring even after success. + ### Configuration Parameters - **`minTargetPercent`**:\ - The minimum percentage of correct target votes per checked epoch required for the task to succeed. The range is 0-100%. + The minimum percentage of correct target votes per checked epoch required for the task to succeed. The range is 0-100%. Default: `0`. - **`maxTargetPercent`**:\ - The maximum allowable percentage of correct target votes per checked epoch for the task to succeed. The range is 0-100%. + The maximum allowable percentage of correct target votes per checked epoch for the task to succeed. The range is 0-100%. Default: `100`. - **`minHeadPercent`**:\ - The minimum percentage of correct head votes per checked epoch needed for the task to succeed. The range is 0-100%. + The minimum percentage of correct head votes per checked epoch needed for the task to succeed. The range is 0-100%. Default: `0`. - **`maxHeadPercent`**:\ - The maximum allowable percentage of correct head votes per checked epoch for the task to succeed. The range is 0-100%. + The maximum allowable percentage of correct head votes per checked epoch for the task to succeed. The range is 0-100%. Default: `100`. - **`minTotalPercent`**:\ - The minimum overall voting participation per checked epoch in percent needed for the task to succeed. The range is 0-100%. + The minimum overall voting participation per checked epoch in percent needed for the task to succeed. The range is 0-100%. Default: `0`. - **`maxTotalPercent`**:\ - The maximum allowable overall voting participation per checked epoch for the task to succeed. The range is 0-100%. + The maximum allowable overall voting participation per checked epoch for the task to succeed. The range is 0-100%. Default: `100`. - **`failOnCheckMiss`**:\ - Determines whether the task should stop with a failure result if a checked epoch does not meet the specified voting ranges. \ - If `false`, the task continues checking subsequent epochs until it succeeds or times out. + Determines whether the task should stop with a failure result if a checked epoch does not meet the specified voting ranges. If `false`, the task continues checking subsequent epochs until it succeeds or times out. Default: `false`. - **`minCheckedEpochs`**:\ - The minimum number of consecutive epochs that must pass the check for the task to succeed. + The minimum number of consecutive epochs that must pass the check for the task to succeed. Default: `1`. + +- **`continueOnPass`**:\ + If set to `true`, the task continues monitoring attestation statistics even after the criteria are met. This is useful for long-running monitoring scenarios. If `false` (default), the task exits immediately on success. ### Defaults @@ -45,4 +52,9 @@ These are the default settings for the `check_consensus_attestation_stats` task: maxTotalPercent: 100 failOnCheckMiss: false minCheckedEpochs: 1 + continueOnPass: false ``` + +### Outputs + +This task does not produce any outputs. diff --git a/pkg/tasks/check_consensus_attestation_stats/config.go b/pkg/tasks/check_consensus_attestation_stats/config.go index 6b39e69d..fabd2188 100644 --- a/pkg/tasks/check_consensus_attestation_stats/config.go +++ b/pkg/tasks/check_consensus_attestation_stats/config.go @@ -9,6 +9,10 @@ type Config struct { MaxTotalPercent uint64 `yaml:"maxTotalPercent" json:"maxTotalPercent"` FailOnCheckMiss bool `yaml:"failOnCheckMiss" json:"failOnCheckMiss"` MinCheckedEpochs uint64 `yaml:"minCheckedEpochs" json:"minCheckedEpochs"` + // ContinueOnPass keeps the task running after the check passes. + // When false (default), the task exits immediately on success. + // When true, the task continues monitoring and may report failure if attestation stats change. + ContinueOnPass bool `yaml:"continueOnPass" json:"continueOnPass"` } func DefaultConfig() Config { diff --git a/pkg/tasks/check_consensus_attestation_stats/task.go b/pkg/tasks/check_consensus_attestation_stats/task.go index 23863a58..bf05cffc 100644 --- a/pkg/tasks/check_consensus_attestation_stats/task.go +++ b/pkg/tasks/check_consensus_attestation_stats/task.go @@ -179,7 +179,10 @@ func (t *Task) Execute(ctx context.Context) error { } checkCount++ - t.runAttestationStatsCheck(ctx, checkEpoch, checkCount) + + if done, err := t.runAttestationStatsCheck(ctx, checkEpoch, checkCount); done { + return err + } lastCheckedEpoch = checkEpoch @@ -263,7 +266,7 @@ func (t *Task) processBlock(ctx context.Context, block *consensus.Block) { t.attesterDutyMap[currentBlockEpoch][parentBlock.Root] = attesterDuties } -func (t *Task) runAttestationStatsCheck(ctx context.Context, epoch uint64, checkCount int) { +func (t *Task) runAttestationStatsCheck(ctx context.Context, epoch uint64, checkCount int) (bool, error) { consensusPool := t.ctx.Scheduler.GetServices().ClientPool().GetConsensusPool() canonicalFork := consensusPool.GetCanonicalFork(1) @@ -283,6 +286,12 @@ func (t *Task) runAttestationStatsCheck(ctx context.Context, epoch uint64, check if t.passedEpochs >= t.config.MinCheckedEpochs { t.ctx.SetResult(types.TaskResultSuccess) t.ctx.ReportProgress(100, fmt.Sprintf("Attestation stats check passed for epoch %d", epoch)) + + t.logger.Infof("epoch %v attestation check result: %v. passed checks: %v, want: %v", epoch, result, t.passedEpochs, t.config.MinCheckedEpochs) + + if !t.config.ContinueOnPass { + return true, nil + } } else { t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for attestation stats... %d/%d (attempt %d)", t.passedEpochs, t.config.MinCheckedEpochs, checkCount)) } @@ -291,16 +300,22 @@ func (t *Task) runAttestationStatsCheck(ctx context.Context, epoch uint64, check if t.config.FailOnCheckMiss { t.ctx.SetResult(types.TaskResultFailure) t.ctx.ReportProgress(0, fmt.Sprintf("Attestation stats check failed for epoch %d (attempt %d)", epoch, checkCount)) - } else { - t.ctx.SetResult(types.TaskResultNone) - t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for attestation stats... (attempt %d)", checkCount)) + + t.logger.Infof("epoch %v attestation check result: %v. passed checks: %v, want: %v", epoch, result, t.passedEpochs, t.config.MinCheckedEpochs) + + return true, fmt.Errorf("attestation stats check failed for epoch %d", epoch) } + + t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for attestation stats... (attempt %d)", checkCount)) } t.logger.Infof("epoch %v attestation check result: %v. passed checks: %v, want: %v", epoch, result, t.passedEpochs, t.config.MinCheckedEpochs) break } + + return false, nil } func (t *Task) checkEpochVotes(epoch uint64, epochVote *epochVotes) bool { diff --git a/pkg/tasks/check_consensus_finality/README.md b/pkg/tasks/check_consensus_finality/README.md index 32a25c2d..05d9e7b8 100644 --- a/pkg/tasks/check_consensus_finality/README.md +++ b/pkg/tasks/check_consensus_finality/README.md @@ -3,20 +3,27 @@ ### Description The `check_consensus_finality` task checks the finality status of the consensus chain. Finality in a blockchain context refers to the point where a block's transactions are considered irreversible. +#### Task Behavior +- The task monitors the finality status of the consensus chain. +- By default, the task returns immediately when the finality criteria are met. +- Use `continueOnPass: true` to keep monitoring even after success (useful when running concurrently with other tasks). + ### Configuration Parameters - **`minUnfinalizedEpochs`**:\ - The minimum number of epochs that are allowed to be not yet finalized. + The minimum number of epochs that are allowed to be not yet finalized. Default: `0`. - **`maxUnfinalizedEpochs`**:\ - The maximum number of epochs that can remain unfinalized before the task fails. + The maximum number of epochs that can remain unfinalized before the task fails. Default: `0`. - **`minFinalizedEpochs`**:\ - The minimum number of epochs that must be finalized for the task to be successful. + The minimum number of epochs that must be finalized for the task to be successful. Default: `0`. - **`failOnCheckMiss`**:\ - If set to `true`, the task will stop with a failure result if the finality status does not meet the criteria specified in the other parameters. \ - If `false`, the task will not fail immediately and will continue checking. + If set to `true`, the task will stop with a failure result if the finality status does not meet the criteria specified in the other parameters. If `false`, the task will not fail immediately and will continue checking. Default: `false`. + +- **`continueOnPass`**:\ + If set to `true`, the task continues monitoring even after the finality criteria are met. This is useful when running concurrently with other tasks where you want to keep checking that finality is maintained. If `false` (default), the task exits immediately on success. ### Defaults @@ -29,4 +36,9 @@ These are the default settings for the `check_consensus_finality` task: maxUnfinalizedEpochs: 0 minFinalizedEpochs: 0 failOnCheckMiss: false + continueOnPass: false ``` + +### Outputs + +This task does not produce any outputs. diff --git a/pkg/tasks/check_consensus_finality/config.go b/pkg/tasks/check_consensus_finality/config.go index e4afe09f..d582e7b2 100644 --- a/pkg/tasks/check_consensus_finality/config.go +++ b/pkg/tasks/check_consensus_finality/config.go @@ -5,6 +5,10 @@ type Config struct { MaxUnfinalizedEpochs uint64 `yaml:"maxUnfinalizedEpochs" json:"maxUnfinalizedEpochs"` MinFinalizedEpochs uint64 `yaml:"minFinalizedEpochs" json:"minFinalizedEpochs"` FailOnCheckMiss bool `yaml:"failOnCheckMiss" json:"failOnCheckMiss"` + // ContinueOnPass keeps the task running after the check passes. + // When false (default), the task exits immediately on success. + // When true, the task continues monitoring and may report failure if conditions change. + ContinueOnPass bool `yaml:"continueOnPass" json:"continueOnPass"` } func DefaultConfig() Config { diff --git a/pkg/tasks/check_consensus_finality/task.go b/pkg/tasks/check_consensus_finality/task.go index 70bfc7f0..5cf63207 100644 --- a/pkg/tasks/check_consensus_finality/task.go +++ b/pkg/tasks/check_consensus_finality/task.go @@ -107,9 +107,15 @@ func (t *Task) Execute(ctx context.Context) error { case checkResult: t.ctx.SetResult(types.TaskResultSuccess) t.ctx.ReportProgress(100, "Finality check passed") + + if !t.config.ContinueOnPass { + return nil + } case t.config.FailOnCheckMiss: t.ctx.SetResult(types.TaskResultFailure) t.ctx.ReportProgress(0, fmt.Sprintf("Finality check failed (attempt %d)", checkCount)) + + return fmt.Errorf("finality check failed") default: t.ctx.SetResult(types.TaskResultNone) t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for finality... (attempt %d)", checkCount)) diff --git a/pkg/tasks/check_consensus_forks/README.md b/pkg/tasks/check_consensus_forks/README.md index 0f421a56..8687636d 100644 --- a/pkg/tasks/check_consensus_forks/README.md +++ b/pkg/tasks/check_consensus_forks/README.md @@ -3,17 +3,24 @@ ### Description The `check_consensus_forks` task is designed to check for forks in the consensus layer of the blockchain. Forks occur when there are divergences in the blockchain, leading to two or more competing chains. +#### Task Behavior +- The task monitors for chain forks over a specified number of epochs. +- By default, the task returns immediately when the fork criteria are met for the minimum number of epochs. +- Use `continueOnPass: true` to keep monitoring even after success (useful for detecting late forks). + ### Configuration Parameters - **`minCheckEpochCount`**:\ - The minimum number of epochs to check for forks. + The minimum number of epochs to check for forks. Default: `1`. - **`maxForkDistance`**:\ - The maximum distance allowed before a divergence in the chain is counted as a fork. \ - The distance is measured by the number of blocks between the heads of the forked chains. + The maximum distance allowed before a divergence in the chain is counted as a fork. The distance is measured by the number of blocks between the heads of the forked chains. Default: `1`. - **`maxForkCount`**:\ - The maximum number of forks that are acceptable. If the number of forks exceeds this limit, the task will complete with a failure result. + The maximum number of forks that are acceptable. If the number of forks exceeds this limit, the task will complete with a failure result. Default: `0`. + +- **`continueOnPass`**:\ + If set to `true`, the task continues monitoring for forks even after the criteria are met. This is useful for detecting forks that may occur later in the test. If `false` (default), the task exits immediately on success. ### Defaults @@ -25,4 +32,10 @@ These are the default settings for the `check_consensus_forks` task: minCheckEpochCount: 1 maxForkDistance: 1 maxForkCount: 0 + continueOnPass: false ``` + +### Outputs + +- **`forks`**:\ + Array of fork info objects containing head slot, root, and clients on each fork. diff --git a/pkg/tasks/check_consensus_forks/config.go b/pkg/tasks/check_consensus_forks/config.go index 174fc10d..04c87855 100644 --- a/pkg/tasks/check_consensus_forks/config.go +++ b/pkg/tasks/check_consensus_forks/config.go @@ -4,6 +4,10 @@ type Config struct { MinCheckEpochCount uint64 `yaml:"minCheckEpochCount" json:"minCheckEpochCount"` MaxForkDistance int64 `yaml:"maxForkDistance" json:"maxForkDistance"` MaxForkCount uint64 `yaml:"maxForkCount" json:"maxForkCount"` + // ContinueOnPass keeps the task running after the check passes. + // When false (default), the task exits immediately on success. + // When true, the task continues monitoring and may report failure if forks occur. + ContinueOnPass bool `yaml:"continueOnPass" json:"continueOnPass"` } func DefaultConfig() Config { diff --git a/pkg/tasks/check_consensus_forks/task.go b/pkg/tasks/check_consensus_forks/task.go index 029f74c9..3c5965c7 100644 --- a/pkg/tasks/check_consensus_forks/task.go +++ b/pkg/tasks/check_consensus_forks/task.go @@ -102,14 +102,17 @@ func (t *Task) Execute(ctx context.Context) error { select { case <-blockSubscription.Channel(): checkCount++ - t.processCheck(checkCount) + + if done, err := t.processCheck(checkCount); done { + return err + } case <-ctx.Done(): return ctx.Err() } } } -func (t *Task) processCheck(checkCount int) { +func (t *Task) processCheck(checkCount int) (bool, error) { consensusPool := t.ctx.Scheduler.GetServices().ClientPool().GetConsensusPool() headForks := consensusPool.GetHeadForks(t.config.MaxForkDistance) headForkInfo := make([]*ForkInfo, len(headForks)) @@ -148,7 +151,7 @@ func (t *Task) processCheck(checkCount int) { t.ctx.SetResult(types.TaskResultFailure) t.ctx.ReportProgress(0, fmt.Sprintf("Too many forks: %d (attempt %d)", len(headForks)-1, checkCount)) - return + return true, fmt.Errorf("too many forks: %d", len(headForks)-1) } _, currentEpoch, err := consensusPool.GetBlockCache().GetWallclock().Now() @@ -157,7 +160,7 @@ func (t *Task) processCheck(checkCount int) { t.ctx.SetResult(types.TaskResultNone) t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for fork check... (attempt %d)", checkCount)) - return + return false, nil } epochCount := currentEpoch.Number() - t.startEpoch @@ -167,9 +170,15 @@ func (t *Task) processCheck(checkCount int) { t.ctx.SetResult(types.TaskResultNone) t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for fork check... %d/%d epochs (attempt %d)", epochCount, t.config.MinCheckEpochCount, checkCount)) - return + return false, nil } t.ctx.SetResult(types.TaskResultSuccess) t.ctx.ReportProgress(100, fmt.Sprintf("Fork check passed after %d epochs", epochCount)) + + if !t.config.ContinueOnPass { + return true, nil + } + + return false, nil } diff --git a/pkg/tasks/check_consensus_identity/README.md b/pkg/tasks/check_consensus_identity/README.md index 158d8e88..e729adc8 100644 --- a/pkg/tasks/check_consensus_identity/README.md +++ b/pkg/tasks/check_consensus_identity/README.md @@ -2,6 +2,12 @@ This task checks consensus client node identity information by querying the `/eth/v1/node/identity` API endpoint. It can verify various aspects of the node identity including CGC (Custody Group Count) extracted from ENR (Ethereum Node Record). +## Task Behavior + +- The task polls clients at regular intervals to check their identity information. +- By default, the task returns immediately when the identity criteria are met. +- Use `continueOnPass: true` to keep monitoring even after success. + ## Configuration ### Required Parameters @@ -12,6 +18,7 @@ This task checks consensus client node identity information by querying the `/et - **`minClientCount`** *(int)*: Minimum number of clients that must pass checks (default: `1`) - **`maxFailCount`** *(int)*: Maximum number of clients that can fail (-1 for no limit, default: `-1`) - **`failOnCheckMiss`** *(bool)*: Whether to fail the task when checks don't pass (default: `false`) +- **`continueOnPass`** *(bool)*: Keep monitoring even after success (default: `false`) ### CGC (Custody Group Count) Checks - **`expectCgc`** *(int)*: Expect exact CGC value @@ -77,6 +84,17 @@ Each client result includes: failOnCheckMiss: true ``` +### Continuous Monitoring +```yaml +- name: monitor_identity + task: check_consensus_identity + config: + clientPattern: "*" + minCgc: 4 + continueOnPass: true + timeout: 30m +``` + ### Comprehensive Identity Check ```yaml - name: full_identity_check @@ -92,26 +110,10 @@ Each client result includes: failOnCheckMiss: true ``` -### Using Outputs in Subsequent Tasks -```yaml -- name: check_identity - task: check_consensus_identity - config: - clientPattern: "*" - expectCgc: 8 - -- name: verify_results - task: run_shell - config: - command: | - echo "Found ${check_identity.matchingCount} matching clients" - echo "Total CGC sum: $(echo '${check_identity.matchingClients}' | jq '[.[] | .cgc] | add')" -``` - ## Use Cases 1. **PeerDAS Validation**: Verify nodes have correct custody assignments 2. **Network Health**: Check node identity consistency across clients 3. **Configuration Validation**: Ensure nodes are properly configured for specific network requirements 4. **Testing**: Validate node behavior changes after deposits or configuration updates -5. **Monitoring**: Track node identity changes over time \ No newline at end of file +5. **Monitoring**: Track node identity changes over time diff --git a/pkg/tasks/check_consensus_identity/config.go b/pkg/tasks/check_consensus_identity/config.go index 37626398..c171aa07 100644 --- a/pkg/tasks/check_consensus_identity/config.go +++ b/pkg/tasks/check_consensus_identity/config.go @@ -32,6 +32,10 @@ type Config struct { // Metadata checks ExpectSeqNumber *uint64 `yaml:"expectSeqNumber" json:"expectSeqNumber"` MinSeqNumber *uint64 `yaml:"minSeqNumber" json:"minSeqNumber"` + // ContinueOnPass keeps the task running after the check passes. + // When false (default), the task exits immediately on success. + // When true, the task continues monitoring and may report failure if identity changes. + ContinueOnPass bool `yaml:"continueOnPass" json:"continueOnPass"` } func DefaultConfig() Config { diff --git a/pkg/tasks/check_consensus_identity/task.go b/pkg/tasks/check_consensus_identity/task.go index 3b089c69..7cb0e29c 100644 --- a/pkg/tasks/check_consensus_identity/task.go +++ b/pkg/tasks/check_consensus_identity/task.go @@ -122,17 +122,20 @@ func (t *Task) Execute(ctx context.Context) error { for { checkCount++ - t.processCheck(checkCount) + + if done, err := t.processCheck(checkCount); done { + return err + } select { case <-time.After(t.config.PollInterval.Duration): case <-ctx.Done(): - return nil + return ctx.Err() } } } -func (t *Task) processCheck(checkCount int) { +func (t *Task) processCheck(checkCount int) (bool, error) { passResultCount := 0 totalClientCount := 0 matchingClients := []*IdentityCheckResult{} @@ -222,25 +225,39 @@ func (t *Task) processCheck(checkCount int) { t.logger.Infof("Setting result to FAILURE (too many failures: %d > %d)", len(failedClients), t.config.MaxFailCount) t.ctx.SetResult(types.TaskResultFailure) t.ctx.ReportProgress(0, fmt.Sprintf("Too many failures: %d (attempt %d)", len(failedClients), checkCount)) - } else { - t.logger.Infof("Setting result to PENDING (too many failures but failOnCheckMiss=false)") - t.ctx.SetResult(types.TaskResultNone) - t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for identity check... %d/%d (attempt %d)", passResultCount, requiredPassCount, checkCount)) + + return true, fmt.Errorf("too many identity check failures: %d", len(failedClients)) } + + t.logger.Infof("Setting result to PENDING (too many failures but failOnCheckMiss=false)") + t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for identity check... %d/%d (attempt %d)", passResultCount, requiredPassCount, checkCount)) + + return false, nil case resultPass: t.logger.Infof("Setting result to SUCCESS (requirements met)") t.ctx.SetResult(types.TaskResultSuccess) t.ctx.ReportProgress(100, fmt.Sprintf("Identity check passed: %d/%d clients", passResultCount, totalClientCount)) + + if !t.config.ContinueOnPass { + return true, nil + } + + return false, nil default: if t.config.FailOnCheckMiss { t.logger.Infof("Setting result to FAILURE (requirements not met and failOnCheckMiss=true)") t.ctx.SetResult(types.TaskResultFailure) t.ctx.ReportProgress(0, fmt.Sprintf("Identity check failed: %d/%d (attempt %d)", passResultCount, requiredPassCount, checkCount)) - } else { - t.logger.Infof("Setting result to PENDING (requirements not met but failOnCheckMiss=false)") - t.ctx.SetResult(types.TaskResultNone) - t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for identity check... %d/%d (attempt %d)", passResultCount, requiredPassCount, checkCount)) + + return true, fmt.Errorf("identity check failed: %d/%d", passResultCount, requiredPassCount) } + + t.logger.Infof("Setting result to PENDING (requirements not met but failOnCheckMiss=false)") + t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for identity check... %d/%d (attempt %d)", passResultCount, requiredPassCount, checkCount)) + + return false, nil } } diff --git a/pkg/tasks/check_consensus_proposer_duty/README.md b/pkg/tasks/check_consensus_proposer_duty/README.md index 5ca3589b..2d91bf3b 100644 --- a/pkg/tasks/check_consensus_proposer_duty/README.md +++ b/pkg/tasks/check_consensus_proposer_duty/README.md @@ -20,6 +20,9 @@ The `check_consensus_proposer_duty` task is designed to check for a specific pro - **`failOnCheckMiss`**:\ This parameter specifies the task's behavior if a matching proposer duty is not found within the `maxSlotDistance`. If set to `false`, the task continues running until it either finds a matching proposer duty or reaches its timeout. If `true`, the task will fail immediately upon not finding a matching duty. +- **`continueOnPass`**:\ + When set to `false` (default), the task exits immediately upon finding a matching proposer duty. When set to `true`, the task continues running after success, allowing it to be used for continuous monitoring within concurrent task execution. + ### Defaults These are the default settings for the `check_consensus_proposer_duty` task: @@ -31,4 +34,5 @@ These are the default settings for the `check_consensus_proposer_duty` task: validatorIndex: null maxSlotDistance: 0 failOnCheckMiss: false + continueOnPass: false ``` diff --git a/pkg/tasks/check_consensus_proposer_duty/config.go b/pkg/tasks/check_consensus_proposer_duty/config.go index cb00aaeb..46e4e5d5 100644 --- a/pkg/tasks/check_consensus_proposer_duty/config.go +++ b/pkg/tasks/check_consensus_proposer_duty/config.go @@ -6,6 +6,10 @@ type Config struct { MinSlotDistance uint64 `yaml:"minSlotDistance" json:"minSlotDistance"` MaxSlotDistance uint64 `yaml:"maxSlotDistance" json:"maxSlotDistance"` FailOnCheckMiss bool `yaml:"failOnCheckMiss" json:"failOnCheckMiss"` + // ContinueOnPass keeps the task running after the check passes. + // When false (default), the task exits immediately on success. + // When true, the task continues monitoring and may report failure if conditions change. + ContinueOnPass bool `yaml:"continueOnPass" json:"continueOnPass"` } func DefaultConfig() Config { diff --git a/pkg/tasks/check_consensus_proposer_duty/task.go b/pkg/tasks/check_consensus_proposer_duty/task.go index cfb0ee2b..ef83101c 100644 --- a/pkg/tasks/check_consensus_proposer_duty/task.go +++ b/pkg/tasks/check_consensus_proposer_duty/task.go @@ -100,26 +100,39 @@ func (t *Task) Execute(ctx context.Context) error { case currentSlot := <-wallclockSlotSubscription.Channel(): checkCount++ - t.processCheck(currentSlot.Number(), checkCount) + + if done, err := t.processCheck(currentSlot.Number(), checkCount); done { + return err + } case <-ctx.Done(): return ctx.Err() } } } -func (t *Task) processCheck(slot uint64, checkCount int) { +func (t *Task) processCheck(slot uint64, checkCount int) (bool, error) { checkResult := t.runProposerDutyCheck(slot) switch { case checkResult: t.ctx.SetResult(types.TaskResultSuccess) t.ctx.ReportProgress(100, fmt.Sprintf("Proposer duty check passed at slot %d", slot)) + + if !t.config.ContinueOnPass { + return true, nil + } + + return false, nil case t.config.FailOnCheckMiss: t.ctx.SetResult(types.TaskResultFailure) t.ctx.ReportProgress(0, fmt.Sprintf("Proposer duty check failed at slot %d", slot)) + + return true, fmt.Errorf("proposer duty check failed at slot %d", slot) default: t.ctx.SetResult(types.TaskResultNone) t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for proposer duty... (attempt %d)", checkCount)) + + return false, nil } } diff --git a/pkg/tasks/check_consensus_reorgs/README.md b/pkg/tasks/check_consensus_reorgs/README.md index 949eb461..47cacb63 100644 --- a/pkg/tasks/check_consensus_reorgs/README.md +++ b/pkg/tasks/check_consensus_reorgs/README.md @@ -3,19 +3,27 @@ ### Description The `check_consensus_reorgs` task is designed to monitor for reorganizations (reorgs) in the consensus layer of the blockchain. Reorgs occur when the blockchain switches to a different chain due to more blocks being added to it, which can be a normal part of blockchain operation or indicate issues. +#### Task Behavior +- The task monitors for chain reorganizations over a specified number of epochs. +- By default, the task returns immediately when the reorg criteria are met for the minimum number of epochs. +- Use `continueOnPass: true` to keep monitoring even after success (useful for detecting late reorgs). + ### Configuration Parameters - **`minCheckEpochCount`**:\ - The minimum number of epochs to be checked for reorgs. An epoch is a specific period in blockchain time. + The minimum number of epochs to be checked for reorgs. An epoch is a specific period in blockchain time. Default: `1`. - **`maxReorgDistance`**:\ - The maximum allowable distance for a reorg to occur. This is measured in terms of the number of blocks. + The maximum allowable distance for a reorg to occur. This is measured in terms of the number of blocks. Default: `0`. - **`maxReorgsPerEpoch`**:\ - The maximum number of reorgs allowed within a single epoch. If this number is exceeded, it could indicate unusual activity on the blockchain. + The maximum number of reorgs allowed within a single epoch. If this number is exceeded, it could indicate unusual activity on the blockchain. Default: `0`. - **`maxTotalReorgs`**:\ - The total maximum number of reorgs allowed across all checked epochs. Exceeding this number could be a sign of instability in the blockchain. + The total maximum number of reorgs allowed across all checked epochs. Exceeding this number could be a sign of instability in the blockchain. Default: `0`. + +- **`continueOnPass`**:\ + If set to `true`, the task continues monitoring for reorgs even after the criteria are met. This is useful for detecting late reorgs during long-running tests. If `false` (default), the task exits immediately on success. ### Defaults @@ -28,4 +36,9 @@ These are the default settings for the `check_consensus_reorgs` task: maxReorgDistance: 0 maxReorgsPerEpoch: 0 maxTotalReorgs: 0 + continueOnPass: false ``` + +### Outputs + +This task does not produce any outputs. diff --git a/pkg/tasks/check_consensus_reorgs/config.go b/pkg/tasks/check_consensus_reorgs/config.go index 5798883e..de5a2bd2 100644 --- a/pkg/tasks/check_consensus_reorgs/config.go +++ b/pkg/tasks/check_consensus_reorgs/config.go @@ -5,6 +5,10 @@ type Config struct { MaxReorgDistance uint64 `yaml:"maxReorgDistance" json:"maxReorgDistance"` MaxReorgsPerEpoch float64 `yaml:"maxReorgsPerEpoch" json:"maxReorgsPerEpoch"` MaxTotalReorgs uint64 `yaml:"maxTotalReorgs" json:"maxTotalReorgs"` + // ContinueOnPass keeps the task running after the check passes. + // When false (default), the task exits immediately on success. + // When true, the task continues monitoring and may report failure if reorgs occur. + ContinueOnPass bool `yaml:"continueOnPass" json:"continueOnPass"` } func DefaultConfig() Config { diff --git a/pkg/tasks/check_consensus_reorgs/task.go b/pkg/tasks/check_consensus_reorgs/task.go index ab0b8f56..6a17899b 100644 --- a/pkg/tasks/check_consensus_reorgs/task.go +++ b/pkg/tasks/check_consensus_reorgs/task.go @@ -114,7 +114,10 @@ func (t *Task) Execute(ctx context.Context) error { } checkCount++ - t.processCheck(checkCount) + + if done, err := t.processCheck(checkCount); done { + return err + } lastBlock = block case <-ctx.Done(): @@ -123,18 +126,26 @@ func (t *Task) Execute(ctx context.Context) error { } } -func (t *Task) processCheck(checkCount int) { +func (t *Task) processCheck(checkCount int) (bool, error) { result := t.runCheck() t.ctx.SetResult(result) switch result { case types.TaskResultSuccess: t.ctx.ReportProgress(100, fmt.Sprintf("Reorg check passed (reorgs: %d, max distance: %d)", t.totalReorgs, t.maxReorgDistance)) + + if !t.config.ContinueOnPass { + return true, nil + } case types.TaskResultFailure: t.ctx.ReportProgress(0, fmt.Sprintf("Reorg check failed (reorgs: %d, max distance: %d)", t.totalReorgs, t.maxReorgDistance)) + + return true, fmt.Errorf("reorg check failed (reorgs: %d, max distance: %d)", t.totalReorgs, t.maxReorgDistance) case types.TaskResultNone: t.ctx.ReportProgress(0, fmt.Sprintf("Checking for reorgs... (attempt %d)", checkCount)) } + + return false, nil } func (t *Task) runCheck() types.TaskResult { diff --git a/pkg/tasks/check_consensus_slot_range/README.md b/pkg/tasks/check_consensus_slot_range/README.md index 615cf62c..474d6a66 100644 --- a/pkg/tasks/check_consensus_slot_range/README.md +++ b/pkg/tasks/check_consensus_slot_range/README.md @@ -20,6 +20,9 @@ The `check_consensus_slot_range` task verifies that the current wall clock time - **`failIfLower`**:\ A flag that determines the task's behavior if the current wall clock time is below the specified minimum slot or epoch number. If `true`, the task will fail in such cases; if `false`, it will continue without failing. +- **`continueOnPass`**:\ + When set to `false` (default), the task exits immediately upon the slot/epoch being within range. When set to `true`, the task continues running after success, allowing it to be used for continuous monitoring within concurrent task execution. + ### Defaults These are the default settings for the `check_consensus_slot_range` task: @@ -32,4 +35,5 @@ These are the default settings for the `check_consensus_slot_range` task: minEpochNumber: 0 maxEpochNumber: 18446744073709551615 failIfLower: false + continueOnPass: false ``` diff --git a/pkg/tasks/check_consensus_slot_range/config.go b/pkg/tasks/check_consensus_slot_range/config.go index 69d43d36..bc00bc3a 100644 --- a/pkg/tasks/check_consensus_slot_range/config.go +++ b/pkg/tasks/check_consensus_slot_range/config.go @@ -8,6 +8,10 @@ type Config struct { MinEpochNumber uint64 `yaml:"minEpochNumber" json:"minEpochNumber"` MaxEpochNumber uint64 `yaml:"maxEpochNumber" json:"maxEpochNumber"` FailIfLower bool `yaml:"failIfLower" json:"failIfLower"` + // ContinueOnPass keeps the task running after the check passes. + // When false (default), the task exits immediately on success. + // When true, the task continues monitoring and may report failure if conditions change. + ContinueOnPass bool `yaml:"continueOnPass" json:"continueOnPass"` } func DefaultConfig() Config { diff --git a/pkg/tasks/check_consensus_slot_range/task.go b/pkg/tasks/check_consensus_slot_range/task.go index f204552b..d72c8d35 100644 --- a/pkg/tasks/check_consensus_slot_range/task.go +++ b/pkg/tasks/check_consensus_slot_range/task.go @@ -98,7 +98,10 @@ func (t *Task) Execute(ctx context.Context) error { for { checkCount++ - t.processCheck(checkCount) + + if done, err := t.processCheck(checkCount); done { + return err + } select { case slot := <-wallclockSubscription.Channel(): @@ -109,19 +112,29 @@ func (t *Task) Execute(ctx context.Context) error { } } -func (t *Task) processCheck(checkCount int) { +func (t *Task) processCheck(checkCount int) (bool, error) { checkResult, isLower := t.runRangeCheck() switch { case checkResult: t.ctx.SetResult(types.TaskResultSuccess) t.ctx.ReportProgress(100, "Slot range check passed") + + if !t.config.ContinueOnPass { + return true, nil + } + + return false, nil case !isLower || t.config.FailIfLower: t.ctx.SetResult(types.TaskResultFailure) t.ctx.ReportProgress(0, "Slot range check failed") + + return true, fmt.Errorf("slot range check failed") default: t.ctx.SetResult(types.TaskResultNone) t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for slot range... (attempt %d)", checkCount)) + + return false, nil } } diff --git a/pkg/tasks/check_consensus_sync_status/README.md b/pkg/tasks/check_consensus_sync_status/README.md index d026edbc..f1204747 100644 --- a/pkg/tasks/check_consensus_sync_status/README.md +++ b/pkg/tasks/check_consensus_sync_status/README.md @@ -3,31 +3,39 @@ ### Description The `check_consensus_sync_status` task checks the synchronization status of consensus clients, ensuring they are aligned with the current state of the blockchain network. +#### Task Behavior +- The task polls consensus clients at regular intervals to check their sync status. +- By default, the task returns immediately when the sync criteria are met. +- Use `continueOnPass: true` to keep monitoring even after success. + ### Configuration Parameters - **`clientPattern`**:\ A regular expression pattern used to specify which clients to check. This allows for targeted health checks of specific clients or groups of clients within the network. A blank pattern targets all clients. - **`pollInterval`**:\ - The frequency for checking the clients' sync status. + The frequency for checking the clients' sync status. Default: `5s`. - **`expectSyncing`**:\ - Set to `true` if the clients are expected to be in a syncing state, or `false` if they should be fully synced. + Set to `true` if the clients are expected to be in a syncing state, or `false` if they should be fully synced. Default: `false`. - **`expectOptimistic`**:\ - When `true`, expects clients to be in an optimistic sync state. + When `true`, expects clients to be in an optimistic sync state. Default: `false`. - **`expectMinPercent`**:\ - The minimum sync progress percentage required for the task to succeed. + The minimum sync progress percentage required for the task to succeed. Default: `100`. - **`expectMaxPercent`**:\ - The maximum sync progress percentage allowable for the task to succeed. + The maximum sync progress percentage allowable for the task to succeed. Default: `100`. - **`minSlotHeight`**:\ - The minimum slot height that clients should be synced to. + The minimum slot height that clients should be synced to. Default: `10`. - **`waitForChainProgression`**:\ - If set to `true`, the task checks for blockchain progression in addition to synchronization status. If `false`, the task solely checks for synchronization status, without waiting for further chain progression. + If set to `true`, the task checks for blockchain progression in addition to synchronization status. If `false`, the task solely checks for synchronization status, without waiting for further chain progression. Default: `false`. + +- **`continueOnPass`**:\ + If set to `true`, the task continues monitoring sync status even after the criteria are met. This is useful when running concurrently with other tasks. If `false` (default), the task exits immediately on success. ### Defaults @@ -44,4 +52,9 @@ Default settings for the `check_consensus_sync_status` task: expectMaxPercent: 100 minSlotHeight: 10 waitForChainProgression: false + continueOnPass: false ``` + +### Outputs + +This task does not produce any outputs. diff --git a/pkg/tasks/check_consensus_sync_status/config.go b/pkg/tasks/check_consensus_sync_status/config.go index b7babb52..1e8180d4 100644 --- a/pkg/tasks/check_consensus_sync_status/config.go +++ b/pkg/tasks/check_consensus_sync_status/config.go @@ -16,6 +16,10 @@ type Config struct { ExpectMaxPercent float64 `yaml:"expectMaxPercent" json:"expectMaxPercent"` MinSlotHeight int `yaml:"minSlotHeight" json:"minSlotHeight"` WaitForChainProgression bool `yaml:"waitForChainProgression" json:"waitForChainProgression"` + // ContinueOnPass keeps the task running after the check passes. + // When false (default), the task exits immediately on success. + // When true, the task continues monitoring and may report failure if sync status changes. + ContinueOnPass bool `yaml:"continueOnPass" json:"continueOnPass"` } func DefaultConfig() Config { diff --git a/pkg/tasks/check_consensus_sync_status/task.go b/pkg/tasks/check_consensus_sync_status/task.go index 1996606b..32dc7f1c 100644 --- a/pkg/tasks/check_consensus_sync_status/task.go +++ b/pkg/tasks/check_consensus_sync_status/task.go @@ -99,17 +99,20 @@ func (t *Task) Execute(ctx context.Context) error { for { checkCount++ - t.processCheck(ctx, checkCount) + + if done := t.processCheck(ctx, checkCount); done { + return nil + } select { case <-time.After(t.config.PollInterval.Duration): case <-ctx.Done(): - return nil + return ctx.Err() } } } -func (t *Task) processCheck(ctx context.Context, checkCount int) { +func (t *Task) processCheck(ctx context.Context, checkCount int) bool { allResultsPass := true goodClients := []*ClientInfo{} failedClients := []*ClientInfo{} @@ -122,7 +125,7 @@ func (t *Task) processCheck(ctx context.Context, checkCount int) { syncStatus, err := client.ConsensusClient.GetRPCClient().GetNodeSyncStatus(ctx) if ctx.Err() != nil { - return + return false } if err != nil { @@ -160,10 +163,14 @@ func (t *Task) processCheck(ctx context.Context, checkCount int) { if allResultsPass { t.ctx.SetResult(types.TaskResultSuccess) t.ctx.ReportProgress(100, fmt.Sprintf("All clients synced: %d", len(goodClients))) - } else { - t.ctx.SetResult(types.TaskResultNone) - t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for sync... %d/%d (attempt %d)", len(goodClients), len(goodClients)+len(failedClients), checkCount)) + + return !t.config.ContinueOnPass } + + t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for sync... %d/%d (attempt %d)", len(goodClients), len(goodClients)+len(failedClients), checkCount)) + + return false } func (t *Task) processClientCheck(client *clients.PoolClient, syncStatus *rpc.SyncStatus, checkLogger logrus.FieldLogger) bool { diff --git a/pkg/tasks/check_consensus_validator_status/README.md b/pkg/tasks/check_consensus_validator_status/README.md index 7b4125dd..0a0bd16c 100644 --- a/pkg/tasks/check_consensus_validator_status/README.md +++ b/pkg/tasks/check_consensus_validator_status/README.md @@ -3,34 +3,45 @@ ### Description The `check_consensus_validator_status` task is focused on verifying the status of validators on the consensus chain. It checks if the validators are in the expected state, as per the specified criteria. +#### Task Behavior +- The task monitors validator status at each epoch. +- By default, the task returns immediately when a matching validator is found. +- Use `continueOnPass: true` to keep monitoring even after success (useful for tracking status changes). + ### Configuration Parameters - **`validatorPubKey`**:\ - The public key of the validator to be checked. If specified, the task will focus on the validator with this public key. + The public key of the validator to be checked. If specified, the task will focus on the validator with this public key. Default: `""`. - **`validatorNamePattern`**:\ - A pattern for identifying validators by name. Useful for filtering validators to be checked based on their names. + A pattern for identifying validators by name. Useful for filtering validators to be checked based on their names. Default: `""`. - **`validatorIndex`**:\ - The index of a specific validator. If set, the task focuses on the validator with this index. If `null`, no filter on validator index is applied. + The index of a specific validator. If set, the task focuses on the validator with this index. If `null`, no filter on validator index is applied. Default: `null`. - **`validatorStatus`**:\ - A list of allowed validator statuses. The task will check if the validator's status matches any of the statuses in this list. + A list of allowed validator statuses. The task will check if the validator's status matches any of the statuses in this list. Default: `[]`. - **`minValidatorBalance`**:\ - The minimum balance of the validator to match. + The minimum balance of the validator to match. Default: `0`. - **`maxValidatorBalance`**:\ - The maximum balance of the validator to match. + The maximum balance of the validator to match. Default: `null`. - **`withdrawalCredsPrefix`**:\ - The withdrawal credentials prefix the validator should have. + The withdrawal credentials prefix the validator should have. Default: `""`. - **`failOnCheckMiss`**:\ - Determines the task's behavior if the validator's status does not match any of the statuses in `validatorStatus`. If `false`, the task will continue running and wait for the validator to match the expected status. If `true`, the task will fail immediately upon a status mismatch. + Determines the task's behavior if the validator's status does not match any of the statuses in `validatorStatus`. If `false`, the task will continue running and wait for the validator to match the expected status. If `true`, the task will fail immediately upon a status mismatch. Default: `false`. + +- **`continueOnPass`**:\ + If set to `true`, the task continues monitoring validator status even after a matching validator is found. This is useful for tracking status changes over time. If `false` (default), the task exits immediately on success. - **`validatorInfoResultVar`**:\ - The name of the variable where the resulting information about the validator will be stored. This includes status, index, balance and any other relevant data fetched during the check. + The name of the variable where the resulting information about the validator will be stored. This includes status, index, balance and any other relevant data fetched during the check. Default: `""`. + +- **`validatorPubKeyResultVar`**:\ + The name of the variable where the validator's public key will be stored. Default: `""`. ### Defaults @@ -47,5 +58,15 @@ These are the default settings for the `check_consensus_validator_status` task: maxValidatorBalance: null withdrawalCredsPrefix: "" failOnCheckMiss: false + continueOnPass: false validatorInfoResultVar: "" + validatorPubKeyResultVar: "" ``` + +### Outputs + +- **`validator`**:\ + The validator information object containing status, index, balance, and other data. + +- **`pubkey`**:\ + The validator's public key as a hex string. diff --git a/pkg/tasks/check_consensus_validator_status/config.go b/pkg/tasks/check_consensus_validator_status/config.go index 01c94c1f..d2b19d55 100644 --- a/pkg/tasks/check_consensus_validator_status/config.go +++ b/pkg/tasks/check_consensus_validator_status/config.go @@ -9,6 +9,10 @@ type Config struct { MaxValidatorBalance *uint64 `yaml:"maxValidatorBalance" json:"maxValidatorBalance"` WithdrawalCredsPrefix string `yaml:"withdrawalCredsPrefix" json:"withdrawalCredsPrefix"` FailOnCheckMiss bool `yaml:"failOnCheckMiss" json:"failOnCheckMiss"` + // ContinueOnPass keeps the task running after the check passes. + // When false (default), the task exits immediately on success. + // When true, the task continues monitoring and may report failure if validator status changes. + ContinueOnPass bool `yaml:"continueOnPass" json:"continueOnPass"` ValidatorInfoResultVar string `yaml:"validatorInfoResultVar" json:"validatorInfoResultVar"` ValidatorPubKeyResultVar string `yaml:"validatorPubKeyResultVar" json:"validatorPubKeyResultVar"` diff --git a/pkg/tasks/check_consensus_validator_status/task.go b/pkg/tasks/check_consensus_validator_status/task.go index 61a16e86..22813f18 100644 --- a/pkg/tasks/check_consensus_validator_status/task.go +++ b/pkg/tasks/check_consensus_validator_status/task.go @@ -97,20 +97,26 @@ func (t *Task) Execute(ctx context.Context) error { // load current epoch duties checkCount++ - t.processCheck(checkCount) + + if done, err := t.processCheck(checkCount); done { + return err + } for { select { case <-wallclockEpochSubscription.Channel(): checkCount++ - t.processCheck(checkCount) + + if done, err := t.processCheck(checkCount); done { + return err + } case <-ctx.Done(): return ctx.Err() } } } -func (t *Task) processCheck(checkCount int) { +func (t *Task) processCheck(checkCount int) (bool, error) { checkResult := t.runValidatorStatusCheck() _, epoch, _ := t.ctx.Scheduler.GetServices().ClientPool().GetConsensusPool().GetBlockCache().GetWallclock().Now() @@ -120,12 +126,22 @@ func (t *Task) processCheck(checkCount int) { case checkResult: t.ctx.SetResult(types.TaskResultSuccess) t.ctx.ReportProgress(100, fmt.Sprintf("Validator status check passed at epoch %d", epoch.Number())) + + if !t.config.ContinueOnPass { + return true, nil + } + + return false, nil case t.config.FailOnCheckMiss: t.ctx.SetResult(types.TaskResultFailure) t.ctx.ReportProgress(0, fmt.Sprintf("Validator status check failed at epoch %d", epoch.Number())) + + return true, fmt.Errorf("validator status check failed at epoch %d", epoch.Number()) default: t.ctx.SetResult(types.TaskResultNone) t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for validator status... (attempt %d)", checkCount)) + + return false, nil } } diff --git a/pkg/tasks/check_eth_call/README.md b/pkg/tasks/check_eth_call/README.md index 140f8e50..12f23d1e 100644 --- a/pkg/tasks/check_eth_call/README.md +++ b/pkg/tasks/check_eth_call/README.md @@ -3,36 +3,44 @@ ### Description The `check_eth_call` task verifies the response from an `eth_call` transaction on the Ethereum blockchain. This task is essential for validating contract interactions that do not require gas or change the state of the blockchain but need to be tested for expected outcomes. +#### Task Behavior +- The task makes `eth_call` requests to check contract state. +- By default, the task returns immediately when the expected result is matched. +- Use `continueOnPass: true` to keep monitoring even after success. + ### Configuration Parameters -- **`ethCallData`**: - The data to be sent in the eth_call transaction, encoded as a hex string. This typically includes the function signature and arguments for contract interactions. +- **`ethCallData`**:\ + The data to be sent in the eth_call transaction, encoded as a hex string. This typically includes the function signature and arguments for contract interactions. Default: `"0x"`. + +- **`expectResult`**:\ + The expected result of the `eth_call` transaction, expressed as a hex string. This is the value that the call is expected to return under normal circumstances and makes the task succeed. Default: `""`. -- **`expectResult`**: - The expected result of the `eth_call` transaction, expressed as a hex string. This is the value that the call is expected to return under normal circumstances and makes the task succeed. +- **`ignoreResults`**:\ + An array of results that, if returned from the `eth_call`, should be ignored. This allows the task to be flexible by acknowledging and skipping known but irrelevant results. Default: `[]`. -- **`ignoreResults`**: - An array of results that, if returned from the `eth_call`, should be ignored. This allows the task to be flexible by acknowledging and skipping known but irrelevant results. +- **`callAddress`**:\ + The contract address targeted by the `eth_call`. This should be the address of the contract whose methods are being invoked. Default: `"0x0000000000000000000000000000000000000000"`. -- **`callAddress`**: - The contract address targeted by the `eth_call`. This should be the address of the contract whose methods are being invoked. +- **`blockNumber`**:\ + Specifies the block number at which the state should be queried. A value of `0` typically indicates the latest block. Default: `0`. -- **`blockNumber`**: - Specifies the block number at which the state should be queried. A value of `0` typically indicates the latest block. +- **`failOnMismatch`**:\ + Determines whether the task should fail if the result of the `eth_call` does not match the `expectResult` and is not in the list of `ignoreResults`. If set to `false`, the task will not fail on a result mismatch. Default: `false`. -- **`failOnMismatch`**: - Determines whether the task should fail if the result of the `eth_call` does not match the `expectResult` and is not in the list of `ignoreResults`. If set to `false`, the task will not fail on a result mismatch, allowing further actions or checks to proceed. +- **`clientPattern`**:\ + A regex pattern to select specific client endpoints for sending the `eth_call`. This allows targeting of appropriate nodes within the network. Default: `""`. -- **`clientPattern`**: - A regex pattern to select specific client endpoints for sending the `eth_call`. This allows targeting of appropriate nodes within the network. +- **`excludeClientPattern`**:\ + A regex pattern to exclude certain clients from being used to make the `eth_call`, optimizing the selection of nodes based on the test scenario. Default: `""`. -- **`excludeClientPattern`**: - A regex pattern to exclude certain clients from being used to make the `eth_call`, optimizing the selection of nodes based on the test scenario. +- **`continueOnPass`**:\ + If set to `true`, the task continues monitoring even after the expected result is matched. This is useful for verifying that the contract state remains consistent over time. If `false` (default), the task exits immediately on success. ### Outputs -- **`callResult`**: - The result of the `eth_call` transaction, returned as a hex string. This output provides direct feedback from the contract method being invoked and is crucial for verifying the result with custom logic. +- **`callResult`**:\ + The result of the `eth_call` transaction, returned as a hex string. This output provides direct feedback from the contract method being invoked. ### Defaults @@ -49,4 +57,5 @@ Default settings for the `check_eth_call` task: failOnMismatch: false clientPattern: "" excludeClientPattern: "" + continueOnPass: false ``` diff --git a/pkg/tasks/check_eth_call/config.go b/pkg/tasks/check_eth_call/config.go index 5e4ba374..9c8feb18 100644 --- a/pkg/tasks/check_eth_call/config.go +++ b/pkg/tasks/check_eth_call/config.go @@ -10,6 +10,10 @@ type Config struct { ClientPattern string `yaml:"clientPattern" json:"clientPattern"` ExcludeClientPattern string `yaml:"excludeClientPattern" json:"excludeClientPattern"` + // ContinueOnPass keeps the task running after the check passes. + // When false (default), the task exits immediately on success. + // When true, the task continues monitoring and may report failure if eth_call results change. + ContinueOnPass bool `yaml:"continueOnPass" json:"continueOnPass"` } func DefaultConfig() Config { diff --git a/pkg/tasks/check_eth_call/task.go b/pkg/tasks/check_eth_call/task.go index a5a1d71e..07834d5f 100644 --- a/pkg/tasks/check_eth_call/task.go +++ b/pkg/tasks/check_eth_call/task.go @@ -110,13 +110,17 @@ func (t *Task) Execute(ctx context.Context) error { if latestBlock != nil { if t.config.BlockNumber == 0 { checkCount++ - t.runCheck(ctx, latestBlock.Number, latestBlock, checkCount) + + if done, err := t.runCheck(ctx, latestBlock.Number, latestBlock, checkCount); done { + return err + } } else if latestBlock.Number >= t.config.BlockNumber { // if the block we're looking for already passed, run the check immediately and return checkCount++ - t.runCheck(ctx, t.config.BlockNumber, nil, checkCount) - return nil + _, err := t.runCheck(ctx, t.config.BlockNumber, nil, checkCount) + + return err } } @@ -129,7 +133,9 @@ func (t *Task) Execute(ctx context.Context) error { if t.config.BlockNumber == 0 { // Run the check for all blocks - t.runCheck(ctx, block.Number, block, checkCount) + if done, err := t.runCheck(ctx, block.Number, block, checkCount); done { + return err + } } else if block.Number >= t.config.BlockNumber { // Run the check once for the block we're looking for if block.Number != t.config.BlockNumber { @@ -137,9 +143,9 @@ func (t *Task) Execute(ctx context.Context) error { block = nil } - t.runCheck(ctx, t.config.BlockNumber, block, checkCount) + _, err := t.runCheck(ctx, t.config.BlockNumber, block, checkCount) - return nil + return err } case <-ctx.Done(): return ctx.Err() @@ -147,7 +153,7 @@ func (t *Task) Execute(ctx context.Context) error { } } -func (t *Task) runCheck(ctx context.Context, blockNumber uint64, block *execution.Block, checkCount int) { +func (t *Task) runCheck(ctx context.Context, blockNumber uint64, block *execution.Block, checkCount int) (bool, error) { // Set up the call message address := common.HexToAddress(t.config.CallAddress) callMsg := ðereum.CallMsg{ @@ -167,7 +173,7 @@ func (t *Task) runCheck(ctx context.Context, blockNumber uint64, block *executio t.logger.Error("check failed: no matching clients found") t.ctx.SetResult(types.TaskResultFailure) - return + return true, fmt.Errorf("no matching clients found") } } else { poolClients := clientPool.GetClientsByNamePatterns(t.config.ClientPattern, t.config.ExcludeClientPattern) @@ -175,7 +181,7 @@ func (t *Task) runCheck(ctx context.Context, blockNumber uint64, block *executio t.logger.Error("check failed: no matching clients found with pattern %v", t.config.ClientPattern) t.ctx.SetResult(types.TaskResultFailure) - return + return true, fmt.Errorf("no matching clients found with pattern %v", t.config.ClientPattern) } clients = make([]*execution.Client, len(poolClients)) @@ -212,7 +218,7 @@ func (t *Task) runCheck(ctx context.Context, blockNumber uint64, block *executio t.logger.WithField("client", client.GetName()).Errorf("RPC error when sending eth_call %v: %v", callMsg, err) t.ctx.SetResult(types.TaskResultFailure) - return + return true, fmt.Errorf("RPC error when sending eth_call: %w", err) } ignoreResult := t.ignoreResult(fetchedResult) @@ -230,12 +236,14 @@ func (t *Task) runCheck(ctx context.Context, blockNumber uint64, block *executio if t.config.FailOnMismatch && !ignoreResult { t.ctx.SetResult(types.TaskResultFailure) - } else { - t.ctx.SetResult(types.TaskResultNone) - t.ctx.ReportProgress(0, fmt.Sprintf("result mismatch (attempt %d)", checkCount)) + + return true, fmt.Errorf("eth_call results mismatch against other client") } - return + t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("result mismatch (attempt %d)", checkCount)) + + return false, nil } // Check if the fetched result is the expected result @@ -250,12 +258,14 @@ func (t *Task) runCheck(ctx context.Context, blockNumber uint64, block *executio if t.config.FailOnMismatch && !ignoreResult { t.ctx.SetResult(types.TaskResultFailure) - } else { - t.ctx.SetResult(types.TaskResultNone) - t.ctx.ReportProgress(0, fmt.Sprintf("expected result mismatch (attempt %d)", checkCount)) + + return true, fmt.Errorf("eth_call results mismatch against expected result") } - return + t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("expected result mismatch (attempt %d)", checkCount)) + + return false, nil } } @@ -267,7 +277,13 @@ func (t *Task) runCheck(ctx context.Context, blockNumber uint64, block *executio if checkedClients > 0 { t.ctx.SetResult(types.TaskResultSuccess) t.ctx.ReportProgress(100, fmt.Sprintf("eth_call successful on %d clients", checkedClients)) + + if !t.config.ContinueOnPass { + return true, nil + } } + + return false, nil } func (t *Task) ignoreResult(result []byte) bool { diff --git a/pkg/tasks/check_execution_sync_status/README.md b/pkg/tasks/check_execution_sync_status/README.md index 52cd66fa..22066896 100644 --- a/pkg/tasks/check_execution_sync_status/README.md +++ b/pkg/tasks/check_execution_sync_status/README.md @@ -3,28 +3,36 @@ ### Description The `check_execution_sync_status` task checks the synchronization status of execution clients in the blockchain network. It ensures that these clients are syncing correctly with the network's current state. +#### Task Behavior +- The task polls execution clients at regular intervals to check their sync status. +- By default, the task returns immediately when the sync criteria are met. +- Use `continueOnPass: true` to keep monitoring even after success. + ### Configuration Parameters - **`clientPattern`**:\ A regular expression pattern used to specify which clients to check. This allows for targeted health checks of specific clients or groups of clients within the network. A blank pattern targets all clients. - **`pollInterval`**:\ - The interval at which the task checks the clients' sync status. This defines the frequency of the synchronization checks. + The interval at which the task checks the clients' sync status. This defines the frequency of the synchronization checks. Default: `5s`. - **`expectSyncing`**:\ - Set this to `true` if the clients are expected to be in a syncing state. If `false`, the task expects the clients to be fully synced. + Set this to `true` if the clients are expected to be in a syncing state. If `false`, the task expects the clients to be fully synced. Default: `false`. - **`expectMinPercent`**:\ - The minimum expected percentage of synchronization. Clients should be synced at least to this level for the task to succeed. + The minimum expected percentage of synchronization. Clients should be synced at least to this level for the task to succeed. Default: `100`. - **`expectMaxPercent`**:\ - The maximum allowable percentage of synchronization. Clients should not be synced beyond this level for the task to pass. + The maximum allowable percentage of synchronization. Clients should not be synced beyond this level for the task to pass. Default: `100`. - **`minBlockHeight`**:\ - The minimum block height that the clients should be synced to. This sets a specific block height requirement for the task. + The minimum block height that the clients should be synced to. This sets a specific block height requirement for the task. Default: `10`. - **`waitForChainProgression`**:\ - If `true`, the task checks for blockchain progression in addition to the synchronization status. If `false`, it only checks for synchronization without waiting for further chain progression. + If `true`, the task checks for blockchain progression in addition to the synchronization status. If `false`, it only checks for synchronization without waiting for further chain progression. Default: `false`. + +- **`continueOnPass`**:\ + If set to `true`, the task continues monitoring sync status even after the criteria are met. This is useful when running concurrently with other tasks. If `false` (default), the task exits immediately on success. ### Defaults @@ -40,4 +48,9 @@ These are the default settings for the `check_execution_sync_status` task: expectMaxPercent: 100 minBlockHeight: 10 waitForChainProgression: false + continueOnPass: false ``` + +### Outputs + +This task does not produce any outputs. diff --git a/pkg/tasks/check_execution_sync_status/config.go b/pkg/tasks/check_execution_sync_status/config.go index 6e1158ec..43a125df 100644 --- a/pkg/tasks/check_execution_sync_status/config.go +++ b/pkg/tasks/check_execution_sync_status/config.go @@ -15,6 +15,10 @@ type Config struct { ExpectMaxPercent float64 `yaml:"expectMaxPercent" json:"expectMaxPercent"` MinBlockHeight int `yaml:"minBlockHeight" json:"minBlockHeight"` WaitForChainProgression bool `yaml:"waitForChainProgression" json:"waitForChainProgression"` + // ContinueOnPass keeps the task running after the check passes. + // When false (default), the task exits immediately on success. + // When true, the task continues monitoring and may report failure if sync status changes. + ContinueOnPass bool `yaml:"continueOnPass" json:"continueOnPass"` } func DefaultConfig() Config { diff --git a/pkg/tasks/check_execution_sync_status/task.go b/pkg/tasks/check_execution_sync_status/task.go index 5f305e85..1c5727c2 100644 --- a/pkg/tasks/check_execution_sync_status/task.go +++ b/pkg/tasks/check_execution_sync_status/task.go @@ -98,17 +98,20 @@ func (t *Task) Execute(ctx context.Context) error { for { checkCount++ - t.processCheck(ctx, checkCount) + + if done := t.processCheck(ctx, checkCount); done { + return nil + } select { case <-time.After(t.config.PollInterval.Duration): case <-ctx.Done(): - return nil + return ctx.Err() } } } -func (t *Task) processCheck(ctx context.Context, checkCount int) { +func (t *Task) processCheck(ctx context.Context, checkCount int) bool { allResultsPass := true goodClients := []*ClientInfo{} failedClients := []*ClientInfo{} @@ -121,7 +124,7 @@ func (t *Task) processCheck(ctx context.Context, checkCount int) { syncStatus, err := client.ExecutionClient.GetRPCClient().GetNodeSyncing(ctx) if ctx.Err() != nil { - return + return false } if err != nil { @@ -159,10 +162,14 @@ func (t *Task) processCheck(ctx context.Context, checkCount int) { if allResultsPass { t.ctx.SetResult(types.TaskResultSuccess) t.ctx.ReportProgress(100, fmt.Sprintf("All clients synced: %d", len(goodClients))) - } else { - t.ctx.SetResult(types.TaskResultNone) - t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for sync... %d/%d (attempt %d)", len(goodClients), len(goodClients)+len(failedClients), checkCount)) + + return !t.config.ContinueOnPass } + + t.ctx.SetResult(types.TaskResultNone) + t.ctx.ReportProgress(0, fmt.Sprintf("Waiting for sync... %d/%d (attempt %d)", len(goodClients), len(goodClients)+len(failedClients), checkCount)) + + return false } func (t *Task) processClientCheck(client *clients.PoolClient, syncStatus *rpc.SyncStatus, checkLogger logrus.FieldLogger) bool { diff --git a/pkg/tasks/run_task_background/README.md b/pkg/tasks/run_task_background/README.md index 2377838d..9add2cf9 100644 --- a/pkg/tasks/run_task_background/README.md +++ b/pkg/tasks/run_task_background/README.md @@ -19,22 +19,22 @@ The `run_task_background` task facilitates the concurrent execution of a foregro - **`backgroundTask`**:\ The task that runs in the background concurrently with the foreground task. It is also defined following the standard task definition format. +- **`newVariableScope`**:\ + Determines if a new variable scope should be created for the foreground task. If `false`, the current scope is passed through. The background task always operates in a new variable scope, which inherits from the parent but does not propagate changes upwards. Default: `false`. + - **`exitOnForegroundSuccess`**:\ - If set to `true`, the `run_task_background` task will exit with a success result when the foreground task's result is set to "success". Note that this does not necessarily mean the foreground task has completed. If still running, both the background and foreground tasks will be cancelled. + If set to `true`, the `run_task_background` task will exit with a success result when the foreground task's result is set to "success". Note that this does not necessarily mean the foreground task has completed. If still running, both the background and foreground tasks will be cancelled. Default: `false`. - **`exitOnForegroundFailure`**:\ - If `true`, the task exits with a failure result when the foreground task's result is set to "failure". This does not imply the foreground task's completion. Both the background and foreground tasks will be cancelled if they are still running. + If `true`, the task exits with a failure result when the foreground task's result is set to "failure". This does not imply the foreground task's completion. Both the background and foreground tasks will be cancelled if they are still running. Default: `false`. - **`onBackgroundComplete`**:\ - Specifies the action to take when the background task completes. Options are: - - `ignore`: No action is taken. + Specifies the action to take when the background task completes first. Options are: + - `ignore` (default): No action is taken. - `fail`: Exits the task with a failure result. - - `succeed`: Exits the task with a success result. + - `succeed` / `success`: Exits the task with a success result. - `failOrIgnore`: Exits with a failure result if the background task fails, otherwise no action is taken. -- **`newVariableScope`**:\ - Determines if a new variable scope should be created for the foreground task. If `false`, the current scope is passed through. The background task always operates in a new variable scope, which inherits from the parent but does not propagate changes upwards. - ### Defaults Default settings for the `run_task_background` task: @@ -44,8 +44,12 @@ Default settings for the `run_task_background` task: config: foregroundTask: {} backgroundTask: {} + newVariableScope: false exitOnForegroundSuccess: false exitOnForegroundFailure: false onBackgroundComplete: "ignore" - newVariableScope: false ``` + +### Outputs + +This task does not produce any outputs. diff --git a/pkg/tasks/run_task_background/config.go b/pkg/tasks/run_task_background/config.go index cfcd1496..b2b141f1 100644 --- a/pkg/tasks/run_task_background/config.go +++ b/pkg/tasks/run_task_background/config.go @@ -7,20 +7,21 @@ import ( ) type Config struct { - ForegroundTask *helper.RawMessageMasked `yaml:"foregroundTask" json:"foregroundTask"` - BackgroundTask *helper.RawMessageMasked `yaml:"backgroundTask" json:"backgroundTask"` + ForegroundTask *helper.RawMessageMasked `yaml:"foregroundTask" json:"foregroundTask"` + BackgroundTask *helper.RawMessageMasked `yaml:"backgroundTask" json:"backgroundTask"` + NewVariableScope bool `yaml:"newVariableScope" json:"newVariableScope"` + // When to complete (based on foreground task result) + // These allow early exit even if foreground task hasn't returned yet ExitOnForegroundSuccess bool `yaml:"exitOnForegroundSuccess" json:"exitOnForegroundSuccess"` ExitOnForegroundFailure bool `yaml:"exitOnForegroundFailure" json:"exitOnForegroundFailure"` - // action when background task stops - // "ignore" - do nothing (default) + // What happens if background task completes first + // "ignore" (default) - do nothing // "fail" - exit with failure - // "succeed" - exit with success + // "succeed" / "success" - exit with success // "failOrIgnore" - exit with failure if background task failed, ignore on success OnBackgroundComplete string `yaml:"onBackgroundComplete" json:"onBackgroundComplete"` - - NewVariableScope bool `yaml:"newVariableScope" json:"newVariableScope"` } func DefaultConfig() Config { diff --git a/pkg/tasks/run_task_matrix/README.md b/pkg/tasks/run_task_matrix/README.md index ee0a5fc7..8ddf12a3 100644 --- a/pkg/tasks/run_task_matrix/README.md +++ b/pkg/tasks/run_task_matrix/README.md @@ -3,28 +3,40 @@ ### Description The `run_task_matrix` task is designed to execute a specified task multiple times, each with different input values drawn from an array. This task is ideal for scenarios where you need to test a task under various conditions or with different sets of data. -### Configuration Parameters - -- **`runConcurrent`**:\ - Determines whether the child tasks (instances of the task being run for each matrix value) should run concurrently or sequentially. If `true`, all tasks run at the same time; if `false`, they run one after the other. +#### Task Behavior +- Creates one instance of the child task for each value in `matrixValues`. +- Tasks can run sequentially (default) or concurrently with `runConcurrent: true`. +- By default, waits for all task instances to complete. +- The result is failure if any instance fails, success if all succeed. -- **`succeedTaskCount`**:\ - The number of child tasks that need to succeed (result status "success") for the `run_task_matrix` task to stop and return a success result. A value of 0 means all child tasks need to succeed for the overall task to be considered successful. +### Configuration Parameters -- **`failTaskCount`**:\ - The number of child tasks that may to fail (result status "failure") before the `run_task_matrix` task to stops and returns a failure result. A value of 0 means that the appearance of any failure in child tasks will cause the overall task to fail. +- **`task`**:\ + The definition of the task to be executed for each matrix value. This task is run repeatedly, once for each value in the `matrixValues` array, with the current value made accessible via the variable named in `matrixVar`. -- **`failOnUndecided`**:\ - If set to true, the `run_task_matrix` task will fail if neither the `succeedTaskCount` nor the `failTaskCount` is reached. +- **`matrixVar`**:\ + The name of the variable to which the current matrix value is assigned for each child task. This allows the child task to access and use the specific value from the matrix. - **`matrixValues`**:\ An array of values that form the matrix. Each value in this array is used to run the child task with a different input. -- **`matrixVar`**:\ - The name of the variable to which the current matrix value is assigned for each child task. This allows the child task to access and use the specific value from the matrix. +- **`runConcurrent`**:\ + Determines whether the child tasks should run concurrently or sequentially. If `true`, all tasks run at the same time; if `false` (default), they run one after the other. -- **`task`**:\ - The definition of the task to be executed for each matrix value. This task is run repeatedly, once for each value in the `matrixValues` array, with the current value made accessible via the variable named in `matrixVar`. +- **`successThreshold`**:\ + The number of child tasks that need to succeed for the overall task to be considered successful. A value of `0` (default) means all child tasks must succeed. + +- **`failureThreshold`**:\ + The number of child tasks that need to fail before the overall task is considered failed. Default: `1` (any single failure causes overall failure). + +- **`stopOnThreshold`**:\ + If set to `true`, remaining child tasks are cancelled when either the success or failure threshold is reached. If `false` (default), the task waits for all children to complete. + +- **`invertResult`**:\ + If set to `true`, the final result is inverted: success becomes failure and failure becomes success. Default: `false`. + +- **`ignoreResult`**:\ + If set to `true`, the task always returns success regardless of child task outcomes. Default: `false`. ### Defaults @@ -33,12 +45,17 @@ Default settings for the `run_task_matrix` task: ```yaml - name: run_task_matrix config: - runConcurrent: false - succeedTaskCount: 0 - failTaskCount: 0 - failOnUndecided: true - matrixValues: [] - matrixVar: "" task: {} + matrixVar: "" + matrixValues: [] + runConcurrent: false + successThreshold: 0 + failureThreshold: 1 + stopOnThreshold: false + invertResult: false + ignoreResult: false ``` +### Outputs + +This task does not produce any outputs. diff --git a/pkg/tasks/run_task_matrix/config.go b/pkg/tasks/run_task_matrix/config.go index 79940b90..b669b56f 100644 --- a/pkg/tasks/run_task_matrix/config.go +++ b/pkg/tasks/run_task_matrix/config.go @@ -7,31 +7,32 @@ import ( ) type Config struct { - // matrix variable name - RunConcurrent bool `yaml:"runConcurrent" json:"runConcurrent"` - - // number of successful child tasks to make this task succeed (0 = all tasks) - SucceedTaskCount uint64 `yaml:"succeedTaskCount" json:"succeedTaskCount"` + Task *helper.RawMessageMasked `yaml:"task" json:"task"` + MatrixVar string `yaml:"matrixVar" json:"matrixVar"` + MatrixValues []any `yaml:"matrixValues" json:"matrixValues"` - // number of failed child tasks to make this task fail (0 = all tasks) - FailTaskCount uint64 `yaml:"failTaskCount" json:"failTaskCount"` - - // fail task if neither succeedTaskCount nor failTaskCount is reached, but all tasks completed - FailOnUndecided bool `yaml:"failOnUndecided" json:"failOnUndecided"` + // Whether to run tasks concurrently (default: false - sequential) + RunConcurrent bool `yaml:"runConcurrent" json:"runConcurrent"` - // matrix variable name - MatrixValues []interface{} `yaml:"matrixValues" json:"matrixValues"` + // Threshold behavior: + // - 0 (default): No threshold - only evaluate result when ALL tasks complete + // - >0: Set result when threshold is reached (but continue until all complete unless StopOnThreshold=true) + SuccessThreshold uint64 `yaml:"successThreshold" json:"successThreshold"` + FailureThreshold uint64 `yaml:"failureThreshold" json:"failureThreshold"` - // matrix variable name - MatrixVar string `yaml:"matrixVar" json:"matrixVar"` + // Early termination - if true, stop immediately when a threshold is reached + // Default: false - always wait for all tasks to complete + StopOnThreshold bool `yaml:"stopOnThreshold" json:"stopOnThreshold"` - // child task - Task *helper.RawMessageMasked `yaml:"task" json:"task"` + // Result transformation + InvertResult bool `yaml:"invertResult" json:"invertResult"` + IgnoreResult bool `yaml:"ignoreResult" json:"ignoreResult"` } func DefaultConfig() Config { return Config{ - FailOnUndecided: true, + FailureThreshold: 1, + StopOnThreshold: true, } } diff --git a/pkg/tasks/run_task_matrix/task.go b/pkg/tasks/run_task_matrix/task.go index 04281a93..1be318eb 100644 --- a/pkg/tasks/run_task_matrix/task.go +++ b/pkg/tasks/run_task_matrix/task.go @@ -173,15 +173,10 @@ func (t *Task) Execute(ctx context.Context) error { }() // watch result updates - successLimit := t.config.SucceedTaskCount - if successLimit == 0 { - successLimit = uint64(len(t.tasks)) - } - - failureLimit := t.config.FailTaskCount - if failureLimit == 0 { - failureLimit = uint64(len(t.tasks)) - } + // When threshold is 0, don't apply threshold logic - only evaluate at completion + // When threshold > 0, use that value as the limit + successLimit := t.config.SuccessThreshold + failureLimit := t.config.FailureThreshold var successCount, failureCount, pendingCount uint64 @@ -211,18 +206,24 @@ func (t *Task) Execute(ctx context.Context) error { } } - if successCount >= successLimit { - t.logger.Infof("success limit reached (%v success, %v failure)", successCount, failureCount) + // Only check success threshold if explicitly configured (> 0) + if successLimit > 0 && successCount >= successLimit { + t.logger.Infof("success threshold reached (%v success, %v failure)", successCount, failureCount) t.ctx.SetResult(types.TaskResultSuccess) - taskComplete = true + if t.config.StopOnThreshold { + taskComplete = true + } } - if !taskComplete && failureCount >= failureLimit { - t.logger.Infof("failure limit reached (%v success, %v failure)", successCount, failureCount) + // Only check failure threshold if explicitly configured (> 0) + if !taskComplete && failureLimit > 0 && failureCount >= failureLimit { + t.logger.Infof("failure threshold reached (%v success, %v failure)", successCount, failureCount) t.ctx.SetResult(types.TaskResultFailure) - taskComplete = true + if t.config.StopOnThreshold { + taskComplete = true + } } if !taskComplete { @@ -238,7 +239,9 @@ func (t *Task) Execute(ctx context.Context) error { if !taskComplete { taskComplete = true - if t.config.FailOnUndecided && len(t.tasks) > 0 { + // All tasks completed - determine final result + // If any task failed, the result is failure; otherwise success + if failureCount > 0 { t.ctx.SetResult(types.TaskResultFailure) } else { t.ctx.SetResult(types.TaskResultSuccess) @@ -256,6 +259,28 @@ func (t *Task) Execute(ctx context.Context) error { cancelFn() taskWaitGroup.Wait() + // Apply result transformation + taskResult := t.ctx.Scheduler.GetTaskState(t.ctx.Index).GetTaskStatus().Result + + if t.config.IgnoreResult { + return nil + } + + if t.config.InvertResult { + if taskResult == types.TaskResultFailure { + t.ctx.SetResult(types.TaskResultSuccess) + return nil + } + + t.ctx.SetResult(types.TaskResultFailure) + + return fmt.Errorf("all tasks succeeded, but failure was expected") + } + + if taskResult == types.TaskResultFailure { + return fmt.Errorf("matrix task execution failed (%d failures)", failureCount) + } + return nil } diff --git a/pkg/tasks/run_task_options/README.md b/pkg/tasks/run_task_options/README.md index b9b1a68d..2682cdcc 100644 --- a/pkg/tasks/run_task_options/README.md +++ b/pkg/tasks/run_task_options/README.md @@ -3,34 +3,33 @@ ### Description The `run_task_options` task is designed to execute a single task with configurable behaviors and response actions. This flexibility allows for precise control over how the task's outcome is handled and how it interacts with the overall test environment. +#### Task Behavior +- Executes the child task and waits for it to complete naturally. +- Supports retry logic for failed tasks. +- Can transform the result (invert, ignore, expect failure). + ### Configuration Parameters - **`task`**:\ The task to be executed. This is defined following the standard task definition format. -- **`propagateResult`**:\ - This setting controls how the result of the child task influences the result of the `run_task_options` task. If set to `true`, any change in the result of the child task (success or failure) is immediately reflected in the result of the parent `run_task_options` task. If `false`, the child task's result is only propagated to the parent task after the child task has completed its execution. +- **`retryOnFailure`**:\ + If set to `true`, the task will retry the execution of the child task if it fails, up to the maximum number of retries specified by `maxRetryCount`. Default: `false`. -- **`exitOnResult`**:\ - If set to `true`, the task will cancel the child task as soon as it sets a result, whether it is "success" or "failure." This option is useful for scenarios where immediate response to the child task's result is necessary. +- **`maxRetryCount`**:\ + The maximum number of times the child task will be retried if it fails and `retryOnFailure` is true. Default: `0` (no retries). - **`invertResult`**:\ - When `true`, the result of the child task is inverted. This means the `run_task_options` task will fail if the child task succeeds and succeed if the child task fails. This can be used to validate negative test scenarios. + When `true`, the result of the child task is inverted. This means the `run_task_options` task will fail if the child task succeeds and succeed if the child task fails. This can be used to validate negative test scenarios. Default: `false`. - **`expectFailure`**:\ - If set to `true`, this option expects the child task to fail. The `run_task_options` task will fail if the child task does not end with a "failure" result, ensuring that failure scenarios are handled as expected. - -- **`ignoreFailure`**:\ - When `true`, any failure result from the child task is ignored, and the `run_task_options` task will return a success result instead. This is useful for cases where the child task's failure is an acceptable outcome. + Alias for `invertResult`. If set to `true`, this option expects the child task to fail. The `run_task_options` task will fail if the child task does not end with a "failure" result. Default: `false`. -- **`retryOnFailure`**:\ - If set to `true`, the task will retry the execution of the child task if it fails, up to the maximum number of retries specified by `maxRetryCount`. - -- **`maxRetryCount`**:\ - The maximum number of times the child task will be retried if it fails and `retryOnFailure` is true. A value of 0 means no retries. +- **`ignoreResult`**:\ + When `true`, any failure result from the child task is ignored, and the `run_task_options` task will return a success result instead. This is useful for cases where the child task's failure is an acceptable outcome. Default: `false`. - **`newVariableScope`**:\ - Determines whether to create a new variable scope for the child task. If `false`, the current scope is passed through, allowing the child task to share the same variable context as the `run_task_options` task. + Determines whether to create a new variable scope for the child task. If `false`, the current scope is passed through, allowing the child task to share the same variable context as the `run_task_options` task. Default: `false`. ### Defaults @@ -40,12 +39,14 @@ Default settings for the `run_task_options` task: - name: run_task_options config: task: null - propagateResult: false - exitOnResult: false - invertResult: false - expectFailure: false - ignoreFailure: false retryOnFailure: false maxRetryCount: 0 + invertResult: false + expectFailure: false + ignoreResult: false newVariableScope: false ``` + +### Outputs + +This task does not produce any outputs. diff --git a/pkg/tasks/run_task_options/config.go b/pkg/tasks/run_task_options/config.go index 448a0ce8..f8d77f38 100644 --- a/pkg/tasks/run_task_options/config.go +++ b/pkg/tasks/run_task_options/config.go @@ -7,16 +7,17 @@ import ( ) type Config struct { - Task *helper.RawMessageMasked `yaml:"task" json:"tasks"` - - PropagateResult bool `yaml:"propagateResult" json:"propagateResult"` - ExitOnResult bool `yaml:"exitOnResult" json:"exitOnResult"` - InvertResult bool `yaml:"invertResult" json:"invertResult"` - ExpectFailure bool `yaml:"expectFailure" json:"expectFailure"` - IgnoreFailure bool `yaml:"ignoreFailure" json:"ignoreFailure"` - RetryOnFailure bool `yaml:"retryOnFailure" json:"retryOnFailure"` - MaxRetryCount uint `yaml:"maxRetryCount" json:"maxRetryCount"` - NewVariableScope bool `yaml:"newVariableScope" json:"newVariableScope"` + Task *helper.RawMessageMasked `yaml:"task" json:"task"` + NewVariableScope bool `yaml:"newVariableScope" json:"newVariableScope"` + + // Retry behavior + RetryOnFailure bool `yaml:"retryOnFailure" json:"retryOnFailure"` + MaxRetryCount uint `yaml:"maxRetryCount" json:"maxRetryCount"` + + // Result transformation + InvertResult bool `yaml:"invertResult" json:"invertResult"` + IgnoreResult bool `yaml:"ignoreResult" json:"ignoreResult"` + ExpectFailure bool `yaml:"expectFailure" json:"expectFailure"` // Alias for invertResult } func DefaultConfig() Config { diff --git a/pkg/tasks/run_task_options/task.go b/pkg/tasks/run_task_options/task.go index d3144c80..390be2a0 100644 --- a/pkg/tasks/run_task_options/task.go +++ b/pkg/tasks/run_task_options/task.go @@ -98,39 +98,17 @@ func (t *Task) Execute(ctx context.Context) error { return fmt.Errorf("failed initializing child task: %w", err) } - // execute task - taskErr = t.ctx.Scheduler.ExecuteTask(ctx, t.task, func(ctx context.Context, cancelFn context.CancelFunc, _ types.TaskIndex) { - t.watchTaskResult(ctx, cancelFn) - }) - - switch { - case t.config.RetryOnFailure && retryCount < t.config.MaxRetryCount: - if taskErr != nil { - retryCount++ - - t.logger.Warnf("child task failed: %w (retrying)", taskErr) - t.ctx.ReportProgress(0, fmt.Sprintf("Retrying child task (attempt %d/%d)...", retryCount+1, t.config.MaxRetryCount+1)) - - continue - } - case t.config.ExpectFailure: - if taskErr == nil { - t.ctx.SetResult(types.TaskResultFailure) - return fmt.Errorf("child task succeeded, but should have failed") - } - - t.ctx.SetResult(types.TaskResultSuccess) - case t.config.IgnoreFailure: - if taskErr != nil { - t.logger.Warnf("child task failed: %w", taskErr) - } - - t.ctx.SetResult(types.TaskResultSuccess) - default: - if taskErr != nil { - t.ctx.SetResult(types.TaskResultFailure) - return fmt.Errorf("child task failed: %w", taskErr) - } + // execute task (tasks self-complete now) + taskErr = t.ctx.Scheduler.ExecuteTask(ctx, t.task, nil) + + // Handle retry logic + if t.config.RetryOnFailure && taskErr != nil && retryCount < t.config.MaxRetryCount { + retryCount++ + + t.logger.Warnf("child task failed: %v (retrying)", taskErr) + t.ctx.ReportProgress(0, fmt.Sprintf("Retrying child task (attempt %d/%d)...", retryCount+1, t.config.MaxRetryCount+1)) + + continue } break @@ -138,51 +116,19 @@ func (t *Task) Execute(ctx context.Context) error { t.ctx.ReportProgress(100, "Task completed") - return taskErr -} - -func (t *Task) watchTaskResult(ctx context.Context, cancelFn context.CancelFunc) { - taskState := t.ctx.Scheduler.GetTaskState(t.task) - currentResult := types.TaskResultNone - - for { - updateChan := taskState.GetTaskResultUpdateChan(currentResult) - if updateChan != nil { - select { - case <-ctx.Done(): - return - case <-updateChan: - } - } - - taskStatus := taskState.GetTaskStatus() - if taskStatus.Result == currentResult { - continue - } - - currentResult = taskStatus.Result - - taskResult := currentResult - if t.config.InvertResult { - switch taskResult { - case types.TaskResultNone: - taskResult = types.TaskResultSuccess - case types.TaskResultSuccess: - taskResult = types.TaskResultNone - case types.TaskResultFailure: - if t.config.ExpectFailure || t.config.IgnoreFailure { - taskResult = types.TaskResultSuccess - } - } - } + // Apply result transformation + if t.config.IgnoreResult { + return nil + } - if t.config.PropagateResult { - t.ctx.SetResult(taskResult) + // ExpectFailure is an alias for InvertResult + if t.config.ExpectFailure || t.config.InvertResult { + if taskErr != nil { + return nil } - if t.config.ExitOnResult { - cancelFn() - return - } + return fmt.Errorf("child task succeeded, but failure was expected") } + + return taskErr } diff --git a/pkg/tasks/run_tasks/README.md b/pkg/tasks/run_tasks/README.md index e2088549..59dd281a 100644 --- a/pkg/tasks/run_tasks/README.md +++ b/pkg/tasks/run_tasks/README.md @@ -5,27 +5,26 @@ The `run_tasks` task executes a series of specified tasks sequentially. This is #### Task Behavior - The task starts the child tasks one after the other in the order they are listed. -- It continuously monitors the result of the currently running child task. As soon as the child task returns a "success" or "failure" result, the execution of that task is stopped. -- After cancelling the current task, the `run_tasks` task then initiates the next task in the sequence. - -An important aspect of this task is that it cancels tasks once they return a result. This is particularly significant for check tasks, which, by their nature, would continue running indefinitely according to their logic. In this sequential setup, however, they are stopped once they achieve a result, allowing the sequence to proceed. +- Each child task runs until it completes naturally (returns success or failure). +- After a child task completes, the `run_tasks` task initiates the next task in the sequence. +- By default, the sequence stops if any child task fails. Use `continueOnFailure` to continue despite failures. ### Configuration Parameters - **`tasks`**:\ An array of tasks to be executed one after the other. Each task is defined according to the standard task structure. -- **`stopChildOnResult`**:\ - If set to `true`, each child task in the sequence is stopped as soon as it sets a result (either "success" or "failure"). This ensures that once a task has reached a outcome, it does not continue to run unnecessarily, allowing the next task in the sequence to commence. +- **`continueOnFailure`**:\ + When `true`, the sequence of tasks continues even if individual tasks fail, allowing the entire sequence to be executed regardless of individual task outcomes. Default: `false`. -- **`expectFailure`**:\ - If set to `true`, this option expects each task in the sequence to fail. The task sequence stops with a "failure" result if any task does not fail as expected. +- **`invertResult`**:\ + If set to `true`, the final result is inverted: success becomes failure and failure becomes success. Useful when you expect all tasks to fail. Default: `false`. -- **`continueOnFailure`**:\ - When `true`, the sequence of tasks continues even if individual tasks fail, allowing the entire sequence to be executed regardless of individual task outcomes. +- **`ignoreResult`**:\ + If set to `true`, the task always returns success regardless of child task outcomes. Default: `false`. - **`newVariableScope`**:\ - Determines whether to create a new variable scope for the child tasks. If `false`, the current scope is passed through, allowing the child tasks to share the same variable context as the `run_tasks` task. + Determines whether to create a new variable scope for the child tasks. If `false`, the current scope is passed through, allowing the child tasks to share the same variable context as the `run_tasks` task. Default: `false`. ### Defaults @@ -35,8 +34,12 @@ Default settings for the `run_tasks` task: - name: run_tasks config: tasks: [] - stopChildOnResult: true - expectFailure: false continueOnFailure: false + invertResult: false + ignoreResult: false newVariableScope: false ``` + +### Outputs + +This task does not produce any outputs. diff --git a/pkg/tasks/run_tasks/config.go b/pkg/tasks/run_tasks/config.go index bfca3d67..102287a0 100644 --- a/pkg/tasks/run_tasks/config.go +++ b/pkg/tasks/run_tasks/config.go @@ -7,18 +7,20 @@ import ( ) type Config struct { - Tasks []helper.RawMessageMasked `yaml:"tasks" json:"tasks"` + Tasks []helper.RawMessageMasked `yaml:"tasks" json:"tasks"` + NewVariableScope bool `yaml:"newVariableScope" json:"newVariableScope"` - StopChildOnResult bool `yaml:"stopChildOnResult" json:"stopChildOnResult"` - ExpectFailure bool `yaml:"expectFailure" json:"expectFailure"` + // Failure handling (default: stop on first failure) ContinueOnFailure bool `yaml:"continueOnFailure" json:"continueOnFailure"` - NewVariableScope bool `yaml:"newVariableScope" json:"newVariableScope"` + + // Result transformation + InvertResult bool `yaml:"invertResult" json:"invertResult"` // Swap success/failure + IgnoreResult bool `yaml:"ignoreResult" json:"ignoreResult"` // Always succeed } func DefaultConfig() Config { return Config{ - Tasks: []helper.RawMessageMasked{}, - StopChildOnResult: true, + Tasks: []helper.RawMessageMasked{}, } } diff --git a/pkg/tasks/run_tasks/task.go b/pkg/tasks/run_tasks/task.go index 9055f10d..f3ab5a23 100644 --- a/pkg/tasks/run_tasks/task.go +++ b/pkg/tasks/run_tasks/task.go @@ -100,25 +100,17 @@ func (t *Task) LoadConfig() error { func (t *Task) Execute(ctx context.Context) error { totalTasks := len(t.tasks) + var taskErr error + for i, task := range t.tasks { - err := t.ctx.Scheduler.ExecuteTask(ctx, task, func(ctx context.Context, cancelFn context.CancelFunc, task types.TaskIndex) { - if t.config.StopChildOnResult { - t.ctx.Scheduler.WatchTaskPass(ctx, cancelFn, task) - } - }) + err := t.ctx.Scheduler.ExecuteTask(ctx, task, nil) - switch { - case t.config.ExpectFailure: - if err == nil { - return fmt.Errorf("child task #%v succeeded, but should have failed", i+1) - } - case t.config.ContinueOnFailure: - if err != nil { - t.logger.Warnf("child task #%v failed: %w", i+1, err) - } - default: - if err != nil { - return fmt.Errorf("child task #%v failed: %w", i+1, err) + if err != nil { + if t.config.ContinueOnFailure { + t.logger.Warnf("child task #%v failed: %v", i+1, err) + } else { + taskErr = fmt.Errorf("child task #%v failed: %w", i+1, err) + break } } @@ -128,5 +120,18 @@ func (t *Task) Execute(ctx context.Context) error { t.ctx.ReportProgress(progress, fmt.Sprintf("Task %d/%d completed", completedTasks, totalTasks)) } - return nil + // Apply result transformation + if t.config.IgnoreResult { + return nil + } + + if t.config.InvertResult { + if taskErr != nil { + return nil + } + + return fmt.Errorf("all tasks succeeded, but failure was expected") + } + + return taskErr } diff --git a/pkg/tasks/run_tasks_concurrent/README.md b/pkg/tasks/run_tasks_concurrent/README.md index 17bdccba..8e83c1f9 100644 --- a/pkg/tasks/run_tasks_concurrent/README.md +++ b/pkg/tasks/run_tasks_concurrent/README.md @@ -3,22 +3,34 @@ ### Description The `run_tasks_concurrent` task allows for the parallel execution of multiple tasks. This task is crucial in scenarios where tasks need to be run simultaneously, such as in testing environments that require concurrent processes or operations. +#### Task Behavior +- All child tasks are started concurrently. +- By default, the task waits for all children to complete. +- The result is failure if any child task fails, success if all succeed. +- Use `stopOnThreshold` to cancel remaining tasks when success/failure threshold is reached. + ### Configuration Parameters -- **`succeedTaskCount`**:\ - The minimum number of child tasks that need to complete with a "success" result for the `run_tasks_concurrent` task to stop and return a success result. A value of 0 indicates that all child tasks need to succeed for the overall task to be considered successful. +- **`tasks`**:\ + An array of child tasks to be executed concurrently. Each task in this array should be defined according to the standard task structure. -- **`failTaskCount`**:\ - The minimum number of child tasks that need to complete with a "failure" result for the `run_tasks_concurrent` task to stop and return a failure result. A value of 1 means the overall task will fail as soon as one child task fails. +- **`successThreshold`**:\ + The minimum number of child tasks that need to complete with a "success" result for the task to be considered successful. A value of `0` (default) means all child tasks must succeed. -- **`failOnUndecided`**:\ - If set to true, the `run_tasks_concurrent` task will fail if neither the `succeedTaskCount` nor the `failTaskCount` is reached. +- **`failureThreshold`**:\ + The number of child tasks that need to fail before the task is considered failed. Default: `1` (any single failure causes overall failure). -- **`newVariableScope`**:\ - If set to true, a new variable scope will be created for the child tasks, if not, the child tasks will use the same variable scope as the parent task. +- **`stopOnThreshold`**:\ + If set to `true`, remaining child tasks are cancelled when either the success or failure threshold is reached. If `false` (default), the task waits for all children to complete before determining the result. -- **`tasks`**:\ - An array of child tasks to be executed concurrently. Each task in this array should be defined according to the standard task structure. +- **`invertResult`**:\ + If set to `true`, the final result is inverted: success becomes failure and failure becomes success. Default: `false`. + +- **`ignoreResult`**:\ + If set to `true`, the task always returns success regardless of child task outcomes. Default: `false`. + +- **`newVariableScope`**:\ + If set to `true`, a new variable scope will be created for each child task. If `false`, the child tasks will use the same variable scope as the parent task. Default: `true`. ### Defaults @@ -27,9 +39,15 @@ Default settings for the `run_tasks_concurrent` task: ```yaml - name: run_tasks_concurrent config: - succeedTaskCount: 0 - failTaskCount: 1 - failOnUndecided: false - newVariableScope: true tasks: [] + successThreshold: 0 + failureThreshold: 1 + stopOnThreshold: false + invertResult: false + ignoreResult: false + newVariableScope: true ``` + +### Outputs + +This task does not produce any outputs. diff --git a/pkg/tasks/run_tasks_concurrent/config.go b/pkg/tasks/run_tasks_concurrent/config.go index 5cf6d345..26da4210 100644 --- a/pkg/tasks/run_tasks_concurrent/config.go +++ b/pkg/tasks/run_tasks_concurrent/config.go @@ -7,26 +7,29 @@ import ( ) type Config struct { - // number of successful child tasks to make this task succeed (0 = all tasks) - SucceedTaskCount uint64 `yaml:"succeedTaskCount" json:"succeedTaskCount"` - - // number of failed child tasks to make this task fail (0 = all tasks) - FailTaskCount uint64 `yaml:"failTaskCount" json:"failTaskCount"` - - // fail task if neither succeedTaskCount nor failTaskCount is reached, but all tasks completed - FailOnUndecided bool `yaml:"failOnUndecided" json:"failOnUndecided"` - - // create a new variable scope for the child tasks - NewVariableScope bool `yaml:"newVariableScope" json:"newVariableScope"` - - // child tasks - Tasks []helper.RawMessageMasked `yaml:"tasks" json:"tasks"` + Tasks []helper.RawMessageMasked `yaml:"tasks" json:"tasks"` + NewVariableScope bool `yaml:"newVariableScope" json:"newVariableScope"` + + // Threshold behavior: + // - 0 (default): No threshold - only evaluate result when ALL tasks complete + // - >0: Set result when threshold is reached (but continue until all complete unless StopOnThreshold=true) + SuccessThreshold uint64 `yaml:"successThreshold" json:"successThreshold"` + FailureThreshold uint64 `yaml:"failureThreshold" json:"failureThreshold"` + + // Early termination - if true, stop immediately when a threshold is reached + // Default: false - always wait for all tasks to complete + StopOnThreshold bool `yaml:"stopOnThreshold" json:"stopOnThreshold"` + + // Result transformation + InvertResult bool `yaml:"invertResult" json:"invertResult"` + IgnoreResult bool `yaml:"ignoreResult" json:"ignoreResult"` } func DefaultConfig() Config { return Config{ Tasks: []helper.RawMessageMasked{}, - FailTaskCount: 1, + FailureThreshold: 1, + StopOnThreshold: true, NewVariableScope: true, } } diff --git a/pkg/tasks/run_tasks_concurrent/task.go b/pkg/tasks/run_tasks_concurrent/task.go index 44b38d16..b8049cde 100644 --- a/pkg/tasks/run_tasks_concurrent/task.go +++ b/pkg/tasks/run_tasks_concurrent/task.go @@ -151,15 +151,10 @@ func (t *Task) Execute(ctx context.Context) error { }() // watch result updates - successLimit := t.config.SucceedTaskCount - if successLimit == 0 { - successLimit = uint64(len(t.tasks)) - } - - failureLimit := t.config.FailTaskCount - if failureLimit == 0 { - failureLimit = uint64(len(t.tasks)) - } + // When threshold is 0, don't apply threshold logic - only evaluate at completion + // When threshold > 0, use that value as the limit + successLimit := t.config.SuccessThreshold + failureLimit := t.config.FailureThreshold var successCount, failureCount, pendingCount uint64 @@ -189,18 +184,24 @@ func (t *Task) Execute(ctx context.Context) error { } } - if successCount >= successLimit { - t.logger.Infof("success limit reached (%v success, %v failure)", successCount, failureCount) + // Only check success threshold if explicitly configured (> 0) + if successLimit > 0 && successCount >= successLimit { + t.logger.Infof("success threshold reached (%v success, %v failure)", successCount, failureCount) t.ctx.SetResult(types.TaskResultSuccess) - taskComplete = true + if t.config.StopOnThreshold { + taskComplete = true + } } - if !taskComplete && failureCount >= failureLimit { - t.logger.Infof("failure limit reached (%v success, %v failure)", successCount, failureCount) + // Only check failure threshold if explicitly configured (> 0) + if !taskComplete && failureLimit > 0 && failureCount >= failureLimit { + t.logger.Infof("failure threshold reached (%v success, %v failure)", successCount, failureCount) t.ctx.SetResult(types.TaskResultFailure) - taskComplete = true + if t.config.StopOnThreshold { + taskComplete = true + } } if !taskComplete { @@ -216,7 +217,9 @@ func (t *Task) Execute(ctx context.Context) error { if !taskComplete { taskComplete = true - if t.config.FailOnUndecided { + // All tasks completed - determine final result + // If any task failed, the result is failure; otherwise success + if failureCount > 0 { t.ctx.SetResult(types.TaskResultFailure) } else { t.ctx.SetResult(types.TaskResultSuccess) @@ -234,6 +237,28 @@ func (t *Task) Execute(ctx context.Context) error { cancelFn() taskWaitGroup.Wait() + // Apply result transformation + taskResult := t.ctx.Scheduler.GetTaskState(t.ctx.Index).GetTaskStatus().Result + + if t.config.IgnoreResult { + return nil + } + + if t.config.InvertResult { + if taskResult == types.TaskResultFailure { + t.ctx.SetResult(types.TaskResultSuccess) + return nil + } + + t.ctx.SetResult(types.TaskResultFailure) + + return fmt.Errorf("all tasks succeeded, but failure was expected") + } + + if taskResult == types.TaskResultFailure { + return fmt.Errorf("concurrent task execution failed (%d failures)", failureCount) + } + return nil } From f76951f783f2f696bed17ccf0e46d1f7f7ec70cd Mon Sep 17 00:00:00 2001 From: pk910 Date: Sun, 1 Feb 2026 20:39:17 +0100 Subject: [PATCH 04/32] [RF-3] reimplement web ui as modern app with live updates --- .gitignore | 10 + .hack/devnet/run.sh | 42 + Makefile | 21 +- go.mod | 1 + go.sum | 2 + pkg/assertoor/coordinator.go | 7 +- pkg/clients/clients.go | 115 + pkg/events/event.go | 35 + pkg/events/sse.go | 132 +- pkg/scheduler/task_state.go | 17 + pkg/web/api/get_clients_api.go | 112 + pkg/web/api/get_task_details_api.go | 15 +- pkg/web/api/get_test_run_details_api.go | 46 +- pkg/web/api/handler.go | 35 +- pkg/web/api/log_level.go | 25 + pkg/web/api/post_test_run_api.go | 5 + pkg/web/api/post_test_run_cancel_api.go | 5 + pkg/web/api/post_test_runs_delete_api.go | 5 + pkg/web/api/post_tests_delete_api.go | 5 + pkg/web/api/post_tests_register_api.go | 5 + .../api/post_tests_register_external_api.go | 5 + pkg/web/auth/check.go | 26 + pkg/web/auth/handler.go | 83 + pkg/web/handlers/clients.go | 30 + pkg/web/handlers/spa.go | 104 + pkg/web/server.go | 130 +- pkg/web/types/config.go | 6 +- web-ui/.gitignore | 22 + web-ui/eslint.config.mjs | 7 + web-ui/package-lock.json | 10849 ++++++++++++++++ web-ui/package.json | 51 + web-ui/postcss.config.js | 6 + web-ui/public/index.html | 13 + web-ui/src/App.tsx | 26 + web-ui/src/api/client.ts | 153 + web-ui/src/components/auth/UserDisplay.tsx | 62 + web-ui/src/components/common/Dropdown.tsx | 130 + web-ui/src/components/common/Layout.tsx | 113 + web-ui/src/components/common/Modal.tsx | 85 + web-ui/src/components/common/Pagination.tsx | 107 + web-ui/src/components/common/SplitPane.tsx | 126 + web-ui/src/components/common/StatusBadge.tsx | 39 + web-ui/src/components/task/TaskDetails.tsx | 222 + web-ui/src/components/task/TaskList.tsx | 337 + web-ui/src/components/test/StartTestModal.tsx | 475 + web-ui/src/context/AuthContext.tsx | 37 + web-ui/src/hooks/useApi.ts | 157 + web-ui/src/hooks/useAuth.ts | 44 + web-ui/src/hooks/useClientEvents.ts | 86 + web-ui/src/hooks/useEventStream.ts | 160 + web-ui/src/hooks/useTheme.ts | 48 + web-ui/src/index.tsx | 32 + web-ui/src/pages/Clients.tsx | 189 + web-ui/src/pages/Dashboard.tsx | 370 + web-ui/src/pages/Registry.tsx | 395 + web-ui/src/pages/TestPage.tsx | 168 + web-ui/src/pages/TestRun.tsx | 384 + web-ui/src/stores/authStore.ts | 197 + web-ui/src/styles/index.css | 130 + web-ui/src/types/api.ts | 261 + web-ui/src/utils/time.ts | 94 + web-ui/tailwind.config.js | 52 + web-ui/tsconfig.json | 26 + web-ui/webpack.config.js | 159 + 64 files changed, 16760 insertions(+), 76 deletions(-) create mode 100644 pkg/web/api/get_clients_api.go create mode 100644 pkg/web/api/log_level.go create mode 100644 pkg/web/auth/check.go create mode 100644 pkg/web/auth/handler.go create mode 100644 pkg/web/handlers/spa.go create mode 100644 web-ui/.gitignore create mode 100644 web-ui/eslint.config.mjs create mode 100644 web-ui/package-lock.json create mode 100644 web-ui/package.json create mode 100644 web-ui/postcss.config.js create mode 100644 web-ui/public/index.html create mode 100644 web-ui/src/App.tsx create mode 100644 web-ui/src/api/client.ts create mode 100644 web-ui/src/components/auth/UserDisplay.tsx create mode 100644 web-ui/src/components/common/Dropdown.tsx create mode 100644 web-ui/src/components/common/Layout.tsx create mode 100644 web-ui/src/components/common/Modal.tsx create mode 100644 web-ui/src/components/common/Pagination.tsx create mode 100644 web-ui/src/components/common/SplitPane.tsx create mode 100644 web-ui/src/components/common/StatusBadge.tsx create mode 100644 web-ui/src/components/task/TaskDetails.tsx create mode 100644 web-ui/src/components/task/TaskList.tsx create mode 100644 web-ui/src/components/test/StartTestModal.tsx create mode 100644 web-ui/src/context/AuthContext.tsx create mode 100644 web-ui/src/hooks/useApi.ts create mode 100644 web-ui/src/hooks/useAuth.ts create mode 100644 web-ui/src/hooks/useClientEvents.ts create mode 100644 web-ui/src/hooks/useEventStream.ts create mode 100644 web-ui/src/hooks/useTheme.ts create mode 100644 web-ui/src/index.tsx create mode 100644 web-ui/src/pages/Clients.tsx create mode 100644 web-ui/src/pages/Dashboard.tsx create mode 100644 web-ui/src/pages/Registry.tsx create mode 100644 web-ui/src/pages/TestPage.tsx create mode 100644 web-ui/src/pages/TestRun.tsx create mode 100644 web-ui/src/stores/authStore.ts create mode 100644 web-ui/src/styles/index.css create mode 100644 web-ui/src/types/api.ts create mode 100644 web-ui/src/utils/time.ts create mode 100644 web-ui/tailwind.config.js create mode 100644 web-ui/tsconfig.json create mode 100644 web-ui/webpack.config.js diff --git a/.gitignore b/.gitignore index 71bf3650..6b1fb16e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,13 @@ test-*.yaml .hack/devnet/generated-** .hack/devnet/custom-** CLAUDE.md + +# Web UI +web-ui/node_modules/ + +# Built React assets (generated by make ui) +pkg/web/static/js/app* +pkg/web/static/js/vendors* +pkg/web/static/js/reactflow* +pkg/web/static/css/app* +pkg/web/static/index.html diff --git a/.hack/devnet/run.sh b/.hack/devnet/run.sh index e7cc42fe..c7b0c1fe 100755 --- a/.hack/devnet/run.sh +++ b/.hack/devnet/run.sh @@ -36,6 +36,48 @@ yq eval-all '. as $item ireduce ({}; . *+ $item)' "${__dir}/generated-assertoor- mv "${__dir}/generated-assertoor-config-final.yaml" "${__dir}/generated-assertoor-config.yaml" rm "${__dir}/generated-assertoor-config-custom.yaml" +# Resolve container hostnames to IPs for running assertoor outside Docker +# Kurtosis service hostnames need to be resolved via Docker network inspection +DOCKER_NETWORK="kt-${ENCLAVE_NAME}" + +# Build a map of service hostnames to container IPs +declare -A hostname_to_ip + +echo "Building hostname to IP mapping from Docker network..." +# Get all container info from the kurtosis network +# Format: container_name -> IP +while IFS= read -r line; do + if [ -n "$line" ]; then + container_name=$(echo "$line" | cut -d'|' -f1) + container_ip=$(echo "$line" | cut -d'|' -f2 | cut -d'/' -f1) + # Kurtosis container names are like: service--uuid + # Remove the trailing --uuid to get the service name (hostname) + service_name=$(echo "$container_name" | sed 's/--[a-f0-9]*$//') + if [ -n "$service_name" ] && [ -n "$container_ip" ]; then + hostname_to_ip["$service_name"]="$container_ip" + fi + fi +done < <(docker network inspect "$DOCKER_NETWORK" --format '{{range $id, $container := .Containers}}{{$container.Name}}|{{$container.IPv4Address}}{{"\n"}}{{end}}' 2>/dev/null) + +# Extract all hostnames from URLs in the config and resolve them +echo "Resolving container hostnames to IPs..." +config_content=$(cat "${__dir}/generated-assertoor-config.yaml") + +# Get unique hostnames from http:// URLs (matches pattern http://hostname:port) +hostnames=$(echo "$config_content" | grep -oE 'http://[a-zA-Z0-9_-]+:[0-9]+' | sed 's|http://||' | cut -d':' -f1 | sort -u) + +for hostname in $hostnames; do + ip="${hostname_to_ip[$hostname]}" + if [ -n "$ip" ]; then + echo " $hostname -> $ip" + config_content=$(echo "$config_content" | sed "s|http://${hostname}:|http://${ip}:|g") + else + echo " WARNING: Could not resolve $hostname" + fi +done + +echo "$config_content" > "${__dir}/generated-assertoor-config.yaml" + cat <" + parts := strings.SplitN(tokenStr, " ", 2) + if len(parts) == 2 && strings.EqualFold(parts[0], "bearer") { + tokenStr = parts[1] + } + + token, _ := jwt.ParseWithClaims(tokenStr, &jwt.RegisteredClaims{}, func(token *jwt.Token) (any, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + + return []byte(h.tokenKey), nil + }) + + return token +} diff --git a/pkg/web/auth/handler.go b/pkg/web/auth/handler.go new file mode 100644 index 00000000..78c7510e --- /dev/null +++ b/pkg/web/auth/handler.go @@ -0,0 +1,83 @@ +package auth + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +// Handler handles authentication requests for the assertoor web UI. +type Handler struct { + userHeader string + tokenKey string +} + +// NewAuthHandler creates a new authentication handler. +func NewAuthHandler(tokenKey, userHeader string) *Handler { + return &Handler{ + userHeader: userHeader, + tokenKey: tokenKey, + } +} + +// GetToken handles the authentication request. +func (h *Handler) GetToken(w http.ResponseWriter, r *http.Request) { + headers := r.Header + authUser := "unauthenticated" + + // Try exact header match first + if values, ok := headers[h.userHeader]; ok && len(values) > 0 { + authUser = values[0] + } else { + // Try case-insensitive match + for key, values := range headers { + if strings.EqualFold(key, h.userHeader) && len(values) > 0 { + authUser = values[0] + break + } + } + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{ + Issuer: "assertoor", + Subject: authUser, + ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)), + }) + + tokenString, err := token.SignedString([]byte(h.tokenKey)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + claims, ok := token.Claims.(jwt.RegisteredClaims) + if !ok { + http.Error(w, "invalid token claims", http.StatusInternalServerError) + return + } + + err = json.NewEncoder(w).Encode(map[string]string{ + "token": tokenString, + "user": authUser, + "expr": fmt.Sprintf("%d", claims.ExpiresAt.Unix()), + "now": fmt.Sprintf("%d", time.Now().Unix()), + }) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + + return + } +} + +// GetLogin redirects to the index page. +func (h *Handler) GetLogin(w http.ResponseWriter, r *http.Request) { + // redirect back to the index page + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) +} diff --git a/pkg/web/handlers/clients.go b/pkg/web/handlers/clients.go index 8fedf71e..fb719c9a 100644 --- a/pkg/web/handlers/clients.go +++ b/pkg/web/handlers/clients.go @@ -1,12 +1,14 @@ package handlers import ( + "encoding/json" "net/http" "time" "github.com/ethpandaops/assertoor/pkg/clients" "github.com/ethpandaops/assertoor/pkg/clients/consensus" "github.com/ethpandaops/assertoor/pkg/clients/execution" + "github.com/sirupsen/logrus" ) type ClientsPage struct { @@ -37,6 +39,12 @@ type ClientsPageClient struct { // Clients will return the "clients" page using a go template func (fh *FrontendHandler) Clients(w http.ResponseWriter, r *http.Request) { + urlArgs := r.URL.Query() + if urlArgs.Has("json") { + fh.ClientsData(w, r) + return + } + templateFiles := LayoutTemplateFiles templateFiles = append(templateFiles, "clients/clients.html", @@ -60,6 +68,28 @@ func (fh *FrontendHandler) Clients(w http.ResponseWriter, r *http.Request) { } } +func (fh *FrontendHandler) ClientsData(w http.ResponseWriter, r *http.Request) { + var pageData *ClientsPage + + var pageError error + + pageData, pageError = fh.getClientsPageData() + if pageError != nil { + fh.HandlePageError(w, r, pageError) + return + } + + w.Header().Set("Content-Type", "application/json") + + err := json.NewEncoder(w).Encode(pageData) + if err != nil { + logrus.WithError(err).Error("error encoding clients data") + + //nolint:gocritic // ignore + http.Error(w, "Internal server error", http.StatusServiceUnavailable) + } +} + //nolint:unparam // ignore func (fh *FrontendHandler) getClientsPageData() (*ClientsPage, error) { pageData := &ClientsPage{ diff --git a/pkg/web/handlers/spa.go b/pkg/web/handlers/spa.go new file mode 100644 index 00000000..3c08fff5 --- /dev/null +++ b/pkg/web/handlers/spa.go @@ -0,0 +1,104 @@ +package handlers + +import ( + "io" + "net/http" + "path" + "strings" + + "github.com/ethpandaops/assertoor/pkg/web/static" + "github.com/sirupsen/logrus" +) + +// SPAHandler serves a React single-page application. +// It serves static files when they exist, otherwise falls back to index.html +// for client-side routing. +type SPAHandler struct { + logger logrus.FieldLogger + staticFS http.FileSystem + indexHTML []byte + contentType string +} + +// NewSPAHandler creates a new SPA handler. +func NewSPAHandler(logger logrus.FieldLogger) (*SPAHandler, error) { + fs := http.FS(static.FS) + + // Pre-load index.html for faster serving + indexFile, err := static.FS.Open("index.html") + if err != nil { + return nil, err + } + defer indexFile.Close() + + indexHTML, err := io.ReadAll(indexFile) + if err != nil { + return nil, err + } + + return &SPAHandler{ + logger: logger, + staticFS: fs, + indexHTML: indexHTML, + contentType: "text/html; charset=utf-8", + }, nil +} + +// ServeHTTP implements http.Handler. +func (h *SPAHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Clean the path + urlPath := path.Clean(r.URL.Path) + if !strings.HasPrefix(urlPath, "/") { + urlPath = "/" + urlPath + } + + // Try to serve static file + if h.serveStaticFile(w, r, urlPath) { + return + } + + // Fall back to index.html for SPA routing + h.serveIndex(w, r) +} + +// serveStaticFile attempts to serve a static file. Returns true if successful. +func (h *SPAHandler) serveStaticFile(w http.ResponseWriter, r *http.Request, urlPath string) bool { + // Don't serve index.html directly - let SPA handle root + if urlPath == "/" || urlPath == "/index.html" { + return false + } + + // Check if the file exists + f, err := h.staticFS.Open(urlPath) + if err != nil { + return false + } + defer f.Close() + + stat, err := f.Stat() + if err != nil || stat.IsDir() { + return false + } + + // Check if file implements ReadSeeker + rs, ok := f.(io.ReadSeeker) + if !ok { + return false + } + + // Serve the static file + http.ServeContent(w, r, urlPath, stat.ModTime(), rs) + + return true +} + +// serveIndex serves the SPA index.html. +func (h *SPAHandler) serveIndex(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", h.contentType) + w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") + w.WriteHeader(http.StatusOK) + + if _, err := w.Write(h.indexHTML); err != nil { + h.logger.WithError(err).Error("failed to write index.html") + } +} diff --git a/pkg/web/server.go b/pkg/web/server.go index d1711bc3..0f0a4c3b 100644 --- a/pkg/web/server.go +++ b/pkg/web/server.go @@ -11,6 +11,7 @@ import ( "github.com/ethpandaops/assertoor/pkg/events" coordinator_types "github.com/ethpandaops/assertoor/pkg/types" "github.com/ethpandaops/assertoor/pkg/web/api" + "github.com/ethpandaops/assertoor/pkg/web/auth" "github.com/ethpandaops/assertoor/pkg/web/handlers" "github.com/ethpandaops/assertoor/pkg/web/types" "github.com/gorilla/mux" @@ -75,15 +76,24 @@ func NewWebServer(config *types.ServerConfig, logger logrus.FieldLogger) (*Serve return ws, nil } -func (ws *Server) ConfigureRoutes(frontendConfig *types.FrontendConfig, apiConfig *types.APIConfig, coordinator coordinator_types.Coordinator, securityTrimmed bool) error { - return ws.ConfigureRoutesWithEventBus(frontendConfig, apiConfig, coordinator, securityTrimmed, nil) -} - -func (ws *Server) ConfigureRoutesWithEventBus(frontendConfig *types.FrontendConfig, apiConfig *types.APIConfig, coordinator coordinator_types.Coordinator, securityTrimmed bool, eventBus *events.EventBus) error { +func (ws *Server) ConfigureRoutes(frontendConfig *types.FrontendConfig, apiConfig *types.APIConfig, coordinator coordinator_types.Coordinator, securityTrimmed bool, eventBus *events.EventBus) error { isAPIEnabled := apiConfig != nil && apiConfig.Enabled + isFrontendEnabled := frontendConfig != nil && frontendConfig.Enabled + + // Create auth handler for protected endpoints + var authHandler *auth.Handler + if isFrontendEnabled || isAPIEnabled { + authHandler = auth.NewAuthHandler(ws.serverConfig.TokenKey, ws.serverConfig.AuthHeader) + ws.router.HandleFunc("/auth/token", authHandler.GetToken).Methods("GET") + ws.router.HandleFunc("/auth/login", authHandler.GetLogin).Methods("GET") + } + if isAPIEnabled { + // Check if authentication is disabled for protected APIs (auth is required by default) + disableAuth := apiConfig.DisableAuth + // register api routes - apiHandler := api.NewAPIHandler(ws.logger.WithField("module", "api"), coordinator) + apiHandler := api.NewAPIHandler(ws.logger.WithField("module", "api"), coordinator, authHandler, disableAuth) // public apis ws.router.HandleFunc("/api/v1/tests", apiHandler.GetTests).Methods("GET") @@ -93,11 +103,25 @@ func (ws *Server) ConfigureRoutesWithEventBus(frontendConfig *types.FrontendConf ws.router.HandleFunc("/api/v1/test_run/{runId}/status", apiHandler.GetTestRunStatus).Methods("GET") ws.router.HandleFunc("/api/v1/task_descriptors", apiHandler.GetTaskDescriptors).Methods("GET") ws.router.HandleFunc("/api/v1/task_descriptor/{name}", apiHandler.GetTaskDescriptor).Methods("GET") + ws.router.HandleFunc("/api/v1/clients", apiHandler.GetClients).Methods("GET") // SSE event stream endpoints if eventBus != nil { - sseHandler := events.NewSSEHandler(ws.logger.WithField("module", "sse"), eventBus) + // Create SSE handler with auth support for log filtering + var sseHandler *events.SSEHandler + if authHandler != nil { + sseHandler = events.NewSSEHandlerWithAuth( + ws.logger.WithField("module", "sse"), + eventBus, + authHandler.CheckAuthToken, + !disableAuth, // Require auth for log events when API auth is not disabled + ) + } else { + sseHandler = events.NewSSEHandler(ws.logger.WithField("module", "sse"), eventBus) + } + ws.router.HandleFunc("/api/v1/events/stream", sseHandler.HandleGlobalStream).Methods("GET") + ws.router.HandleFunc("/api/v1/events/clients", sseHandler.HandleClientStream).Methods("GET") ws.router.HandleFunc("/api/v1/test_run/{runId}/events", func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) @@ -111,19 +135,17 @@ func (ws *Server) ConfigureRoutesWithEventBus(frontendConfig *types.FrontendConf }).Methods("GET") } - // private apis - if !securityTrimmed { - ws.router.HandleFunc("/api/v1/tests/register", apiHandler.PostTestsRegister).Methods("POST") - ws.router.HandleFunc("/api/v1/tests/register_external", apiHandler.PostTestsRegisterExternal).Methods("POST") - ws.router.HandleFunc("/api/v1/tests/delete", apiHandler.PostTestsDelete).Methods("POST") - ws.router.HandleFunc("/api/v1/test_run", apiHandler.PostTestRunsSchedule).Methods("POST") // legacy - ws.router.HandleFunc("/api/v1/test_runs/schedule", apiHandler.PostTestRunsSchedule).Methods("POST") - ws.router.HandleFunc("/api/v1/test_runs/delete", apiHandler.PostTestRunsDelete).Methods("POST") - ws.router.HandleFunc("/api/v1/test_run/{runId}/cancel", apiHandler.PostTestRunCancel).Methods("POST") - ws.router.HandleFunc("/api/v1/test_run/{runId}/details", apiHandler.GetTestRunDetails).Methods("GET") - ws.router.HandleFunc("/api/v1/test_run/{runId}/task/{taskIndex}/details", apiHandler.GetTestRunTaskDetails).Methods("GET") - ws.router.HandleFunc("/api/v1/test_run/{runId}/task/{taskId}/result/{resultType}/{fileId:.*}", apiHandler.GetTaskResult).Methods("GET") - } + // protected apis (require authentication) + ws.router.HandleFunc("/api/v1/tests/register", apiHandler.PostTestsRegister).Methods("POST") + ws.router.HandleFunc("/api/v1/tests/register_external", apiHandler.PostTestsRegisterExternal).Methods("POST") + ws.router.HandleFunc("/api/v1/tests/delete", apiHandler.PostTestsDelete).Methods("POST") + ws.router.HandleFunc("/api/v1/test_run", apiHandler.PostTestRunsSchedule).Methods("POST") // legacy + ws.router.HandleFunc("/api/v1/test_runs/schedule", apiHandler.PostTestRunsSchedule).Methods("POST") + ws.router.HandleFunc("/api/v1/test_runs/delete", apiHandler.PostTestRunsDelete).Methods("POST") + ws.router.HandleFunc("/api/v1/test_run/{runId}/cancel", apiHandler.PostTestRunCancel).Methods("POST") + ws.router.HandleFunc("/api/v1/test_run/{runId}/details", apiHandler.GetTestRunDetails).Methods("GET") + ws.router.HandleFunc("/api/v1/test_run/{runId}/task/{taskIndex}/details", apiHandler.GetTestRunTaskDetails).Methods("GET") + ws.router.HandleFunc("/api/v1/test_run/{runId}/task/{taskId}/result/{resultType}/{fileId:.*}", apiHandler.GetTaskResult).Methods("GET") } if frontendConfig != nil { @@ -133,29 +155,53 @@ func (ws *Server) ConfigureRoutesWithEventBus(frontendConfig *types.FrontendConf } if frontendConfig.Enabled { - frontendHandler := handlers.NewFrontendHandler( - coordinator, - ws.logger.WithField("module", "web-frontend"), - frontendConfig.SiteName, - frontendConfig.Minify, - frontendConfig.Debug, - securityTrimmed, - isAPIEnabled, - ) - - ws.router.HandleFunc("/", frontendHandler.Index).Methods("GET") - ws.router.HandleFunc("/registry", frontendHandler.Registry).Methods("GET") - ws.router.HandleFunc("/test/{testId}", frontendHandler.TestPage).Methods("GET") - ws.router.HandleFunc("/run/{runId}", frontendHandler.TestRun).Methods("GET") - ws.router.HandleFunc("/clients", frontendHandler.Clients).Methods("GET") - ws.router.HandleFunc("/logs/{since}", frontendHandler.LogsData).Methods("GET") - - if isAPIEnabled { - // add swagger handler - ws.router.PathPrefix("/api/docs/").Handler(ws.getSwaggerHandler(ws.logger, frontendHandler)) - } + // Create SPA handler for React frontend + spaHandler, err := handlers.NewSPAHandler(ws.logger.WithField("module", "web-spa")) + if err != nil { + ws.logger.WithError(err).Warn("failed to create SPA handler, falling back to legacy frontend") + + // Fall back to legacy frontend + frontendHandler := handlers.NewFrontendHandler( + coordinator, + ws.logger.WithField("module", "web-frontend"), + frontendConfig.SiteName, + frontendConfig.Minify, + frontendConfig.Debug, + securityTrimmed, + isAPIEnabled, + ) + + ws.router.HandleFunc("/", frontendHandler.Index).Methods("GET") + ws.router.HandleFunc("/registry", frontendHandler.Registry).Methods("GET") + ws.router.HandleFunc("/test/{testId}", frontendHandler.TestPage).Methods("GET") + ws.router.HandleFunc("/run/{runId}", frontendHandler.TestRun).Methods("GET") + ws.router.HandleFunc("/clients", frontendHandler.Clients).Methods("GET") + ws.router.HandleFunc("/logs/{since}", frontendHandler.LogsData).Methods("GET") + + if isAPIEnabled { + ws.router.PathPrefix("/api/docs/").Handler(ws.getSwaggerHandler(ws.logger, frontendHandler)) + } - ws.router.PathPrefix("/").Handler(frontendHandler) + ws.router.PathPrefix("/").Handler(frontendHandler) + } else { + // Use React SPA for all frontend routes + if isAPIEnabled { + // Swagger docs still need special handling + frontendHandler := handlers.NewFrontendHandler( + coordinator, + ws.logger.WithField("module", "web-frontend"), + frontendConfig.SiteName, + frontendConfig.Minify, + frontendConfig.Debug, + securityTrimmed, + isAPIEnabled, + ) + ws.router.PathPrefix("/api/docs/").Handler(ws.getSwaggerHandler(ws.logger, frontendHandler)) + } + + // SPA handles all other routes + ws.router.PathPrefix("/").Handler(spaHandler) + } } } diff --git a/pkg/web/types/config.go b/pkg/web/types/config.go index d1ab1a16..6de890cf 100644 --- a/pkg/web/types/config.go +++ b/pkg/web/types/config.go @@ -16,6 +16,9 @@ type ServerConfig struct { ReadTimeout time.Duration `yaml:"readTimeout" envconfig:"WEB_SERVER_READ_TIMEOUT"` WriteTimeout time.Duration `yaml:"writeTimeout" envconfig:"WEB_SERVER_WRITE_TIMEOUT"` IdleTimeout time.Duration `yaml:"idleTimeout" envconfig:"WEB_SERVER_IDLE_TIMEOUT"` + + AuthHeader string `yaml:"authHeader" envconfig:"WEB_SERVER_AUTH_HEADER"` + TokenKey string `yaml:"tokenKey" envconfig:"WEB_SERVER_TOKEN_KEY"` } type FrontendConfig struct { @@ -27,5 +30,6 @@ type FrontendConfig struct { } type APIConfig struct { - Enabled bool `yaml:"enabled" envconfig:"WEB_API_ENABLED"` + Enabled bool `yaml:"enabled" envconfig:"WEB_API_ENABLED"` + DisableAuth bool `yaml:"disableAuth" envconfig:"WEB_API_DISABLE_AUTH"` } diff --git a/web-ui/.gitignore b/web-ui/.gitignore new file mode 100644 index 00000000..8a2398ff --- /dev/null +++ b/web-ui/.gitignore @@ -0,0 +1,22 @@ +# Dependencies +node_modules/ + +# Build output (goes to pkg/web/static) +dist/ + +# IDE +.idea/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# TypeScript +*.tsbuildinfo diff --git a/web-ui/eslint.config.mjs b/web-ui/eslint.config.mjs new file mode 100644 index 00000000..56015635 --- /dev/null +++ b/web-ui/eslint.config.mjs @@ -0,0 +1,7 @@ +// ESLint flat config for React + TypeScript +// Dependencies will be configured after npm install +export default [ + { + ignores: ['node_modules/**', 'dist/**', '*.config.js', '*.config.mjs'], + }, +]; diff --git a/web-ui/package-lock.json b/web-ui/package-lock.json new file mode 100644 index 00000000..f7e9b7a2 --- /dev/null +++ b/web-ui/package-lock.json @@ -0,0 +1,10849 @@ +{ + "name": "assertoor-ui", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "assertoor-ui", + "version": "1.0.0", + "dependencies": { + "@dnd-kit/core": "^6.1.0", + "@dnd-kit/sortable": "^8.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@tanstack/react-query": "^5.59.0", + "js-yaml": "^4.1.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.26.2", + "reactflow": "^11.11.4", + "zustand": "^4.5.5" + }, + "devDependencies": { + "@types/js-yaml": "^4.0.9", + "@types/react": "^18.3.8", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^8.6.0", + "@typescript-eslint/parser": "^8.6.0", + "autoprefixer": "^10.4.20", + "copy-webpack-plugin": "^12.0.2", + "css-loader": "^7.1.2", + "css-minimizer-webpack-plugin": "^7.0.0", + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.36.1", + "eslint-plugin-react-hooks": "^4.6.2", + "html-webpack-plugin": "^5.6.0", + "mini-css-extract-plugin": "^2.9.1", + "postcss": "^8.4.47", + "postcss-loader": "^8.1.1", + "style-loader": "^4.0.0", + "tailwindcss": "^3.4.12", + "terser-webpack-plugin": "^5.3.10", + "ts-loader": "^9.5.1", + "typescript": "^5.6.2", + "webpack": "^5.94.0", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^5.1.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-8.0.0.tgz", + "integrity": "sha512-U3jk5ebVXe1Lr7c2wU7SBZjcWdQP+j7peHJfCspnA81enlu88Mgd7CC8Q+pub9ubP7eKVETzJW+IBAhsqbSu/g==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.1.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "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, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "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.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "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, + "license": "ISC", + "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, + "license": "Apache-2.0", + "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, + "license": "BSD-3-Clause" + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "17.65.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.65.0.tgz", + "integrity": "sha512-eBrIXd0/Ld3p9lpDDlMaMn6IEfWqtHMD+z61u0JrIiPzsV1r7m6xDZFRxJyvIFTEO+SWdYF9EiQbXZGd8BzPfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-core": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.56.10.tgz", + "integrity": "sha512-PyAEA/3cnHhsGcdY+AmIU+ZPqTuZkDhCXQ2wkXypdLitSpd6d5Ivxhnq4wa2ETRWFVJGabYynBWxIijOswSmOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.56.10.tgz", + "integrity": "sha512-/FVK63ysNzTPOnCCcPoPHt77TOmachdMS422txM4KhxddLdbW1fIbFMYH0AM0ow/YchCyS5gqEjKLNyv71j/5Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.56.10.tgz", + "integrity": "sha512-7R4Gv3tkUdW3dXfXiOkqxkElxKNVdd8BDOWC0/dbERd0pXpPY+s2s1Mino+aTvkGrFPiY+mmVxA7zhskm4Ue4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/fs-print": "4.56.10", + "@jsonjoy.com/fs-snapshot": "4.56.10", + "glob-to-regex.js": "^1.0.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.56.10.tgz", + "integrity": "sha512-uUnKz8R0YJyKq5jXpZtkGV9U0pJDt8hmYcLRrPjROheIfjMXsz82kXMgAA/qNg0wrZ1Kv+hrg7azqEZx6XZCVw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.56.10.tgz", + "integrity": "sha512-oH+O6Y4lhn9NyG6aEoFwIBNKZeYy66toP5LJcDOMBgL99BKQMUf/zWJspdRhMdn/3hbzQsZ8EHHsuekbFLGUWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-fsa": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.56.10.tgz", + "integrity": "sha512-8EuPBgVI2aDPwFdaNQeNpHsyqPi3rr+85tMNG/lHvQLiVjzoZsvxA//Xd8aB567LUhy4QS03ptT+unkD/DIsNg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.56.10" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-print": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.56.10.tgz", + "integrity": "sha512-JW4fp5mAYepzFsSGrQ48ep8FXxpg4niFWHdF78wDrFGof7F3tKDJln72QFDEn/27M1yHd4v7sKHHVPh78aWcEw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-utils": "4.56.10", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.56.10.tgz", + "integrity": "sha512-DkR6l5fj7+qj0+fVKm/OOXMGfDFCGXLfyHkORH3DF8hxkpDgIHbhf/DwncBMs2igu/ST7OEkexn1gIqoU6Y+9g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { + "version": "17.65.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.65.0.tgz", + "integrity": "sha512-Xrh7Fm/M0QAYpekSgmskdZYnFdSGnsxJ/tHaolA4bNwWdG9i65S8m83Meh7FOxyJyQAdo4d4J97NOomBLEfkDQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { + "version": "17.65.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.65.0.tgz", + "integrity": "sha512-7MXcRYe7n3BG+fo3jicvjB0+6ypl2Y/bQp79Sp7KeSiiCgLqw4Oled6chVv07/xLVTdo3qa1CD0VCCnPaw+RGA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.65.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.65.0.tgz", + "integrity": "sha512-e0SG/6qUCnVhHa0rjDJHgnXnbsacooHVqQHxspjvlYQSkHm+66wkHw6Gql+3u/WxI/b1VsOdUi0M+fOtkgKGdQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "17.65.0", + "@jsonjoy.com/buffers": "17.65.0", + "@jsonjoy.com/codegen": "17.65.0", + "@jsonjoy.com/json-pointer": "17.65.0", + "@jsonjoy.com/util": "17.65.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.65.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.65.0.tgz", + "integrity": "sha512-uhTe+XhlIZpWOxgPcnO+iSCDgKKBpwkDVTyYiXX9VayGV8HSFVJM67M6pUE71zdnXF1W0Da21AvnhlmdwYPpow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/util": "17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.65.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.65.0.tgz", + "integrity": "sha512-cWiEHZccQORf96q2y6zU3wDeIVPeidmGqd9cNKJRYoVHTV0S1eHPy5JTbHpMnGfDvtvujQwQozOqgO9ABu6h0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "17.65.0", + "@jsonjoy.com/codegen": "17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.0.tgz", + "integrity": "sha512-2uZqP+ggSncESeUF/9Su8rWqGclEfEiz1SyU02WX5fUONFfkjzS2Z/F1Li0ofSmf4JqYXIOdCAZqIXAIBAT1OA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-x509-attr": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.0.tgz", + "integrity": "sha512-BeWIu5VpTIhfRysfEp73SGbwjjoLL/JWXhJ/9mo4vXnz3tRGm+NGm3KNcRzQ9VMVqwYS2RHlolz21svzRXIHPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.0.tgz", + "integrity": "sha512-FF3LMGq6SfAOwUG2sKpPXblibn6XnEIKa+SryvUl5Pik+WR9rmRA3OCiwz8R3lVXnYnyRkSZsSLdml8H3UiOcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.0.tgz", + "integrity": "sha512-rtUvtf+tyKGgokHHmZzeUojRZJYPxoD/jaN1+VAB4kKR7tXrnDCA/RAWXAIhMJJC+7W27IIRGe9djvxKgsldCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-pkcs8": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.0.tgz", + "integrity": "sha512-KyQ4D8G/NrS7Fw3XCJrngxmjwO/3htnA0lL9gDICvEQ+GJ+EPFqldcJQTwPIdvx98Tua+WjkdKHSC0/Km7T+lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.0.tgz", + "integrity": "sha512-b78OQ6OciW0aqZxdzliXGYHASeCvvw5caqidbpQRYW2mBtXIX2WhofNXTEe7NyxTb0P6J62kAAWLwn0HuMF1Fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-pfx": "^2.6.0", + "@peculiar/asn1-pkcs8": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-x509-attr": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.0.tgz", + "integrity": "sha512-Nu4C19tsrTsCp9fDrH+sdcOKoVfdfoQQ7S3VqjJU6vedR7tY3RLkQ5oguOIB3zFW33USDUuYZnPEQYySlgha4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", + "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.0.tgz", + "integrity": "sha512-uzYbPEpoQiBoTq0/+jZtpM6Gq6zADBx+JNFP3yqRgziWBxQ/Dt/HcuvRfm9zJTPdRcBqPNdaRHTVwpyiq6iNMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.0.tgz", + "integrity": "sha512-MuIAXFX3/dc8gmoZBkwJWxUWOSvG4MMDntXhrOZpJVMkYX+MYc/rUAU2uJOved9iJEoiUx7//3D8oG83a78UJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@reactflow/background": { + "version": "11.3.14", + "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz", + "integrity": "sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/controls": { + "version": "11.2.14", + "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.14.tgz", + "integrity": "sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/core": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.4.tgz", + "integrity": "sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==", + "license": "MIT", + "dependencies": { + "@types/d3": "^7.4.0", + "@types/d3-drag": "^3.0.1", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/minimap": { + "version": "11.7.14", + "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.14.tgz", + "integrity": "sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/node-resizer": { + "version": "2.2.14", + "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.14.tgz", + "integrity": "sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.4", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/node-toolbar": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.14.tgz", + "integrity": "sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.90.20", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", + "integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.90.20", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.20.tgz", + "integrity": "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.90.20" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.1.0.tgz", + "integrity": "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", + "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", + "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/type-utils": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.54.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", + "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", + "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.54.0", + "@typescript-eslint/types": "^8.54.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", + "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", + "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", + "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", + "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", + "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.54.0", + "@typescript-eslint/tsconfig-utils": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", + "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", + "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.54.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.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, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "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, + "license": "MIT", + "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/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asn1js": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.7.tgz", + "integrity": "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.24", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", + "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001766", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "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" + } + ], + "license": "CC-BY-4.0" + }, + "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, + "license": "MIT", + "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/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/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, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "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, + "license": "MIT" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-webpack-plugin": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-declaration-sorter": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.1.tgz", + "integrity": "sha512-gz6x+KkgNCjxq3Var03pRYLhyNfwhkKF1g/yoLgDNtFvVu0/fOLV9C8fFEZRjACp/XQLumjAYo7JVjzH3wLbxA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-loader": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.3.tgz", + "integrity": "sha512-frbERmjT0UC5lMheWpJmMilnt9GEhbZJN/heUb7/zaJYeIzj5St9HvDcfshzzOqbsS+rYpMk++2SD3vGETDSyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.40", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.6.3" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-7.0.4.tgz", + "integrity": "sha512-2iACis+P8qdLj1tHcShtztkGhCNIRUajJj7iX0IM9a5FA0wXGwjV8Nf6+HsBjBfb4LO8TTAVoetBbM54V6f3+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "cssnano": "^7.0.4", + "jest-worker": "^30.0.5", + "postcss": "^8.4.40", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "lightningcss": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.2.tgz", + "integrity": "sha512-HYOPBsNvoiFeR1eghKD5C3ASm64v9YVyJB4Ivnl2gqKoQYvjjN/G0rztvKQq8OxocUtC6sjqY8jwYngIB4AByA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-preset-default": "^7.0.10", + "lilconfig": "^3.1.3" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/cssnano-preset-default": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.10.tgz", + "integrity": "sha512-6ZBjW0Lf1K1Z+0OKUAUpEN62tSXmYChXWi2NAA0afxEVsj9a+MbcB1l5qel6BHJHmULai2fCGRthCeKSFbScpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.27.0", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^5.0.1", + "postcss-calc": "^10.1.1", + "postcss-colormin": "^7.0.5", + "postcss-convert-values": "^7.0.8", + "postcss-discard-comments": "^7.0.5", + "postcss-discard-duplicates": "^7.0.2", + "postcss-discard-empty": "^7.0.1", + "postcss-discard-overridden": "^7.0.1", + "postcss-merge-longhand": "^7.0.5", + "postcss-merge-rules": "^7.0.7", + "postcss-minify-font-values": "^7.0.1", + "postcss-minify-gradients": "^7.0.1", + "postcss-minify-params": "^7.0.5", + "postcss-minify-selectors": "^7.0.5", + "postcss-normalize-charset": "^7.0.1", + "postcss-normalize-display-values": "^7.0.1", + "postcss-normalize-positions": "^7.0.1", + "postcss-normalize-repeat-style": "^7.0.1", + "postcss-normalize-string": "^7.0.1", + "postcss-normalize-timing-functions": "^7.0.1", + "postcss-normalize-unicode": "^7.0.5", + "postcss-normalize-url": "^7.0.1", + "postcss-normalize-whitespace": "^7.0.1", + "postcss-ordered-values": "^7.0.2", + "postcss-reduce-initial": "^7.0.5", + "postcss-reduce-transforms": "^7.0.1", + "postcss-svgo": "^7.1.0", + "postcss-unique-selectors": "^7.0.4" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/cssnano-utils": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.1.tgz", + "integrity": "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "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, + "license": "MIT" + }, + "node_modules/default-browser": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", + "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "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, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.283", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz", + "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==", + "dev": true, + "license": "ISC" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", + "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.1", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@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": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "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, + "license": "MIT", + "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/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/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, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/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, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "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, + "license": "BSD-2-Clause", + "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, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "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, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "license": "BSD-2-Clause", + "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.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "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, + "license": "BSD-2-Clause", + "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, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "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.8" + }, + "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, + "license": "ISC", + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "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, + "license": "MIT", + "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.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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, + "license": "ISC" + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "license": "ISC", + "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, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regex.js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "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, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.6", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.6.tgz", + "integrity": "sha512-bLjW01UTrvoWTJQL5LsMRo1SypHW80FTm12OJRSnr3v6YHNhfe+1r0MYUZJMACxnCHURVnBWRwAsWs2yPU9Ezw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "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, + "license": "MIT", + "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, + "license": "ISC", + "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, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ipaddr.js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-network-error": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", + "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", + "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.2.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "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, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/launch-editor": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", + "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" + } + }, + "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, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "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, + "license": "MIT" + }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.56.10", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.56.10.tgz", + "integrity": "sha512-eLvzyrwqLHnLYalJP7YZ3wBe79MXktMdfQbvMrVD80K+NhrIukCVBvgP30zTJYEEDh9hZ/ep9z0KOdD7FSHo7w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.56.10", + "@jsonjoy.com/fs-fsa": "4.56.10", + "@jsonjoy.com/fs-node": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.56.10", + "@jsonjoy.com/fs-node-to-fsa": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/fs-print": "4.56.10", + "@jsonjoy.com/fs-snapshot": "4.56.10", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.0.tgz", + "integrity": "sha512-540P2c5dYnJlyJxTaSloliZexv8rji6rY8FhQN+WF/82iHQfA23j/xtJx97L+mXOML27EqksSek/g4eK7jaL3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "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, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "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, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "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/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkijs": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.3.3.tgz", + "integrity": "sha512-+KD8hJtqQMYoTuL1bbGOqxb4z+nZkTAwVdNtWwe8Tc2xNbEmdJYIYoc6Qt0uF55e6YW6KuTHw1DjQ18gMhzepw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-calc": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", + "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12 || ^20.9 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.38" + } + }, + "node_modules/postcss-colormin": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.5.tgz", + "integrity": "sha512-ekIBP/nwzRWhEMmIxHHbXHcMdzd1HIUzBECaj5KEdLz9DVP2HzT065sEhvOx1dkLjYW7jyD0CngThx6bpFi2fA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.27.0", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-convert-values": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.8.tgz", + "integrity": "sha512-+XNKuPfkHTCEo499VzLMYn94TiL3r9YqRE3Ty+jP7UX4qjewUONey1t7CG21lrlTLN07GtGM8MqFVp86D4uKJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.27.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-discard-comments": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.5.tgz", + "integrity": "sha512-IR2Eja8WfYgN5n32vEGSctVQ1+JARfu4UH8M7bgGh1bC+xI/obsPJXaBpQF7MAByvgwZinhpHpdrmXtvVVlKcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.1.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.2.tgz", + "integrity": "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-discard-empty": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.1.tgz", + "integrity": "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.1.tgz", + "integrity": "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-import/node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-loader": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.2.0.tgz", + "integrity": "sha512-tHX+RkpsXVcc7st4dSdDGliI+r4aAQDuv+v3vFYHixb6YgjreG5AG4SEB0kDK8u2s6htqEEpKlkhSBUTvWKYnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^9.0.0", + "jiti": "^2.5.1", + "semver": "^7.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/postcss-merge-longhand": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.5.tgz", + "integrity": "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^7.0.5" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-merge-rules": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.7.tgz", + "integrity": "sha512-njWJrd/Ms6XViwowaaCc+/vqhPG3SmXn725AGrnl+BgTuRPEacjiLEaGq16J6XirMJbtKkTwnt67SS+e2WGoew==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.27.0", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^5.0.1", + "postcss-selector-parser": "^7.1.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.1.tgz", + "integrity": "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.1.tgz", + "integrity": "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "colord": "^2.9.3", + "cssnano-utils": "^5.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-minify-params": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.5.tgz", + "integrity": "sha512-FGK9ky02h6Ighn3UihsyeAH5XmLEE2MSGH5Tc4tXMFtEDx7B+zTG6hD/+/cT+fbF7PbYojsmmWjyTwFwW1JKQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.27.0", + "cssnano-utils": "^5.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.5.tgz", + "integrity": "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "postcss-selector-parser": "^7.1.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.1.tgz", + "integrity": "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.1.tgz", + "integrity": "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.1.tgz", + "integrity": "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.1.tgz", + "integrity": "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-normalize-string": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.1.tgz", + "integrity": "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.1.tgz", + "integrity": "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.5.tgz", + "integrity": "sha512-X6BBwiRxVaFHrb2WyBMddIeB5HBjJcAaUHyhLrM2FsxSq5TFqcHSsK7Zu1otag+o0ZphQGJewGH1tAyrD0zX1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.27.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-normalize-url": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.1.tgz", + "integrity": "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.1.tgz", + "integrity": "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-ordered-values": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.2.tgz", + "integrity": "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-utils": "^5.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.5.tgz", + "integrity": "sha512-RHagHLidG8hTZcnr4FpyMB2jtgd/OcyAazjMhoy5qmWJOx1uxKh4ntk0Pb46ajKM0rkf32lRH4C8c9qQiPR6IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.27.0", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.1.tgz", + "integrity": "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.1.0.tgz", + "integrity": "sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^4.0.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >= 18" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz", + "integrity": "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.1.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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" + } + ], + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.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==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/reactflow": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz", + "integrity": "sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==", + "license": "MIT", + "dependencies": { + "@reactflow/background": "11.3.14", + "@reactflow/controls": "11.2.14", + "@reactflow/core": "11.11.4", + "@reactflow/minimap": "11.7.14", + "@reactflow/node-resizer": "2.2.14", + "@reactflow/node-toolbar": "1.3.14" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/rechoir/node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "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, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz", + "integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/x509": "^1.14.2", + "pkijs": "^3.3.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.8.0", + "mime-types": "~2.1.35", + "parseurl": "~1.3.3" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" + } + }, + "node_modules/stylehacks": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.7.tgz", + "integrity": "sha512-bJkD0JkEtbRrMFtwgpJyBbFIwfDDONQ1Ov3sDLZQP8HuJ73kBOyx66H4bOcAbVWmnfLdvQ0AJwXxOMkpujcO6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.27.0", + "postcss-selector-parser": "^7.1.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.32" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "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, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svgo": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", + "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^5.1.0", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "^1.4.1" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/svgo/node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/svgo/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/svgo/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/svgo/node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/svgo/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tailwindcss/node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/thingies": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", + "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "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, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/ts-loader": { + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", + "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, + "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, + "license": "MIT", + "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, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.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, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webpack": { + "version": "5.104.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", + "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.4", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.4.4", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", + "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.43.1", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/webpack-dev-server": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.3.tgz", + "integrity": "sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.25", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.8.1", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.22.1", + "graceful-fs": "^4.2.6", + "http-proxy-middleware": "^2.0.9", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^5.5.0", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "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, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "dev": true, + "license": "MIT", + "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/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + } + } +} diff --git a/web-ui/package.json b/web-ui/package.json new file mode 100644 index 00000000..c9307c69 --- /dev/null +++ b/web-ui/package.json @@ -0,0 +1,51 @@ +{ + "name": "assertoor-ui", + "version": "1.0.0", + "description": "Modern React web UI for Assertoor", + "private": true, + "scripts": { + "dev": "webpack serve --mode development", + "build": "webpack --mode production", + "build:dev": "webpack --mode development", + "lint": "eslint src", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@dnd-kit/core": "^6.1.0", + "@dnd-kit/sortable": "^8.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@tanstack/react-query": "^5.59.0", + "js-yaml": "^4.1.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.26.2", + "reactflow": "^11.11.4", + "zustand": "^4.5.5" + }, + "devDependencies": { + "@types/js-yaml": "^4.0.9", + "@types/react": "^18.3.8", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^8.6.0", + "@typescript-eslint/parser": "^8.6.0", + "autoprefixer": "^10.4.20", + "copy-webpack-plugin": "^12.0.2", + "css-loader": "^7.1.2", + "css-minimizer-webpack-plugin": "^7.0.0", + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.36.1", + "eslint-plugin-react-hooks": "^4.6.2", + "html-webpack-plugin": "^5.6.0", + "mini-css-extract-plugin": "^2.9.1", + "postcss": "^8.4.47", + "postcss-loader": "^8.1.1", + "style-loader": "^4.0.0", + "tailwindcss": "^3.4.12", + "terser-webpack-plugin": "^5.3.10", + "ts-loader": "^9.5.1", + "typescript": "^5.6.2", + "webpack": "^5.94.0", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^5.1.0" + } +} diff --git a/web-ui/postcss.config.js b/web-ui/postcss.config.js new file mode 100644 index 00000000..12a703d9 --- /dev/null +++ b/web-ui/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/web-ui/public/index.html b/web-ui/public/index.html new file mode 100644 index 00000000..35b5f4a9 --- /dev/null +++ b/web-ui/public/index.html @@ -0,0 +1,13 @@ + + + + + + + + Assertoor + + +
+ + diff --git a/web-ui/src/App.tsx b/web-ui/src/App.tsx new file mode 100644 index 00000000..c9bea5cf --- /dev/null +++ b/web-ui/src/App.tsx @@ -0,0 +1,26 @@ +import { Routes, Route } from 'react-router-dom'; +import { AuthProvider } from './context/AuthContext'; +import Layout from './components/common/Layout'; +import Dashboard from './pages/Dashboard'; +import TestRun from './pages/TestRun'; +import Registry from './pages/Registry'; +import TestPage from './pages/TestPage'; +import Clients from './pages/Clients'; + +function App() { + return ( + + + }> + } /> + } /> + } /> + } /> + } /> + + + + ); +} + +export default App; diff --git a/web-ui/src/api/client.ts b/web-ui/src/api/client.ts new file mode 100644 index 00000000..32f3fac9 --- /dev/null +++ b/web-ui/src/api/client.ts @@ -0,0 +1,153 @@ +import type { + ApiResponse, + TestRun, + Test, + TestDetails, + TestRunDetails, + TaskDetails, + TaskDescriptor, + ClientsPage, + ScheduleTestRunRequest, +} from '../types/api'; +import { authStore } from '../stores/authStore'; + +const API_BASE = '/api/v1'; + +async function fetchApi(endpoint: string, options?: RequestInit): Promise { + const response = await fetch(`${API_BASE}${endpoint}`, { + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + }, + }); + + if (!response.ok) { + throw new Error(`API error: ${response.status} ${response.statusText}`); + } + + const result: ApiResponse = await response.json(); + + if (result.status !== 'OK') { + throw new Error(result.status); + } + + return result.data; +} + +// Fetch API with Authorization header for protected endpoints +async function fetchApiWithAuth(endpoint: string, options?: RequestInit): Promise { + const authHeader = authStore.getAuthHeader(); + const headers: Record = { + 'Content-Type': 'application/json', + ...(options?.headers as Record), + }; + + if (authHeader) { + headers['Authorization'] = authHeader; + } + + const response = await fetch(`${API_BASE}${endpoint}`, { + ...options, + headers, + }); + + if (response.status === 401) { + throw new Error('Unauthorized: Please log in to perform this action'); + } + + if (!response.ok) { + throw new Error(`API error: ${response.status} ${response.statusText}`); + } + + const result: ApiResponse = await response.json(); + + if (result.status !== 'OK') { + throw new Error(result.status); + } + + return result.data; +} + +// Test runs list +export async function getTestRuns(testId?: string): Promise { + const params = testId ? `?test_id=${encodeURIComponent(testId)}` : ''; + return fetchApi(`/test_runs${params}`); +} + +// Tests (registry) list +export async function getTests(): Promise { + return fetchApi('/tests'); +} + +// Test details +export async function getTestDetails(testId: string): Promise { + return fetchApi(`/test/${encodeURIComponent(testId)}`); +} + +// Clients list +export async function getClients(): Promise { + return fetchApi('/clients'); +} + +// Test run details +export async function getTestRunDetails(runId: number): Promise { + return fetchApiWithAuth(`/test_run/${runId}/details`); +} + +// Task details +export async function getTaskDetails(runId: number, taskIndex: number): Promise { + return fetchApiWithAuth(`/test_run/${runId}/task/${taskIndex}/details`); +} + +// Task descriptors +export async function getTaskDescriptors(): Promise { + return fetchApi('/task_descriptors'); +} + +export async function getTaskDescriptor(name: string): Promise { + return fetchApi(`/task_descriptor/${encodeURIComponent(name)}`); +} + +// Admin operations (require authentication) +export async function scheduleTestRun(request: ScheduleTestRunRequest): Promise<{ run_id: number }> { + return fetchApiWithAuth<{ run_id: number }>('/test_runs/schedule', { + method: 'POST', + body: JSON.stringify(request), + }); +} + +export async function cancelTestRun(runId: number): Promise { + await fetchApiWithAuth(`/test_run/${runId}/cancel`, { + method: 'POST', + body: JSON.stringify({}), + }); +} + +export async function deleteTestRuns(runIds: number[]): Promise { + await fetchApiWithAuth('/test_runs/delete', { + method: 'POST', + body: JSON.stringify({ test_runs: runIds }), + }); +} + +export async function deleteTest(testId: string): Promise { + await fetchApiWithAuth('/tests/delete', { + method: 'POST', + body: JSON.stringify({ tests: [testId] }), + }); +} + +export async function registerTest(yaml: string): Promise { + await fetchApiWithAuth('/tests/register', { + method: 'POST', + body: JSON.stringify({ yaml }), + }); +} + +export async function registerExternalTest(url: string): Promise { + await fetchApiWithAuth('/tests/register_external', { + method: 'POST', + body: JSON.stringify({ file: url }), + }); +} diff --git a/web-ui/src/components/auth/UserDisplay.tsx b/web-ui/src/components/auth/UserDisplay.tsx new file mode 100644 index 00000000..751ef7fe --- /dev/null +++ b/web-ui/src/components/auth/UserDisplay.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { useAuthContext } from '../../context/AuthContext'; + +export const UserDisplay: React.FC = () => { + const { isLoggedIn, user, loading, login } = useAuthContext(); + + if (loading) { + return ( +
+
+
+ ); + } + + if (!isLoggedIn) { + return ( + + ); + } + + return ( +
+ + {user} +
+ ); +}; + +function LoginIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +function UserIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +export default UserDisplay; diff --git a/web-ui/src/components/common/Dropdown.tsx b/web-ui/src/components/common/Dropdown.tsx new file mode 100644 index 00000000..08e81221 --- /dev/null +++ b/web-ui/src/components/common/Dropdown.tsx @@ -0,0 +1,130 @@ +import { useState, useRef, useEffect, useCallback, useLayoutEffect } from 'react'; +import { createPortal } from 'react-dom'; + +interface DropdownProps { + trigger: React.ReactNode; + children: React.ReactNode; + align?: 'left' | 'right'; +} + +function Dropdown({ trigger, children, align = 'right' }: DropdownProps) { + const [isOpen, setIsOpen] = useState(false); + const [position, setPosition] = useState({ top: 0, left: 0 }); + const triggerRef = useRef(null); + const menuRef = useRef(null); + + const updatePosition = useCallback(() => { + if (!triggerRef.current) return; + + const rect = triggerRef.current.getBoundingClientRect(); + const menuWidth = 192; // w-48 = 12rem = 192px + const menuHeight = 200; // Approximate max height + + let top = rect.bottom + 4; + let left = align === 'right' ? rect.right - menuWidth : rect.left; + + // Ensure menu stays within viewport horizontally + if (left < 8) { + left = 8; + } else if (left + menuWidth > window.innerWidth - 8) { + left = window.innerWidth - menuWidth - 8; + } + + // If menu would go off bottom of screen, show it above the trigger + if (top + menuHeight > window.innerHeight - 8) { + top = rect.top - menuHeight - 4; + if (top < 8) { + top = 8; + } + } + + setPosition({ top, left }); + }, [align]); + + const handleToggle = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setIsOpen(prev => { + if (!prev) { + // Will update position in useLayoutEffect + } + return !prev; + }); + }, []); + + const handleClose = useCallback(() => { + setIsOpen(false); + }, []); + + // Update position when opening (useLayoutEffect to avoid flicker) + useLayoutEffect(() => { + if (isOpen) { + updatePosition(); + } + }, [isOpen, updatePosition]); + + // Close on outside click + useEffect(() => { + if (!isOpen) return; + + const handleClickOutside = (e: MouseEvent) => { + if ( + triggerRef.current && + !triggerRef.current.contains(e.target as Node) && + menuRef.current && + !menuRef.current.contains(e.target as Node) + ) { + handleClose(); + } + }; + + // Only close on window scroll, not on internal element scrolls + const handleWindowScroll = () => { + handleClose(); + }; + + document.addEventListener('mousedown', handleClickOutside); + window.addEventListener('scroll', handleWindowScroll); + window.addEventListener('resize', handleClose); + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + window.removeEventListener('scroll', handleWindowScroll); + window.removeEventListener('resize', handleClose); + }; + }, [isOpen, handleClose]); + + // Close on escape + useEffect(() => { + if (!isOpen) return; + + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + handleClose(); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [isOpen, handleClose]); + + return ( +
+
{trigger}
+ {isOpen && + createPortal( +
+ {children} +
, + document.body + )} +
+ ); +} + +export default Dropdown; diff --git a/web-ui/src/components/common/Layout.tsx b/web-ui/src/components/common/Layout.tsx new file mode 100644 index 00000000..d4e7b037 --- /dev/null +++ b/web-ui/src/components/common/Layout.tsx @@ -0,0 +1,113 @@ +import { Outlet, Link, useLocation } from 'react-router-dom'; +import { useTheme } from '../../hooks/useTheme'; +import { UserDisplay } from '../auth/UserDisplay'; + +const navItems = [ + { path: '/', label: 'Dashboard' }, + { path: '/registry', label: 'Registry' }, + { path: '/clients', label: 'Clients' }, +]; + +function Layout() { + const location = useLocation(); + const { theme, toggleTheme } = useTheme(); + + return ( +
+ {/* Header */} +
+
+
+ {/* Logo */} + +
+ A +
+ Assertoor + + + {/* Navigation */} + + + {/* Right side controls */} +
+ {/* User display */} + + + {/* Theme toggle */} + +
+
+
+
+ + {/* Main content */} +
+
+ +
+
+ + {/* Footer */} +
+
+

+ Assertoor - Ethereum Test Orchestration +

+
+
+
+ ); +} + +function SunIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +function MoonIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +export default Layout; diff --git a/web-ui/src/components/common/Modal.tsx b/web-ui/src/components/common/Modal.tsx new file mode 100644 index 00000000..5cc7540a --- /dev/null +++ b/web-ui/src/components/common/Modal.tsx @@ -0,0 +1,85 @@ +import { useEffect, useCallback } from 'react'; +import { createPortal } from 'react-dom'; + +interface ModalProps { + isOpen: boolean; + onClose: () => void; + title: string; + children: React.ReactNode; + size?: 'sm' | 'md' | 'lg' | 'xl'; +} + +function Modal({ isOpen, onClose, title, children, size = 'md' }: ModalProps) { + const handleEscape = useCallback( + (e: KeyboardEvent) => { + if (e.key === 'Escape') { + onClose(); + } + }, + [onClose] + ); + + useEffect(() => { + if (isOpen) { + document.addEventListener('keydown', handleEscape); + document.body.style.overflow = 'hidden'; + } + + return () => { + document.removeEventListener('keydown', handleEscape); + document.body.style.overflow = ''; + }; + }, [isOpen, handleEscape]); + + if (!isOpen) return null; + + const sizeClasses = { + sm: 'max-w-md', + md: 'max-w-lg', + lg: 'max-w-2xl', + xl: 'max-w-4xl', + }; + + const modalContent = ( +
+ {/* Backdrop */} +
+ + {/* Modal */} +
+ {/* Header */} +
+

{title}

+ +
+ + {/* Content */} +
{children}
+
+
+ ); + + // Use portal to render modal at document body level + // This ensures fixed positioning works relative to the viewport + return createPortal(modalContent, document.body); +} + +function CloseIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +export default Modal; diff --git a/web-ui/src/components/common/Pagination.tsx b/web-ui/src/components/common/Pagination.tsx new file mode 100644 index 00000000..e1efdcd6 --- /dev/null +++ b/web-ui/src/components/common/Pagination.tsx @@ -0,0 +1,107 @@ +interface PaginationProps { + currentPage: number; + totalPages: number; + onPageChange: (page: number) => void; +} + +function Pagination({ currentPage, totalPages, onPageChange }: PaginationProps) { + if (totalPages <= 1) { + return null; + } + + const pages = getPageNumbers(currentPage, totalPages); + + return ( + + ); +} + +function getPageNumbers(current: number, total: number): (number | '...')[] { + if (total <= 7) { + return Array.from({ length: total }, (_, i) => i + 1); + } + + const pages: (number | '...')[] = []; + + // Always show first page + pages.push(1); + + if (current > 3) { + pages.push('...'); + } + + // Pages around current + const start = Math.max(2, current - 1); + const end = Math.min(total - 1, current + 1); + + for (let i = start; i <= end; i++) { + pages.push(i); + } + + if (current < total - 2) { + pages.push('...'); + } + + // Always show last page + if (total > 1) { + pages.push(total); + } + + return pages; +} + +function ChevronLeftIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +function ChevronRightIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +export default Pagination; diff --git a/web-ui/src/components/common/SplitPane.tsx b/web-ui/src/components/common/SplitPane.tsx new file mode 100644 index 00000000..4251cfa5 --- /dev/null +++ b/web-ui/src/components/common/SplitPane.tsx @@ -0,0 +1,126 @@ +import { useState, useRef, useCallback, useEffect } from 'react'; + +interface SplitPaneProps { + left: React.ReactNode; + right: React.ReactNode; + defaultLeftWidth?: number; // percentage (0-100) + minLeftWidth?: number; // percentage + maxLeftWidth?: number; // percentage + storageKey?: string; +} + +function SplitPane({ + left, + right, + defaultLeftWidth = 50, + minLeftWidth = 20, + maxLeftWidth = 80, + storageKey, +}: SplitPaneProps) { + const [leftWidth, setLeftWidth] = useState(() => { + if (storageKey) { + const saved = localStorage.getItem(`splitPane:${storageKey}`); + if (saved) { + const parsed = parseFloat(saved); + if (!isNaN(parsed) && parsed >= minLeftWidth && parsed <= maxLeftWidth) { + return parsed; + } + } + } + return defaultLeftWidth; + }); + + const [isDragging, setIsDragging] = useState(false); + const containerRef = useRef(null); + + const handleMouseDown = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + setIsDragging(true); + }, []); + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!isDragging || !containerRef.current) return; + + const rect = containerRef.current.getBoundingClientRect(); + const newLeftWidth = ((e.clientX - rect.left) / rect.width) * 100; + const clampedWidth = Math.min(Math.max(newLeftWidth, minLeftWidth), maxLeftWidth); + setLeftWidth(clampedWidth); + }, + [isDragging, minLeftWidth, maxLeftWidth] + ); + + const handleMouseUp = useCallback(() => { + if (isDragging) { + setIsDragging(false); + if (storageKey) { + localStorage.setItem(`splitPane:${storageKey}`, leftWidth.toString()); + } + } + }, [isDragging, leftWidth, storageKey]); + + useEffect(() => { + if (isDragging) { + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + } + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + }; + }, [isDragging, handleMouseMove, handleMouseUp]); + + return ( + <> + {/* Mobile: Stacked layout */} +
+
{left}
+
{right}
+
+ + {/* Desktop: Split pane layout */} +
+ {/* Left panel */} +
+ {left} +
+ + {/* Divider */} +
+ {/* Drag handle indicator */} +
+
+ + {/* Right panel */} +
+ {right} +
+
+ + ); +} + +export default SplitPane; diff --git a/web-ui/src/components/common/StatusBadge.tsx b/web-ui/src/components/common/StatusBadge.tsx new file mode 100644 index 00000000..8edd662c --- /dev/null +++ b/web-ui/src/components/common/StatusBadge.tsx @@ -0,0 +1,39 @@ +import type { TestStatus, TaskStatus, TaskResult } from '../../types/api'; + +type Status = TestStatus | TaskStatus | TaskResult; + +interface StatusBadgeProps { + status: Status; + size?: 'sm' | 'md'; +} + +const statusConfig: Record = { + // Test statuses + pending: { label: 'Pending', className: 'status-pending' }, + running: { label: 'Running', className: 'status-running' }, + success: { label: 'Success', className: 'status-success' }, + failure: { label: 'Failure', className: 'status-failure' }, + aborted: { label: 'Aborted', className: 'status-aborted' }, + skipped: { label: 'Skipped', className: 'status-skipped' }, + // Task statuses + complete: { label: 'Complete', className: 'status-complete' }, + // Task results + none: { label: 'Pending', className: 'status-pending' }, +}; + +function StatusBadge({ status, size = 'md' }: StatusBadgeProps) { + const config = statusConfig[status] || statusConfig.pending; + + const sizeClasses = size === 'sm' ? 'px-1.5 py-0.5 text-xs' : 'px-2 py-1 text-sm'; + + return ( + + {status === 'running' && ( + + )} + {config.label} + + ); +} + +export default StatusBadge; diff --git a/web-ui/src/components/task/TaskDetails.tsx b/web-ui/src/components/task/TaskDetails.tsx new file mode 100644 index 00000000..273dd8dc --- /dev/null +++ b/web-ui/src/components/task/TaskDetails.tsx @@ -0,0 +1,222 @@ +import { useState } from 'react'; +import { useTaskDetails } from '../../hooks/useApi'; +import type { TaskState, TaskLogEntry } from '../../types/api'; +import { formatDurationMs, formatTime } from '../../utils/time'; + +interface TaskDetailsProps { + runId: number; + task: TaskState; +} + +function TaskDetails({ runId, task }: TaskDetailsProps) { + const [activeTab, setActiveTab] = useState<'overview' | 'logs' | 'config' | 'result'>('overview'); + const { data: details } = useTaskDetails(runId, task.index); + + const tabs = [ + { id: 'overview' as const, label: 'Overview' }, + { id: 'logs' as const, label: 'Logs' }, + { id: 'config' as const, label: 'Config' }, + { id: 'result' as const, label: 'Result' }, + ]; + + return ( +
+ {/* Tabs */} +
+ {tabs.map((tab) => ( + + ))} +
+ + {/* Tab content */} +
+ {activeTab === 'overview' && } + {activeTab === 'logs' && } + {activeTab === 'config' && } + {activeTab === 'result' && } +
+
+ ); +} + +function OverviewTab({ task }: { task: TaskState }) { + // Calculate duration + const duration = task.runtime || 0; + + // Determine status display + const getStatusText = () => { + if (task.status === 'running') return 'Running'; + if (task.status === 'complete') { + if (task.result === 'success') return 'Success'; + if (task.result === 'failure') return 'Failure'; + return 'Complete'; + } + return 'Pending'; + }; + + return ( +
+
+

Task Name

+

{task.name}

+
+ + {task.title && task.title !== task.name && ( +
+

Title

+

{task.title}

+
+ )} + +
+

Status

+

{getStatusText()}

+
+ + {task.started && ( +
+

Duration

+

{formatDurationMs(duration)}

+
+ )} + + {task.timeout > 0 && ( +
+

Timeout

+

{formatDurationMs(task.timeout)}

+
+ )} + + {task.result_error && ( +
+

Error

+

+ {task.result_error} +

+
+ )} + + {task.result_files && task.result_files.length > 0 && ( +
+

Result Files

+ +
+ )} +
+ ); +} + +function LogsTab({ logs }: { logs: TaskLogEntry[] }) { + if (logs.length === 0) { + return ( +

No logs available

+ ); + } + + const getLevelStyle = (level: string): { class: string; label: string } => { + switch (level) { + case 'trace': + return { class: 'text-gray-400', label: 'TRC' }; + case 'debug': + return { class: 'text-gray-500', label: 'DBG' }; + case 'info': + return { class: 'text-blue-500', label: 'INF' }; + case 'warn': + return { class: 'text-yellow-500', label: 'WRN' }; + case 'error': + return { class: 'text-red-500', label: 'ERR' }; + case 'fatal': + case 'panic': + return { class: 'text-red-600 font-bold', label: level.toUpperCase().slice(0, 3) }; + default: + return { class: 'text-gray-500', label: level.toUpperCase().slice(0, 3) }; + } + }; + + return ( +
+ {logs.map((log, i) => { + const style = getLevelStyle(log.level); + return ( +
+ + {formatTime(log.time)} + + + [{style.label}] + + {log.msg} + {log.datalen > 0 && ( +
+ {Object.entries(log.data).map(([key, value]) => ( + + {key}: {value} + + ))} +
+ )} +
+ ); + })} +
+ ); +} + +function ConfigTab({ yaml }: { yaml?: string }) { + if (!yaml) { + return ( +

No configuration available

+ ); + } + + return ( +
+      {yaml}
+    
+ ); +} + +function ResultTab({ yaml }: { yaml?: string }) { + if (!yaml) { + return ( +

No result data available

+ ); + } + + return ( +
+      {yaml}
+    
+ ); +} + +function formatFileSize(bytes: number): string { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; +} + +export default TaskDetails; diff --git a/web-ui/src/components/task/TaskList.tsx b/web-ui/src/components/task/TaskList.tsx new file mode 100644 index 00000000..d7be0adf --- /dev/null +++ b/web-ui/src/components/task/TaskList.tsx @@ -0,0 +1,337 @@ +import { useMemo, useState, useEffect, useCallback } from 'react'; +import type { TaskState } from '../../types/api'; +import StatusBadge from '../common/StatusBadge'; + +interface TaskListProps { + tasks: TaskState[]; + selectedIndex: number | null; + onSelect: (index: number) => void; +} + +interface ProcessedTask extends TaskState { + depth: number; + hasChildren: boolean; + childCount: number; +} + +function TaskList({ tasks, selectedIndex, onSelect }: TaskListProps) { + const [collapsedTasks, setCollapsedTasks] = useState>(new Set()); + + // Build tree structure with hierarchical ordering + const { processedTasks, childrenMap } = useMemo(() => { + // Build a map of parent -> children + const childrenMap = new Map(); + const taskMap = new Map(); + const rootTasks: TaskState[] = []; + + // First pass: index all tasks and find roots + for (const task of tasks) { + taskMap.set(task.index, task); + const parentIndex = task.parent_index; + + if (parentIndex < 0 || parentIndex === task.index || !tasks.some((t) => t.index === parentIndex)) { + // Root task (no parent, or parent doesn't exist) + rootTasks.push(task); + } else { + // Child task + const siblings = childrenMap.get(parentIndex) || []; + siblings.push(task); + childrenMap.set(parentIndex, siblings); + } + } + + // Sort root tasks by index + rootTasks.sort((a, b) => a.index - b.index); + + // Sort all children arrays by index + childrenMap.forEach((children) => { + children.sort((a, b) => a.index - b.index); + }); + + // Count all descendants for each task + const countDescendants = (taskIndex: number): number => { + const children = childrenMap.get(taskIndex) || []; + let count = children.length; + for (const child of children) { + count += countDescendants(child.index); + } + return count; + }; + + // Flatten the tree with DFS traversal to get hierarchical order + const result: ProcessedTask[] = []; + + const traverse = (task: TaskState, depth: number) => { + const children = childrenMap.get(task.index) || []; + const childCount = countDescendants(task.index); + result.push({ + ...task, + depth, + hasChildren: children.length > 0, + childCount, + }); + for (const child of children) { + traverse(child, depth + 1); + } + }; + + for (const rootTask of rootTasks) { + traverse(rootTask, 0); + } + + return { processedTasks: result, childrenMap }; + }, [tasks]); + + // Filter out tasks whose ancestors are collapsed + const visibleTasks = useMemo(() => { + if (collapsedTasks.size === 0) return processedTasks; + + // Build a set of all hidden task indices (children of collapsed tasks) + const hiddenTasks = new Set(); + + const hideDescendants = (taskIndex: number) => { + const children = childrenMap.get(taskIndex) || []; + for (const child of children) { + hiddenTasks.add(child.index); + hideDescendants(child.index); + } + }; + + for (const collapsedIndex of collapsedTasks) { + hideDescendants(collapsedIndex); + } + + return processedTasks.filter((task) => !hiddenTasks.has(task.index)); + }, [processedTasks, collapsedTasks, childrenMap]); + + const toggleCollapse = useCallback((taskIndex: number, e: React.MouseEvent) => { + e.stopPropagation(); + setCollapsedTasks((prev) => { + const next = new Set(prev); + if (next.has(taskIndex)) { + next.delete(taskIndex); + } else { + next.add(taskIndex); + } + return next; + }); + }, []); + + const collapseAll = useCallback(() => { + const tasksWithChildren = processedTasks + .filter((t) => t.hasChildren) + .map((t) => t.index); + setCollapsedTasks(new Set(tasksWithChildren)); + }, [processedTasks]); + + const expandAll = useCallback(() => { + setCollapsedTasks(new Set()); + }, []); + + const hasCollapsibleTasks = processedTasks.some((t) => t.hasChildren); + + return ( +
+ {/* Collapse/Expand controls */} + {hasCollapsibleTasks && ( +
+ + | + +
+ )} + + {/* Task list */} +
+ {visibleTasks.map((task) => ( + onSelect(task.index)} + onToggleCollapse={(e) => toggleCollapse(task.index, e)} + /> + ))} +
+
+ ); +} + +interface TaskListItemProps { + task: ProcessedTask; + depth: number; + isSelected: boolean; + isCollapsed: boolean; + onClick: () => void; + onToggleCollapse: (e: React.MouseEvent) => void; +} + +function TaskListItem({ task, depth, isSelected, isCollapsed, onClick, onToggleCollapse }: TaskListItemProps) { + // Determine display status based on task state + const getDisplayStatus = (): 'pending' | 'running' | 'success' | 'failure' => { + if (task.status === 'running') return 'running'; + if (task.status === 'complete') { + if (task.result === 'success') return 'success'; + if (task.result === 'failure') return 'failure'; + } + return 'pending'; + }; + + const displayStatus = getDisplayStatus(); + const isRunning = task.status === 'running'; + const hasProgress = task.progress > 0 && task.progress < 100; + + return ( + + ); +} + +function ChevronIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +interface TaskRuntimeProps { + task: TaskState; +} + +function TaskRuntime({ task }: TaskRuntimeProps) { + const [now, setNow] = useState(Date.now()); + + // Update every second for running tasks + useEffect(() => { + if (task.status !== 'running') return; + + const interval = setInterval(() => { + setNow(Date.now()); + }, 1000); + + return () => clearInterval(interval); + }, [task.status]); + + // Calculate runtime + const getRuntimeMs = (): number => { + if (task.status === 'running' && task.start_time > 0) { + // For running tasks, calculate from start_time (in ms) to now + return now - task.start_time; + } + // For completed tasks, use the pre-calculated runtime (already in ms) + return task.runtime || 0; + }; + + const runtimeMs = getRuntimeMs(); + + // Don't show if no runtime + if (runtimeMs <= 0 && task.status === 'pending') { + return null; + } + + return ( + + {formatRuntimeCompact(runtimeMs)} + + ); +} + +function formatRuntimeCompact(ms: number): string { + if (ms < 1000) { + return `${ms}ms`; + } + + const seconds = Math.floor(ms / 1000); + if (seconds < 60) { + return `${seconds}s`; + } + + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + if (minutes < 60) { + return `${minutes}m ${remainingSeconds}s`; + } + + const hours = Math.floor(minutes / 60); + const remainingMinutes = minutes % 60; + return `${hours}h ${remainingMinutes}m`; +} + +export default TaskList; diff --git a/web-ui/src/components/test/StartTestModal.tsx b/web-ui/src/components/test/StartTestModal.tsx new file mode 100644 index 00000000..c8d3136e --- /dev/null +++ b/web-ui/src/components/test/StartTestModal.tsx @@ -0,0 +1,475 @@ +import { useState, useEffect, useMemo, useCallback } from 'react'; +import { useTests, useTestDetails, useScheduleTestRun } from '../../hooks/useApi'; +import Modal from '../common/Modal'; +import yaml from 'js-yaml'; + +interface StartTestModalProps { + isOpen: boolean; + onClose: () => void; + initialTestId?: string | null; +} + +type ConfigValue = string | number | boolean | null | undefined | Record | unknown[]; + +interface ConfigFormValues { + [key: string]: ConfigValue; +} + +function StartTestModal({ isOpen, onClose, initialTestId }: StartTestModalProps) { + const [selectedTestId, setSelectedTestId] = useState(initialTestId || ''); + const [step, setStep] = useState<'select' | 'configure'>(initialTestId ? 'configure' : 'select'); + const [editMode, setEditMode] = useState<'form' | 'yaml'>('form'); + const [formConfig, setFormConfig] = useState({}); + const [yamlConfig, setYamlConfig] = useState(''); + const [allowDuplicate, setAllowDuplicate] = useState(false); + const [skipQueue, setSkipQueue] = useState(false); + const [error, setError] = useState(null); + + const { data: tests, isLoading: testsLoading } = useTests(); + const { data: testDetails, isLoading: detailsLoading } = useTestDetails(selectedTestId, { + enabled: !!selectedTestId, + }); + const scheduleMutation = useScheduleTestRun(); + + // Reset state when modal opens/closes + useEffect(() => { + if (isOpen) { + if (initialTestId) { + setSelectedTestId(initialTestId); + setStep('configure'); + } else { + setSelectedTestId(''); + setStep('select'); + } + setEditMode('form'); + setFormConfig({}); + setYamlConfig(''); + setAllowDuplicate(false); + setSkipQueue(false); + setError(null); + } + }, [isOpen, initialTestId]); + + // Initialize config when test details are loaded + useEffect(() => { + if (testDetails?.config) { + const initialConfig: ConfigFormValues = {}; + for (const [key, value] of Object.entries(testDetails.config)) { + initialConfig[key] = value as ConfigValue; + } + setFormConfig(initialConfig); + setYamlConfig(yaml.dump(initialConfig)); + } else { + setFormConfig({}); + setYamlConfig(''); + } + }, [testDetails]); + + // Sync form config to yaml when switching modes + const handleModeSwitch = useCallback((mode: 'form' | 'yaml') => { + if (mode === 'yaml' && editMode === 'form') { + // Switching from form to yaml - serialize current form values + try { + setYamlConfig(yaml.dump(formConfig)); + } catch { + // Keep existing yaml if serialization fails + } + } else if (mode === 'form' && editMode === 'yaml') { + // Switching from yaml to form - parse yaml + try { + const parsed = yaml.load(yamlConfig) as ConfigFormValues; + if (parsed && typeof parsed === 'object') { + setFormConfig(parsed); + } + } catch { + setError('Invalid YAML syntax. Please fix before switching to form mode.'); + return; + } + } + setError(null); + setEditMode(mode); + }, [editMode, formConfig, yamlConfig]); + + const handleTestSelect = useCallback(() => { + if (selectedTestId) { + setStep('configure'); + } + }, [selectedTestId]); + + const handleBack = useCallback(() => { + if (initialTestId) { + // If started with a specific test, close instead of going back + onClose(); + } else { + setStep('select'); + setError(null); + } + }, [initialTestId, onClose]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + + try { + // Get config values based on current edit mode + let config: Record | undefined; + + if (editMode === 'yaml') { + if (yamlConfig.trim()) { + try { + config = yaml.load(yamlConfig) as Record; + } catch (err) { + setError(`Invalid YAML: ${err instanceof Error ? err.message : 'Parse error'}`); + return; + } + } + } else { + if (Object.keys(formConfig).length > 0) { + config = formConfig as Record; + } + } + + const result = await scheduleMutation.mutateAsync({ + test_id: selectedTestId, + config: config && Object.keys(config).length > 0 ? config : undefined, + allow_duplicate: allowDuplicate, + skip_queue: skipQueue, + }); + + // Navigate to the new test run + window.location.href = `/run/${result.run_id}`; + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to schedule test'); + } + }; + + const updateFormValue = useCallback((key: string, value: ConfigValue) => { + setFormConfig((prev) => ({ ...prev, [key]: value })); + }, []); + + const selectedTest = useMemo(() => { + return tests?.find((t) => t.id === selectedTestId); + }, [tests, selectedTestId]); + + const hasConfig = testDetails?.config && Object.keys(testDetails.config).length > 0; + + const handleClose = () => { + onClose(); + }; + + return ( + + {step === 'select' ? ( +
+
+ + {testsLoading ? ( +
+
+
+ ) : ( + + )} +
+ + {selectedTestId && selectedTest && ( +
+
+ Test ID: + {selectedTestId} +
+
+ Source: + + {selectedTest.source} + +
+
+ )} + +
+ + +
+
+ ) : ( +
+ {detailsLoading ? ( +
+
+
+ ) : ( + <> + {/* Test info */} +
+
+ Test ID: + {selectedTestId} +
+ {testDetails?.timeout && testDetails.timeout > 0 && ( +
+ Default Timeout: + {formatTimeout(testDetails.timeout)} +
+ )} +
+ + {/* Configuration section */} + {hasConfig && ( +
+
+

Configuration Variables

+
+ + +
+
+ + {editMode === 'form' ? ( +
+ {testDetails?.config && + Object.entries(testDetails.config).map(([key, defaultValue]) => ( + updateFormValue(key, value)} + /> + ))} +
+ ) : ( +
+ ",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Me(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return R(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return R(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0r;e=0<=r?++n:--n){i[t.LIST_ESCAPEES[e]]=t.LIST_ESCAPED[e]}return i}();t.PATTERN_CHARACTERS_TO_ESCAPE=new r("[\\x00-\\x1f]|…| |
|
");t.PATTERN_MAPPING_ESCAPEES=new r(t.LIST_ESCAPEES.join("|").split("\\").join("\\\\"));t.PATTERN_SINGLE_QUOTING=new r("[\\s'\":{}[\\],&*#?]|^[-?|<>=!%@`]");t.requiresDoubleQuoting=function(e){return this.PATTERN_CHARACTERS_TO_ESCAPE.test(e)};t.escapeWithDoubleQuotes=function(e){var t;t=this.PATTERN_MAPPING_ESCAPEES.replace(e,function(e){return function(t){return e.MAPPING_ESCAPEES_TO_ESCAPED[t]}}(this));return'"'+t+'"'};t.requiresSingleQuoting=function(e){return this.PATTERN_SINGLE_QUOTING.test(e)};t.escapeWithSingleQuotes=function(e){return"'"+e.replace(/'/g,"''")+"'"};return t}();t.exports=i},{"./Pattern":8}],3:[function(e,t,n){var i,r=function(e,t){for(var n in t){if(s.call(t,n))e[n]=t[n]}function i(){this.constructor=e}i.prototype=t.prototype;e.prototype=new i;e.__super__=t.prototype;return e},s={}.hasOwnProperty;i=function(e){r(t,e);function t(e,t,n){this.message=e;this.parsedLine=t;this.snippet=n}t.prototype.toString=function(){if(this.parsedLine!=null&&this.snippet!=null){return" "+this.message+" (line "+this.parsedLine+": '"+this.snippet+"')"}else{return" "+this.message}};return t}(Error);t.exports=i},{}],4:[function(e,t,n){var i,r=function(e,t){for(var n in t){if(s.call(t,n))e[n]=t[n]}function i(){this.constructor=e}i.prototype=t.prototype;e.prototype=new i;e.__super__=t.prototype;return e},s={}.hasOwnProperty;i=function(e){r(t,e);function t(e,t,n){this.message=e;this.parsedLine=t;this.snippet=n}t.prototype.toString=function(){if(this.parsedLine!=null&&this.snippet!=null){return" "+this.message+" (line "+this.parsedLine+": '"+this.snippet+"')"}else{return" "+this.message}};return t}(Error);t.exports=i},{}],5:[function(e,t,n){var i,r=function(e,t){for(var n in t){if(s.call(t,n))e[n]=t[n]}function i(){this.constructor=e}i.prototype=t.prototype;e.prototype=new i;e.__super__=t.prototype;return e},s={}.hasOwnProperty;i=function(e){r(t,e);function t(e,t,n){this.message=e;this.parsedLine=t;this.snippet=n}t.prototype.toString=function(){if(this.parsedLine!=null&&this.snippet!=null){return" "+this.message+" (line "+this.parsedLine+": '"+this.snippet+"')"}else{return" "+this.message}};return t}(Error);t.exports=i},{}],6:[function(e,t,n){var i,r,s,l,u,a,o,f,c=[].indexOf||function(e){for(var t=0,n=this.length;t=0){h=this.parseQuotedScalar(e,i);s=i.i;if(t!=null){A=f.ltrim(e.slice(s)," ");if(!(T=A.charAt(0),c.call(t,T)>=0)){throw new l("Unexpected characters ("+e.slice(s)+").")}}}else{if(!t){h=e.slice(s);s+=h.length;_=h.indexOf(" #");if(_!==-1){h=f.rtrim(h.slice(0,_))}}else{u=t.join("|");p=this.PATTERN_SCALAR_BY_DELIMITERS[u];if(p==null){p=new a("^(.+?)("+u+")");this.PATTERN_SCALAR_BY_DELIMITERS[u]=p}if(o=p.exec(e.slice(s))){h=o[1];s+=h.length}else{throw new l("Malformed inline YAML string ("+e+").")}}if(r){h=this.evaluateScalar(h,i)}}i.i=s;return h};e.parseQuotedScalar=function(e,t){var n,i,r;n=t.i;if(!(i=this.PATTERN_QUOTED_SCALAR.exec(e.slice(n)))){throw new u("Malformed inline YAML string ("+e.slice(n)+").")}r=i[0].substr(1,i[0].length-2);if('"'===e.charAt(n)){r=o.unescapeDoubleQuotedString(r)}else{r=o.unescapeSingleQuotedString(r)}n+=i[0].length;t.i=n;return r};e.parseSequence=function(e,t){var n,i,r,s,l,a,o,f;a=[];l=e.length;r=t.i;r+=1;while(r0)){p=null}return o(E.slice(0,u),p)}}if(r){throw new l("Custom object support when parsing a YAML file has been disabled.")}return null}break;case"0":if("0x"===e.slice(0,2)){return f.hexDec(e)}else if(f.isDigits(e)){return f.octDec(e)}else if(f.isNumeric(e)){return parseFloat(e)}else{return e}break;case"+":if(f.isDigits(e)){c=e;n=parseInt(c);if(c===String(n)){return n}else{return c}}else if(f.isNumeric(e)){return parseFloat(e)}else if(this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(e)){return parseFloat(e.replace(",",""))}return e;case"-":if(f.isDigits(e.slice(1))){if("0"===e.charAt(1)){return-f.octDec(e.slice(1))}else{c=e.slice(1);n=parseInt(c);if(c===String(n)){return-n}else{return-c}}}else if(f.isNumeric(e)){return parseFloat(e)}else if(this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(e)){return parseFloat(e.replace(",",""))}return e;default:if(i=f.stringToDate(e)){return i}else if(f.isNumeric(e)){return parseFloat(e)}else if(this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(e)){return parseFloat(e.replace(",",""))}return e}}};return e}();t.exports=s},{"./Escaper":2,"./Exception/DumpException":3,"./Exception/ParseException":4,"./Exception/ParseMore":5,"./Pattern":8,"./Unescaper":9,"./Utils":10}],7:[function(e,t,n){var i,r,s,l,u,a;i=e("./Inline");u=e("./Pattern");a=e("./Utils");r=e("./Exception/ParseException");s=e("./Exception/ParseMore");l=function(){e.prototype.PATTERN_FOLDED_SCALAR_ALL=new u("^(?:(?![^\\|>]*)\\s+)?(?\\||>)(?\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(? +#.*)?$");e.prototype.PATTERN_FOLDED_SCALAR_END=new u("(?\\||>)(?\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(? +#.*)?$");e.prototype.PATTERN_SEQUENCE_ITEM=new u("^\\-((?\\s+)(?.+?))?\\s*$");e.prototype.PATTERN_ANCHOR_VALUE=new u("^&(?[^ ]+) *(?.*)");e.prototype.PATTERN_COMPACT_NOTATION=new u("^(?"+i.REGEX_QUOTED_STRING+"|[^ '\"\\{\\[].*?) *\\:(\\s+(?.+?))?\\s*$");e.prototype.PATTERN_MAPPING_ITEM=new u("^(?"+i.REGEX_QUOTED_STRING+"|[^ '\"\\[\\{].*?) *\\:(\\s+(?.+?))?\\s*$");e.prototype.PATTERN_DECIMAL=new u("\\d+");e.prototype.PATTERN_INDENT_SPACES=new u("^ +");e.prototype.PATTERN_TRAILING_LINES=new u("(\n*)$");e.prototype.PATTERN_YAML_HEADER=new u("^\\%YAML[: ][\\d\\.]+.*\n","m");e.prototype.PATTERN_LEADING_COMMENTS=new u("^(\\#.*?\n)+","m");e.prototype.PATTERN_DOCUMENT_MARKER_START=new u("^\\-\\-\\-.*?\n","m");e.prototype.PATTERN_DOCUMENT_MARKER_END=new u("^\\.\\.\\.\\s*$","m");e.prototype.PATTERN_FOLDED_SCALAR_BY_INDENTATION={};e.prototype.CONTEXT_NONE=0;e.prototype.CONTEXT_SEQUENCE=1;e.prototype.CONTEXT_MAPPING=2;function e(e){this.offset=e!=null?e:0;this.lines=[];this.currentLineNb=-1;this.currentLine="";this.refs={}}e.prototype.parse=function(t,n,s){var l,u,o,f,c,h,p,E,T,_,A,L,d,N,g,R,x,C,I,m,S,w,v,y,P,b,D,O,M,G,U,X,F,k,H,j,Y,B,Q;if(n==null){n=false}if(s==null){s=null}this.currentLineNb=-1;this.currentLine="";this.lines=this.cleanup(t).split("\n");h=null;c=this.CONTEXT_NONE;u=false;while(this.moveToNextLine()){if(this.isCurrentLineEmpty()){continue}if("\t"===this.currentLine[0]){throw new r("A YAML file cannot contain tabs as indentation.",this.getRealCurrentLineNb()+1,this.currentLine)}N=D=false;if(Q=this.PATTERN_SEQUENCE_ITEM.exec(this.currentLine)){if(this.CONTEXT_MAPPING===c){throw new r("You cannot define a sequence item when in a mapping")}c=this.CONTEXT_SEQUENCE;if(h==null){h=[]}if(Q.value!=null&&(b=this.PATTERN_ANCHOR_VALUE.exec(Q.value))){N=b.ref;Q.value=b.value}if(!(Q.value!=null)||""===a.trim(Q.value," ")||a.ltrim(Q.value," ").indexOf("#")===0){if(this.currentLineNb=l){n.push(this.currentLine.slice(l))}else if(a.ltrim(this.currentLine).charAt(0)==="#"){}else if(0===i){this.moveToPreviousLine();break}else{throw new r("Indentation problem.",this.getRealCurrentLineNb()+1,this.currentLine)}}return n.join("\n")};e.prototype.moveToNextLine=function(){if(this.currentLineNb>=this.lines.length-1){return false}this.currentLine=this.lines[++this.currentLineNb];return true};e.prototype.moveToPreviousLine=function(){this.currentLine=this.lines[--this.currentLineNb]};e.prototype.parseValue=function(e,t,n){var l,u,o,f,c,h,p,E,T;if(0===e.indexOf("*")){h=e.indexOf("#");if(h!==-1){e=e.substr(1,h-2)}else{e=e.slice(1)}if(this.refs[e]===void 0){throw new r('Reference "'+e+'" does not exist.',this.currentLine)}return this.refs[e]}if(f=this.PATTERN_FOLDED_SCALAR_ALL.exec(e)){c=(p=f.modifiers)!=null?p:"";o=Math.abs(parseInt(c));if(isNaN(o)){o=0}T=this.parseFoldedScalar(f.separator,this.PATTERN_DECIMAL.replace(c,""),o);if(f.type!=null){i.configure(t,n);return i.parseScalar(f.type+" "+T)}else{return T}}if((E=e.charAt(0))==="["||E==="{"||E==='"'||E==="'"){while(true){try{return i.parse(e,t,n)}catch(u){l=u;if(l instanceof s&&this.moveToNextLine()){e+="\n"+a.trim(this.currentLine," ")}else{l.parsedLine=this.getRealCurrentLineNb()+1;l.snippet=this.currentLine;throw l}}}}else{if(this.isNextLineIndented()){e+="\n"+this.getNextEmbedBlock()}return i.parse(e,t,n)}};e.prototype.parseFoldedScalar=function(t,n,i){var r,s,l,o,f,c,h,p,E,T;if(n==null){n=""}if(i==null){i=0}h=this.moveToNextLine();if(!h){return""}r=this.isCurrentLineBlank();T="";while(h&&r){if(h=this.moveToNextLine()){T+="\n";r=this.isCurrentLineBlank()}}if(0===i){if(f=this.PATTERN_INDENT_SPACES.exec(this.currentLine)){i=f[0].length}}if(i>0){p=this.PATTERN_FOLDED_SCALAR_BY_INDENTATION[i];if(p==null){p=new u("^ {"+i+"}(.*)$");e.prototype.PATTERN_FOLDED_SCALAR_BY_INDENTATION[i]=p}while(h&&(r||(f=p.exec(this.currentLine)))){if(r){T+=this.currentLine.slice(i)}else{T+=f[1]}if(h=this.moveToNextLine()){T+="\n";r=this.isCurrentLineBlank()}}}else if(h){T+="\n"}if(h){this.moveToPreviousLine()}if(">"===t){c="";E=T.split("\n");for(s=0,l=E.length;sn){i=true}this.moveToPreviousLine();return i};e.prototype.isCurrentLineEmpty=function(){var e;e=a.trim(this.currentLine," ");return e.length===0||e.charAt(0)==="#"};e.prototype.isCurrentLineBlank=function(){return""===a.trim(this.currentLine," ")};e.prototype.isCurrentLineComment=function(){var e;e=a.ltrim(this.currentLine," ");return e.charAt(0)==="#"};e.prototype.cleanup=function(e){var t,n,i,r,s,l,u,o,f,c,h,p,E,T;if(e.indexOf("\r")!==-1){e=e.split("\r\n").join("\n").split("\r").join("\n")}t=0;c=this.PATTERN_YAML_HEADER.replaceAll(e,""),e=c[0],t=c[1];this.offset+=t;h=this.PATTERN_LEADING_COMMENTS.replaceAll(e,"",1),T=h[0],t=h[1];if(t===1){this.offset+=a.subStrCount(e,"\n")-a.subStrCount(T,"\n");e=T}p=this.PATTERN_DOCUMENT_MARKER_START.replaceAll(e,"",1),T=p[0],t=p[1];if(t===1){this.offset+=a.subStrCount(e,"\n")-a.subStrCount(T,"\n");e=T;e=this.PATTERN_DOCUMENT_MARKER_END.replace(e,"")}f=e.split("\n");E=-1;for(r=0,l=f.length;r0){for(n=s=0,u=f.length;s"){r+="(";s++;if(a.length>0){if(u==null){u={}}u[a]=i}break}else{a+=f}s++}}else{r+=n;i++}}else{r+=n}}else{r+=n}s++}this.rawRegex=e;this.cleanedRegex=r;this.regex=new RegExp(this.cleanedRegex,"g"+t.replace("g",""));this.mapping=u}e.prototype.exec=function(e){var t,n,i,r;this.regex.lastIndex=0;n=this.regex.exec(e);if(n==null){return null}if(this.mapping!=null){r=this.mapping;for(i in r){t=r[i];n[i]=n[t]}}return n};e.prototype.test=function(e){this.regex.lastIndex=0;return this.regex.test(e)};e.prototype.replace=function(e,t){this.regex.lastIndex=0;return e.replace(this.regex,t)};e.prototype.replaceAll=function(e,t,n){var i;if(n==null){n=0}this.regex.lastIndex=0;i=0;while(this.regex.test(e)&&(n===0||i[0-9][0-9][0-9][0-9])"+"-(?[0-9][0-9]?)"+"-(?[0-9][0-9]?)"+"(?:(?:[Tt]|[ \t]+)"+"(?[0-9][0-9]?)"+":(?[0-9][0-9])"+":(?[0-9][0-9])"+"(?:.(?[0-9]*))?"+"(?:[ \t]*(?Z|(?[-+])(?[0-9][0-9]?)"+"(?::(?[0-9][0-9]))?))?)?"+"$","i");t.LOCAL_TIMEZONE_OFFSET=(new Date).getTimezoneOffset()*60*1e3;t.trim=function(e,t){var n,i;if(t==null){t="\\s"}n=this.REGEX_LEFT_TRIM_BY_CHAR[t];if(n==null){this.REGEX_LEFT_TRIM_BY_CHAR[t]=n=new RegExp("^"+t+""+t+"*")}n.lastIndex=0;i=this.REGEX_RIGHT_TRIM_BY_CHAR[t];if(i==null){this.REGEX_RIGHT_TRIM_BY_CHAR[t]=i=new RegExp(t+""+t+"*$")}i.lastIndex=0;return e.replace(n,"").replace(i,"")};t.ltrim=function(e,t){var n;if(t==null){t="\\s"}n=this.REGEX_LEFT_TRIM_BY_CHAR[t];if(n==null){this.REGEX_LEFT_TRIM_BY_CHAR[t]=n=new RegExp("^"+t+""+t+"*")}n.lastIndex=0;return e.replace(n,"")};t.rtrim=function(e,t){var n;if(t==null){t="\\s"}n=this.REGEX_RIGHT_TRIM_BY_CHAR[t];if(n==null){this.REGEX_RIGHT_TRIM_BY_CHAR[t]=n=new RegExp(t+""+t+"*$")}n.lastIndex=0;return e.replace(n,"")};t.isEmpty=function(e){return!e||e===""||e==="0"||e instanceof Array&&e.length===0||this.isEmptyObject(e)};t.isEmptyObject=function(e){var t;return e instanceof Object&&function(){var n;n=[];for(t in e){if(!s.call(e,t))continue;n.push(t)}return n}().length===0};t.subStrCount=function(e,t,n,i){var r,s,l,u,a,o;r=0;e=""+e;t=""+t;if(n!=null){e=e.slice(n)}if(i!=null){e=e.slice(0,i)}u=e.length;o=t.length;for(s=l=0,a=u;0<=a?la;s=0<=a?++l:--l){if(t===e.slice(s,o)){r++;s+=o-1}}return r};t.isDigits=function(e){this.REGEX_DIGITS.lastIndex=0;return this.REGEX_DIGITS.test(e)};t.octDec=function(e){this.REGEX_OCTAL.lastIndex=0;return parseInt((e+"").replace(this.REGEX_OCTAL,""),8)};t.hexDec=function(e){this.REGEX_HEXADECIMAL.lastIndex=0;e=this.trim(e);if((e+"").slice(0,2)==="0x"){e=(e+"").slice(2)}return parseInt((e+"").replace(this.REGEX_HEXADECIMAL,""),16)};t.utf8chr=function(e){var t;t=String.fromCharCode;if(128>(e%=2097152)){return t(e)}if(2048>e){return t(192|e>>6)+t(128|e&63)}if(65536>e){return t(224|e>>12)+t(128|e>>6&63)+t(128|e&63)}return t(240|e>>18)+t(128|e>>12&63)+t(128|e>>6&63)+t(128|e&63)};t.parseBoolean=function(e,t){var n;if(t==null){t=true}if(typeof e==="string"){n=e.toLowerCase();if(!t){if(n==="no"){return false}}if(n==="0"){return false}if(n==="false"){return false}if(n===""){return false}return true}return!!e};t.isNumeric=function(e){this.REGEX_SPACES.lastIndex=0;return typeof e==="number"||typeof e==="string"&&!isNaN(e)&&e.replace(this.REGEX_SPACES,"")!==""};t.stringToDate=function(e){var t,n,i,r,s,l,u,a,o,f,c,h;if(!(e!=null?e.length:void 0)){return null}s=this.PATTERN_DATE.exec(e);if(!s){return null}h=parseInt(s.year,10);u=parseInt(s.month,10)-1;n=parseInt(s.day,10);if(s.hour==null){t=new Date(Date.UTC(h,u,n));return t}r=parseInt(s.hour,10);l=parseInt(s.minute,10);a=parseInt(s.second,10);if(s.fraction!=null){i=s.fraction.slice(0,3);while(i.length<3){i+="0"}i=parseInt(i,10)}else{i=0}if(s.tz!=null){o=parseInt(s.tz_hour,10);if(s.tz_minute!=null){f=parseInt(s.tz_minute,10)}else{f=0}c=(o*60+f)*6e4;if("-"===s.tz_sign){c*=-1}}t=new Date(Date.UTC(h,u,n,r,l,a,i));if(c){t.setTime(t.getTime()-c)}return t};t.strRepeat=function(e,t){var n,i;i=""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 Ge)=x~O9rX{ diff --git a/pkg/web/static/webfonts/fa-brands-400.woff2 b/pkg/web/static/webfonts/fa-brands-400.woff2 deleted file mode 100644 index 71e31852689289b8d7b94ce0541953df40f76500..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 108020 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 diff --git a/pkg/web/static/webfonts/fa-regular-400.woff2 b/pkg/web/static/webfonts/fa-regular-400.woff2 deleted file mode 100644 index 7f021680b9f71711d5029c2199c42b131bb2cc5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24948 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 diff --git a/pkg/web/static/webfonts/fa-solid-900.woff2 b/pkg/web/static/webfonts/fa-solid-900.woff2 deleted file mode 100644 index 5c16cd3e8a008bdcbed97022c005278971f810c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 150124 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

    gcuh(yM)5x$wXlMQz` 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 DW9(%e diff --git a/pkg/web/static/webfonts/fa-v4compatibility.ttf b/pkg/web/static/webfonts/fa-v4compatibility.ttf deleted file mode 100644 index ab6ae22482929b542e5e73060736e9687a97acfd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10172 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 OATL4XY${dQ=Kle}q)K!E diff --git a/pkg/web/static/webfonts/fa-v4compatibility.woff2 b/pkg/web/static/webfonts/fa-v4compatibility.woff2 deleted file mode 100644 index 9027e38bcd1a01fe6207346ad5d1d9473f1ac5f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4564 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 diff --git a/web-ui/package-lock.json b/web-ui/package-lock.json index f7e9b7a2..0f8b1065 100644 --- a/web-ui/package-lock.json +++ b/web-ui/package-lock.json @@ -8,10 +8,13 @@ "name": "assertoor-ui", "version": "1.0.0", "dependencies": { + "@codemirror/lang-yaml": "^6.1.2", "@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", "@tanstack/react-query": "^5.59.0", + "@uiw/codemirror-theme-github": "^4.25.4", + "@uiw/react-codemirror": "^4.25.4", "js-yaml": "^4.1.1", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -84,6 +87,123 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz", + "integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.1.tgz", + "integrity": "sha512-uWDWFypNdQmz2y1LaNJzK7fL7TYKLeUAU0npEC685OKTF3KcQ2Vu3klIM78D7I6wGhktme0lh3CuQLv0ZCrD9Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-yaml": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz", + "integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.0.0", + "@lezer/yaml": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.1.tgz", + "integrity": "sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.3.tgz", + "integrity": "sha512-y3YkYhdnhjDBAe0VIA0c4wVoFOvnp8CnAvfLqi0TqotIv92wIlAAP7HELOpLBsKwjAX6W92rSflA6an/2zBvXw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.6.0.tgz", + "integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.4.tgz", + "integrity": "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/theme-one-dark": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", + "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.39.12", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.12.tgz", + "integrity": "sha512-f+/VsHVn/kOA9lltk/GFzuYwVVAKmOnNjxbrhkk3tPHntFqjWeI2TbIXx006YkBkqC10wZ4NsnWXCQiFPeAISQ==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -839,6 +959,47 @@ "dev": true, "license": "MIT" }, + "node_modules/@lezer/common": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.0.tgz", + "integrity": "sha512-PNGcolp9hr4PJdXR4ix7XtixDrClScvtSCYW3rQG106oVMOOI+jFb+0+J3mbeL/53g1Zd6s0kJzaw6Ri68GmAA==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz", + "integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/yaml": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.4.tgz", + "integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.4.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, "node_modules/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", @@ -2006,6 +2167,90 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@uiw/codemirror-extensions-basic-setup": { + "version": "4.25.4", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.25.4.tgz", + "integrity": "sha512-YzNwkm0AbPv1EXhCHYR5v0nqfemG2jEB0Z3Att4rBYqKrlG7AA9Rhjc3IyBaOzsBu18wtrp9/+uhTyu7TXSRng==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@codemirror/autocomplete": ">=6.0.0", + "@codemirror/commands": ">=6.0.0", + "@codemirror/language": ">=6.0.0", + "@codemirror/lint": ">=6.0.0", + "@codemirror/search": ">=6.0.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/view": ">=6.0.0" + } + }, + "node_modules/@uiw/codemirror-theme-github": { + "version": "4.25.4", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-github/-/codemirror-theme-github-4.25.4.tgz", + "integrity": "sha512-M5zRT2vIpNsuKN0Lz+DwLnmhHW8Eddp1M9zC0hm3V+bvffmaSn/pUDey1eqGIv5xNNmjhqvDAz0a90xLYCzvSw==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.25.4" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-themes": { + "version": "4.25.4", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.25.4.tgz", + "integrity": "sha512-2SLktItgcZC4p0+PfFusEbAHwbuAWe3bOOntCevVgHtrWGtGZX3IPv2k8IKZMgOXtAHyGKpJvT9/nspPn/uCQg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@codemirror/language": ">=6.0.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/view": ">=6.0.0" + } + }, + "node_modules/@uiw/react-codemirror": { + "version": "4.25.4", + "resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.25.4.tgz", + "integrity": "sha512-ipO067oyfUw+DVaXhQCxkB0ZD9b7RnY+ByrprSYSKCHaULvJ3sqWYC/Zen6zVQ8/XC4o5EPBfatGiX20kC7XGA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.6", + "@codemirror/commands": "^6.1.0", + "@codemirror/state": "^6.1.1", + "@codemirror/theme-one-dark": "^6.0.0", + "@uiw/codemirror-extensions-basic-setup": "4.25.4", + "codemirror": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.11.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/theme-one-dark": ">=6.0.0", + "@codemirror/view": ">=6.0.0", + "codemirror": ">=6.0.0", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -3090,6 +3335,21 @@ "node": ">=6" } }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3299,6 +3559,12 @@ } } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -9479,6 +9745,12 @@ "webpack": "^5.27.0" } }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, "node_modules/stylehacks": { "version": "7.0.7", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.7.tgz", @@ -10319,6 +10591,12 @@ "node": ">= 0.8" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/watchpack": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", diff --git a/web-ui/package.json b/web-ui/package.json index c9307c69..14069118 100644 --- a/web-ui/package.json +++ b/web-ui/package.json @@ -11,10 +11,13 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@codemirror/lang-yaml": "^6.1.2", "@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", "@tanstack/react-query": "^5.59.0", + "@uiw/codemirror-theme-github": "^4.25.4", + "@uiw/react-codemirror": "^4.25.4", "js-yaml": "^4.1.1", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/web-ui/src/App.tsx b/web-ui/src/App.tsx index c9bea5cf..080860a7 100644 --- a/web-ui/src/App.tsx +++ b/web-ui/src/App.tsx @@ -1,22 +1,68 @@ +import { lazy, Suspense } from 'react'; import { Routes, Route } from 'react-router-dom'; import { AuthProvider } from './context/AuthContext'; import Layout from './components/common/Layout'; -import Dashboard from './pages/Dashboard'; -import TestRun from './pages/TestRun'; -import Registry from './pages/Registry'; -import TestPage from './pages/TestPage'; -import Clients from './pages/Clients'; + +// Lazy load page components for code splitting +const Dashboard = lazy(() => import(/* webpackChunkName: "page-dashboard" */ './pages/Dashboard')); +const TestRun = lazy(() => import(/* webpackChunkName: "page-testrun" */ './pages/TestRun')); +const Registry = lazy(() => import(/* webpackChunkName: "page-registry" */ './pages/Registry')); +const TestPage = lazy(() => import(/* webpackChunkName: "page-test" */ './pages/TestPage')); +const Clients = lazy(() => import(/* webpackChunkName: "page-clients" */ './pages/Clients')); + +function PageLoader() { + return ( +

    +
    +
    + ); +} function App() { return ( }> - } /> - } /> - } /> - } /> - } /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> diff --git a/web-ui/src/api/client.ts b/web-ui/src/api/client.ts index 32f3fac9..a64523d5 100644 --- a/web-ui/src/api/client.ts +++ b/web-ui/src/api/client.ts @@ -80,9 +80,9 @@ export async function getTests(): Promise { return fetchApi('/tests'); } -// Test details +// Test details (uses auth to get vars with global variables merged in) export async function getTestDetails(testId: string): Promise { - return fetchApi(`/test/${encodeURIComponent(testId)}`); + return fetchApiWithAuth(`/test/${encodeURIComponent(testId)}`); } // Clients list diff --git a/web-ui/src/components/common/Layout.tsx b/web-ui/src/components/common/Layout.tsx index d4e7b037..6b417341 100644 --- a/web-ui/src/components/common/Layout.tsx +++ b/web-ui/src/components/common/Layout.tsx @@ -1,4 +1,4 @@ -import { Outlet, Link, useLocation } from 'react-router-dom'; +import { Outlet, Link, useLocation, useMatch } from 'react-router-dom'; import { useTheme } from '../../hooks/useTheme'; import { UserDisplay } from '../auth/UserDisplay'; @@ -11,6 +11,7 @@ const navItems = [ function Layout() { const location = useLocation(); const { theme, toggleTheme } = useTheme(); + const isTestRunPage = useMatch('/run/:runId'); return (
    @@ -67,7 +68,7 @@ function Layout() { {/* Main content */}
    -
    +
    diff --git a/web-ui/src/components/common/Modal.tsx b/web-ui/src/components/common/Modal.tsx index 5cc7540a..d1f6c1c3 100644 --- a/web-ui/src/components/common/Modal.tsx +++ b/web-ui/src/components/common/Modal.tsx @@ -34,10 +34,10 @@ function Modal({ isOpen, onClose, title, children, size = 'md' }: ModalProps) { if (!isOpen) return null; const sizeClasses = { - sm: 'max-w-md', - md: 'max-w-lg', - lg: 'max-w-2xl', - xl: 'max-w-4xl', + sm: 'max-w-lg', + md: 'max-w-2xl', + lg: 'max-w-4xl', + xl: 'max-w-6xl', }; const modalContent = ( diff --git a/web-ui/src/components/common/SplitPane.tsx b/web-ui/src/components/common/SplitPane.tsx index 4251cfa5..db30aa34 100644 --- a/web-ui/src/components/common/SplitPane.tsx +++ b/web-ui/src/components/common/SplitPane.tsx @@ -7,6 +7,7 @@ interface SplitPaneProps { minLeftWidth?: number; // percentage maxLeftWidth?: number; // percentage storageKey?: string; + maxHeight?: string; // CSS max-height value (e.g., '95vh', '600px') } function SplitPane({ @@ -16,6 +17,7 @@ function SplitPane({ minLeftWidth = 20, maxLeftWidth = 80, storageKey, + maxHeight, }: SplitPaneProps) { const [leftWidth, setLeftWidth] = useState(() => { if (storageKey) { @@ -75,23 +77,25 @@ function SplitPane({ }; }, [isDragging, handleMouseMove, handleMouseUp]); + const heightStyle = maxHeight ? { maxHeight, height: maxHeight } : { minHeight: '400px' }; + return ( <> {/* Mobile: Stacked layout */}
    -
    {left}
    -
    {right}
    +
    {left}
    +
    {right}
    {/* Desktop: Split pane layout */}
    {/* Left panel */}
    {left} @@ -115,7 +119,7 @@ function SplitPane({
    {/* Right panel */} -
    +
    {right}
    diff --git a/web-ui/src/components/common/StatusBadge.tsx b/web-ui/src/components/common/StatusBadge.tsx index 8edd662c..8f422c81 100644 --- a/web-ui/src/components/common/StatusBadge.tsx +++ b/web-ui/src/components/common/StatusBadge.tsx @@ -5,6 +5,7 @@ type Status = TestStatus | TaskStatus | TaskResult; interface StatusBadgeProps { status: Status; size?: 'sm' | 'md'; + progress?: number; // 0-100, only shown when status is 'running' } const statusConfig: Record = { @@ -21,19 +22,75 @@ const statusConfig: Record = { none: { label: 'Pending', className: 'status-pending' }, }; -function StatusBadge({ status, size = 'md' }: StatusBadgeProps) { +function StatusBadge({ status, size = 'md', progress }: StatusBadgeProps) { const config = statusConfig[status] || statusConfig.pending; const sizeClasses = size === 'sm' ? 'px-1.5 py-0.5 text-xs' : 'px-2 py-1 text-sm'; + const isRunning = status === 'running'; + const hasProgress = isRunning && progress !== undefined && progress > 0; return ( - {status === 'running' && ( - + {isRunning && ( + hasProgress ? ( + + ) : ( + + ) )} {config.label} ); } +interface ProgressRingProps { + progress: number; + size: number; +} + +function ProgressRing({ progress, size }: ProgressRingProps) { + const strokeWidth = 2; + const radius = (size - strokeWidth) / 2; + const circumference = 2 * Math.PI * radius; + const offset = circumference - (progress / 100) * circumference; + const center = size / 2; + + return ( +
    + + {/* Background circle */} + + {/* Progress circle */} + + + {/* Percentage text */} + + {Math.round(progress)} + +
    + ); +} + export default StatusBadge; diff --git a/web-ui/src/components/graph/TaskGraph.tsx b/web-ui/src/components/graph/TaskGraph.tsx new file mode 100644 index 00000000..3e48dbbd --- /dev/null +++ b/web-ui/src/components/graph/TaskGraph.tsx @@ -0,0 +1,134 @@ +import { useCallback, useEffect, useMemo, useRef } from 'react'; +import ReactFlow, { + Background, + Controls, + MiniMap, + type NodeTypes, + useReactFlow, + ReactFlowProvider, +} from 'reactflow'; +import 'reactflow/dist/style.css'; +import type { TaskState } from '../../types/api'; +import TaskGraphNode from './TaskGraphNode'; +import { useTaskGraph } from './useTaskGraph'; + +interface TaskGraphProps { + tasks: TaskState[]; + selectedIndex: number | null; + onSelect: (index: number) => void; +} + +// Define custom node types +const nodeTypes: NodeTypes = { + taskNode: TaskGraphNode, +}; + +function TaskGraphInner({ tasks, selectedIndex, onSelect }: TaskGraphProps) { + const { nodes, edges } = useTaskGraph(tasks, selectedIndex, onSelect); + const { fitView, getNode } = useReactFlow(); + const prevTasksLengthRef = useRef(tasks.length); + const initialFitDone = useRef(false); + + // Fit view on initial load + useEffect(() => { + if (nodes.length > 0 && !initialFitDone.current) { + // Small delay to ensure nodes are rendered + const timer = setTimeout(() => { + fitView({ padding: 0.2, duration: 300 }); + initialFitDone.current = true; + }, 100); + return () => clearTimeout(timer); + } + }, [nodes.length, fitView]); + + // Fit view when new tasks are added + useEffect(() => { + if (tasks.length !== prevTasksLengthRef.current) { + prevTasksLengthRef.current = tasks.length; + if (initialFitDone.current) { + // Don't animate when adding tasks during execution + fitView({ padding: 0.2, duration: 0 }); + } + } + }, [tasks.length, fitView]); + + // Center on selected node when selection changes + useEffect(() => { + if (selectedIndex === null) return; + + const node = getNode(`task-${selectedIndex}`); + if (!node) return; + + // Don't center if node is already visible in viewport + // Just highlight it instead + }, [selectedIndex, getNode]); + + // MiniMap node color based on status + const minimapNodeColor = useCallback((node: { data: { task: TaskState } }) => { + const task = node.data?.task; + if (!task) return '#9ca3af'; + + if (task.status === 'running') return '#3b82f6'; + if (task.status === 'complete') { + if (task.result === 'success') return '#22c55e'; + if (task.result === 'failure') return '#ef4444'; + } + return '#9ca3af'; + }, []); + + // Empty state + if (tasks.length === 0) { + return ( +
    + No tasks to display +
    + ); + } + + return ( + + + + + + ); +} + +// Wrapper component that provides ReactFlow context +function TaskGraph(props: TaskGraphProps) { + // Memoize to prevent unnecessary re-renders of the entire graph + const memoizedProps = useMemo(() => props, [props.tasks, props.selectedIndex, props.onSelect]); + + return ( + + + + ); +} + +export default TaskGraph; diff --git a/web-ui/src/components/graph/TaskGraphNode.tsx b/web-ui/src/components/graph/TaskGraphNode.tsx new file mode 100644 index 00000000..097facf2 --- /dev/null +++ b/web-ui/src/components/graph/TaskGraphNode.tsx @@ -0,0 +1,195 @@ +import { memo, useEffect, useState } from 'react'; +import { Handle, Position, type NodeProps } from 'reactflow'; +import type { TaskState } from '../../types/api'; + +export interface TaskNodeData { + task: TaskState; + isSelected: boolean; + onSelect: (index: number) => void; +} + +function TaskGraphNode({ data, selected }: NodeProps) { + const { task, isSelected, onSelect } = data; + const [now, setNow] = useState(Date.now()); + + // Update timer for running tasks + useEffect(() => { + if (task.status !== 'running') return; + + const interval = setInterval(() => { + setNow(Date.now()); + }, 1000); + + return () => clearInterval(interval); + }, [task.status]); + + // Determine display status + const getDisplayStatus = (): 'pending' | 'running' | 'success' | 'failure' | 'skipped' => { + if (task.status === 'running') return 'running'; + if (task.status === 'complete') { + if (task.result === 'success') return 'success'; + if (task.result === 'failure') return 'failure'; + // result === 'none' typically means skipped + if (!task.started) return 'skipped'; + } + return 'pending'; + }; + + const displayStatus = getDisplayStatus(); + const isRunning = task.status === 'running'; + const hasProgress = task.progress > 0 && task.progress < 100; + + // Calculate runtime + const getRuntimeMs = (): number => { + if (task.status === 'running' && task.start_time > 0) { + return now - task.start_time; + } + return task.runtime || 0; + }; + + const runtimeMs = getRuntimeMs(); + + // Status colors + const statusColors = { + pending: { + border: 'border-gray-300 dark:border-gray-600', + bg: 'bg-gray-50 dark:bg-gray-800', + dot: 'bg-gray-400', + }, + running: { + border: 'border-blue-400 dark:border-blue-500', + bg: 'bg-blue-50 dark:bg-blue-900/20', + dot: 'bg-blue-500', + }, + success: { + border: 'border-green-400 dark:border-green-500', + bg: 'bg-green-50 dark:bg-green-900/20', + dot: 'bg-green-500', + }, + failure: { + border: 'border-red-400 dark:border-red-500', + bg: 'bg-red-50 dark:bg-red-900/20', + dot: 'bg-red-500', + }, + skipped: { + border: 'border-yellow-400 dark:border-yellow-500', + bg: 'bg-yellow-50 dark:bg-yellow-900/20', + dot: 'bg-yellow-500', + }, + }; + + const colors = statusColors[displayStatus]; + + return ( + <> + {/* Input handle (top) */} + + + {/* Node content */} +
    onSelect(task.index)} + className={` + w-[180px] rounded-lg border-2 cursor-pointer + transition-all duration-200 shadow-sm hover:shadow-md + ${colors.border} ${colors.bg} + ${isSelected || selected ? 'ring-2 ring-primary-500 ring-offset-2 ring-offset-[var(--color-bg-primary)]' : ''} + `} + > + {/* Header with status indicator */} +
    + + + #{task.index} + + {runtimeMs > 0 && ( + + {formatRuntimeCompact(runtimeMs)} + + )} +
    + + {/* Task info */} +
    +
    + {task.title || task.name} +
    + {task.title && task.title !== task.name && ( +
    + {task.name} +
    + )} + + {/* Progress bar for running tasks */} + {isRunning && hasProgress && ( +
    +
    +
    +
    +
    + + {Math.round(task.progress)}% + +
    + {task.progress_message && ( +
    + {task.progress_message} +
    + )} +
    + )} + + {/* Error message for failed tasks */} + {displayStatus === 'failure' && task.result_error && ( +
    + {task.result_error} +
    + )} +
    +
    + + {/* Output handle (bottom) */} + + + ); +} + +function formatRuntimeCompact(ms: number): string { + if (ms < 1000) { + return `${ms}ms`; + } + + const seconds = Math.floor(ms / 1000); + if (seconds < 60) { + return `${seconds}s`; + } + + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + if (minutes < 60) { + return `${minutes}m${remainingSeconds > 0 ? ` ${remainingSeconds}s` : ''}`; + } + + const hours = Math.floor(minutes / 60); + const remainingMinutes = minutes % 60; + return `${hours}h${remainingMinutes > 0 ? ` ${remainingMinutes}m` : ''}`; +} + +export default memo(TaskGraphNode); diff --git a/web-ui/src/components/graph/index.ts b/web-ui/src/components/graph/index.ts new file mode 100644 index 00000000..678f801c --- /dev/null +++ b/web-ui/src/components/graph/index.ts @@ -0,0 +1,4 @@ +export { default as TaskGraph } from './TaskGraph'; +export { default as TaskGraphNode } from './TaskGraphNode'; +export { useTaskGraph } from './useTaskGraph'; +export type { TaskNodeData } from './TaskGraphNode'; diff --git a/web-ui/src/components/graph/useTaskGraph.ts b/web-ui/src/components/graph/useTaskGraph.ts new file mode 100644 index 00000000..1ab81e6d --- /dev/null +++ b/web-ui/src/components/graph/useTaskGraph.ts @@ -0,0 +1,351 @@ +import { useMemo } from 'react'; +import type { Node, Edge } from 'reactflow'; +import type { TaskState } from '../../types/api'; +import type { TaskNodeData } from './TaskGraphNode'; + +// Layout constants +const NODE_WIDTH = 180; +const NODE_HEIGHT = 80; +const HORIZONTAL_GAP = 60; +const VERTICAL_GAP = 50; + +// Glue task names that should not be rendered as nodes +const GLUE_TASKS = new Set([ + 'run_tasks', + 'run_tasks_concurrent', + 'run_task_matrix', + 'run_task_options', + 'run_task_background', +]); + +// Tasks that execute children concurrently (parallel lanes) +const CONCURRENT_GLUE_TASKS = new Set([ + 'run_tasks_concurrent', + 'run_task_matrix', +]); + +function isGlueTask(task: TaskState): boolean { + return GLUE_TASKS.has(task.name); +} + +function isConcurrentGlueTask(task: TaskState): boolean { + return CONCURRENT_GLUE_TASKS.has(task.name); +} + +export interface UseTaskGraphResult { + nodes: Node[]; + edges: Edge[]; +} + +export function useTaskGraph( + tasks: TaskState[], + selectedIndex: number | null, + onSelect: (index: number) => void +): UseTaskGraphResult { + return useMemo(() => { + if (!tasks || tasks.length === 0) { + return { nodes: [], edges: [] }; + } + + // Build task lookup maps + const taskMap = new Map(); + const childrenMap = new Map(); + + for (const task of tasks) { + taskMap.set(task.index, task); + } + + for (const task of tasks) { + const parentIndex = task.parent_index; + if (parentIndex >= 0 && parentIndex !== task.index && taskMap.has(parentIndex)) { + const siblings = childrenMap.get(parentIndex) || []; + siblings.push(task); + childrenMap.set(parentIndex, siblings); + } + } + + // Sort children by index + childrenMap.forEach((children) => { + children.sort((a, b) => a.index - b.index); + }); + + // Find root tasks + const rootTasks = tasks.filter(task => { + const parentIndex = task.parent_index; + return parentIndex < 0 || parentIndex === task.index || !taskMap.has(parentIndex); + }).sort((a, b) => a.index - b.index); + + // Execution graph edges: from -> to[] + const edges = new Map>(); + const reverseEdges = new Map>(); // to -> from[] + const visibleTasks = new Set(); + + function addEdge(from: number, to: number) { + if (!edges.has(from)) edges.set(from, new Set()); + edges.get(from)!.add(to); + if (!reverseEdges.has(to)) reverseEdges.set(to, new Set()); + reverseEdges.get(to)!.add(from); + } + + // Result of processing: first visible tasks (entry) and last visible tasks (exit) + interface FlowResult { + first: number[]; + last: number[]; + } + + // Process task tree and build execution graph + function process(task: TaskState): FlowResult { + const children = childrenMap.get(task.index) || []; + + if (isGlueTask(task)) { + if (children.length === 0) { + return { first: [], last: [] }; + } + + if (isConcurrentGlueTask(task)) { + // Concurrent: all children run in parallel + const allFirst: number[] = []; + const allLast: number[] = []; + + for (const child of children) { + const r = process(child); + allFirst.push(...r.first); + allLast.push(...r.last); + } + + return { first: allFirst, last: allLast }; + } else { + // Sequential: children run one after another + let prevLast: number[] = []; + let first: number[] = []; + + for (let i = 0; i < children.length; i++) { + const r = process(children[i]); + + // First child's entry is our entry + if (i === 0) { + first = r.first; + } + + // Connect previous exit to current entry + for (const f of prevLast) { + for (const t of r.first) { + addEdge(f, t); + } + } + + // Update exit for next iteration + prevLast = r.last.length > 0 ? r.last : prevLast; + } + + return { first, last: prevLast }; + } + } else { + // Visible task + visibleTasks.add(task.index); + return { first: [task.index], last: [task.index] }; + } + } + + // Process all roots sequentially + let prevLast: number[] = []; + for (const root of rootTasks) { + const r = process(root); + for (const f of prevLast) { + for (const t of r.first) { + addEdge(f, t); + } + } + prevLast = r.last.length > 0 ? r.last : prevLast; + } + + // Now we have: visibleTasks and edges between them + // Assign rows using topological sort (BFS from sources) + const nodeRows = new Map(); + const inDegree = new Map(); + + for (const idx of visibleTasks) { + inDegree.set(idx, 0); + } + for (const idx of visibleTasks) { + const outs = edges.get(idx); + if (outs) { + for (const to of outs) { + if (visibleTasks.has(to)) { + inDegree.set(to, (inDegree.get(to) || 0) + 1); + } + } + } + } + + // Start with nodes that have no incoming edges + let queue = Array.from(visibleTasks).filter(idx => (inDegree.get(idx) || 0) === 0); + let row = 0; + + while (queue.length > 0) { + const nextQueue: number[] = []; + + for (const idx of queue) { + nodeRows.set(idx, row); + const outs = edges.get(idx); + if (outs) { + for (const to of outs) { + if (visibleTasks.has(to)) { + const newDegree = (inDegree.get(to) || 1) - 1; + inDegree.set(to, newDegree); + if (newDegree === 0) { + nextQueue.push(to); + } + } + } + } + } + + queue = nextQueue; + row++; + } + + // Handle any remaining nodes (cycles or disconnected) + for (const idx of visibleTasks) { + if (!nodeRows.has(idx)) { + nodeRows.set(idx, row); + } + } + + // Group nodes by row for lane assignment + const byRow = new Map(); + for (const idx of visibleTasks) { + const r = nodeRows.get(idx) || 0; + if (!byRow.has(r)) byRow.set(r, []); + byRow.get(r)!.push(idx); + } + + // Assign lanes to minimize crossings + // Strategy: inherit lane from predecessor when possible + const nodeLanes = new Map(); + const sortedRows = Array.from(byRow.keys()).sort((a, b) => a - b); + + for (const r of sortedRows) { + const nodesInRow = byRow.get(r)!; + + // Sort by predecessor lane (for consistent ordering) + nodesInRow.sort((a, b) => { + const getPredLane = (idx: number): number => { + const preds = reverseEdges.get(idx); + if (preds) { + for (const p of preds) { + const lane = nodeLanes.get(p); + if (lane !== undefined) return lane; + } + } + return Infinity; + }; + return getPredLane(a) - getPredLane(b); + }); + + // Assign lanes + const usedLanes = new Set(); + + for (const idx of nodesInRow) { + // Try to use predecessor's lane + let targetLane = -1; + const preds = reverseEdges.get(idx); + if (preds) { + for (const p of preds) { + const lane = nodeLanes.get(p); + if (lane !== undefined && !usedLanes.has(lane)) { + targetLane = lane; + break; + } + } + } + + // If no preference or conflict, find next available + if (targetLane < 0) { + targetLane = 0; + while (usedLanes.has(targetLane)) targetLane++; + } + + nodeLanes.set(idx, targetLane); + usedLanes.add(targetLane); + } + } + + // Find max lane for centering + let maxLane = 0; + for (const lane of nodeLanes.values()) { + maxLane = Math.max(maxLane, lane); + } + + // Convert to React Flow format + const totalWidth = (maxLane + 1) * (NODE_WIDTH + HORIZONTAL_GAP) - HORIZONTAL_GAP; + const offsetX = -totalWidth / 2; + + const resultNodes: Node[] = []; + for (const idx of visibleTasks) { + const task = taskMap.get(idx); + if (!task) continue; + + const lane = nodeLanes.get(idx) || 0; + const nodeRow = nodeRows.get(idx) || 0; + + resultNodes.push({ + id: `task-${idx}`, + type: 'taskNode', + position: { + x: offsetX + lane * (NODE_WIDTH + HORIZONTAL_GAP), + y: nodeRow * (NODE_HEIGHT + VERTICAL_GAP), + }, + data: { + task, + isSelected: idx === selectedIndex, + onSelect, + }, + }); + } + + // Create edges + const resultEdges: Edge[] = []; + for (const [from, tos] of edges) { + if (!visibleTasks.has(from)) continue; + + const fromLane = nodeLanes.get(from) || 0; + + for (const to of tos) { + if (!visibleTasks.has(to)) continue; + + const toLane = nodeLanes.get(to) || 0; + const toTask = taskMap.get(to); + const isRunning = toTask?.status === 'running'; + const isComplete = toTask?.status === 'complete'; + const isSuccess = toTask?.result === 'success'; + const isFailure = toTask?.result === 'failure'; + + // Use straight edge for same lane (vertical), smoothstep for different lanes + const edgeType = fromLane === toLane ? 'straight' : 'smoothstep'; + + resultEdges.push({ + id: `edge-${from}-${to}`, + source: `task-${from}`, + target: `task-${to}`, + type: edgeType, + animated: isRunning, + style: { + stroke: isRunning + ? '#3b82f6' + : isComplete && isSuccess + ? '#22c55e' + : isComplete && isFailure + ? '#ef4444' + : '#9ca3af', + strokeWidth: 2, + }, + }); + } + } + + return { nodes: resultNodes, edges: resultEdges }; + }, [tasks, selectedIndex, onSelect]); +} + +export default useTaskGraph; diff --git a/web-ui/src/components/task/TaskDetails.tsx b/web-ui/src/components/task/TaskDetails.tsx index 273dd8dc..eebbde0d 100644 --- a/web-ui/src/components/task/TaskDetails.tsx +++ b/web-ui/src/components/task/TaskDetails.tsx @@ -99,8 +99,8 @@ function OverviewTab({ task }: { task: TaskState }) { {task.result_error && (
    -

    Error

    -

    +

    Error

    +

    {task.result_error}

    @@ -193,7 +193,7 @@ function ConfigTab({ yaml }: { yaml?: string }) { } return ( -
    +    
           {yaml}
         
    ); @@ -207,7 +207,7 @@ function ResultTab({ yaml }: { yaml?: string }) { } return ( -
    +    
           {yaml}
         
    ); diff --git a/web-ui/src/components/task/TaskList.tsx b/web-ui/src/components/task/TaskList.tsx index d7be0adf..c099640d 100644 --- a/web-ui/src/components/task/TaskList.tsx +++ b/web-ui/src/components/task/TaskList.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState, useEffect, useCallback } from 'react'; +import { useMemo, useState, useEffect, useCallback, useRef } from 'react'; import type { TaskState } from '../../types/api'; import StatusBadge from '../common/StatusBadge'; @@ -16,6 +16,7 @@ interface ProcessedTask extends TaskState { function TaskList({ tasks, selectedIndex, onSelect }: TaskListProps) { const [collapsedTasks, setCollapsedTasks] = useState>(new Set()); + const initialCollapseApplied = useRef(false); // Build tree structure with hierarchical ordering const { processedTasks, childrenMap } = useMemo(() => { @@ -82,6 +83,21 @@ function TaskList({ tasks, selectedIndex, onSelect }: TaskListProps) { return { processedTasks: result, childrenMap }; }, [tasks]); + // Auto-collapse succeeded tasks with children on initial load + useEffect(() => { + if (initialCollapseApplied.current || processedTasks.length === 0) return; + + const succeededWithChildren = processedTasks + .filter((t) => t.hasChildren && t.result === 'success') + .map((t) => t.index); + + if (succeededWithChildren.length > 0) { + setCollapsedTasks(new Set(succeededWithChildren)); + } + + initialCollapseApplied.current = true; + }, [processedTasks]); + // Filter out tasks whose ancestors are collapsed const visibleTasks = useMemo(() => { if (collapsedTasks.size === 0) return processedTasks; @@ -255,7 +271,7 @@ function TaskListItem({ task, depth, isSelected, isCollapsed, onClick, onToggleC )}
    - +
    diff --git a/web-ui/src/components/test/StartTestModal.tsx b/web-ui/src/components/test/StartTestModal.tsx index c8d3136e..09249891 100644 --- a/web-ui/src/components/test/StartTestModal.tsx +++ b/web-ui/src/components/test/StartTestModal.tsx @@ -2,6 +2,26 @@ import { useState, useEffect, useMemo, useCallback } from 'react'; import { useTests, useTestDetails, useScheduleTestRun } from '../../hooks/useApi'; import Modal from '../common/Modal'; import yaml from 'js-yaml'; +import CodeMirror from '@uiw/react-codemirror'; +import { yaml as yamlLang } from '@codemirror/lang-yaml'; +import { githubLight, githubDark } from '@uiw/codemirror-theme-github'; + +// Hook to detect dark mode +function useDarkMode() { + const [isDark, setIsDark] = useState(() => + document.documentElement.classList.contains('dark') + ); + + useEffect(() => { + const observer = new MutationObserver(() => { + setIsDark(document.documentElement.classList.contains('dark')); + }); + observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); + return () => observer.disconnect(); + }, []); + + return isDark; +} interface StartTestModalProps { isOpen: boolean; @@ -24,6 +44,8 @@ function StartTestModal({ isOpen, onClose, initialTestId }: StartTestModalProps) const [allowDuplicate, setAllowDuplicate] = useState(false); const [skipQueue, setSkipQueue] = useState(false); const [error, setError] = useState(null); + const isDarkMode = useDarkMode(); + const cmTheme = isDarkMode ? githubDark : githubLight; const { data: tests, isLoading: testsLoading } = useTests(); const { data: testDetails, isLoading: detailsLoading } = useTestDetails(selectedTestId, { @@ -51,10 +73,12 @@ function StartTestModal({ isOpen, onClose, initialTestId }: StartTestModalProps) }, [isOpen, initialTestId]); // Initialize config when test details are loaded + // Use vars (which includes global vars merged in) when available, fall back to raw config useEffect(() => { - if (testDetails?.config) { + const configSource = testDetails?.vars || testDetails?.config; + if (configSource) { const initialConfig: ConfigFormValues = {}; - for (const [key, value] of Object.entries(testDetails.config)) { + for (const [key, value] of Object.entries(configSource)) { initialConfig[key] = value as ConfigValue; } setFormConfig(initialConfig); @@ -125,7 +149,21 @@ function StartTestModal({ isOpen, onClose, initialTestId }: StartTestModalProps) } } else { if (Object.keys(formConfig).length > 0) { - config = formConfig as Record; + // Parse any YAML strings for object/array fields + config = {}; + for (const [key, value] of Object.entries(formConfig)) { + if (typeof value === 'string' && configSource && typeof configSource[key] === 'object') { + // This was an object/array field stored as YAML string + try { + config[key] = yaml.load(value); + } catch (err) { + setError(`Invalid YAML in field "${key}": ${err instanceof Error ? err.message : 'Parse error'}`); + return; + } + } else { + config[key] = value; + } + } } } @@ -151,7 +189,9 @@ function StartTestModal({ isOpen, onClose, initialTestId }: StartTestModalProps) return tests?.find((t) => t.id === selectedTestId); }, [tests, selectedTestId]); - const hasConfig = testDetails?.config && Object.keys(testDetails.config).length > 0; + // Use vars (with global vars merged) when available, fall back to raw config + const configSource = testDetails?.vars || testDetails?.config; + const hasConfig = configSource && Object.keys(configSource).length > 0; const handleClose = () => { onClose(); @@ -272,25 +312,36 @@ function StartTestModal({ isOpen, onClose, initialTestId }: StartTestModalProps) {editMode === 'form' ? (
    - {testDetails?.config && - Object.entries(testDetails.config).map(([key, defaultValue]) => ( + {configSource && + Object.entries(configSource).map(([key, defaultValue]) => ( updateFormValue(key, value)} + theme={cmTheme} /> ))}
    ) : (
    - -
    -
    -
    -
    -
    - - -
    -
    -
    -
- -
-
-
- - - - - -
-{{ end }} - -{{ define "js" }} -{{ end }} -{{ define "css" }} -{{ end }} \ No newline at end of file diff --git a/pkg/web/templates/sidebar/sidebar.html b/pkg/web/templates/sidebar/sidebar.html deleted file mode 100644 index bff8bfe8..00000000 --- a/pkg/web/templates/sidebar/sidebar.html +++ /dev/null @@ -1,165 +0,0 @@ -{{ define "sidebar" }} - - {{ if .CanRegisterTests }} - - - - {{ end }} -{{ end }} \ No newline at end of file diff --git a/pkg/web/templates/templates.go b/pkg/web/templates/templates.go deleted file mode 100644 index c3843d32..00000000 --- a/pkg/web/templates/templates.go +++ /dev/null @@ -1,171 +0,0 @@ -package templates - -import ( - "bufio" - "embed" - "fmt" - "html/template" - "io" - "io/fs" - "path" - "path/filepath" - "regexp" - "strings" - "sync" - - "github.com/sirupsen/logrus" - "github.com/tdewolff/minify" -) - -var ( - //go:embed * - templateFiles embed.FS -) - -type Templates struct { - logger logrus.FieldLogger - cache map[string]*template.Template - cacheMux sync.RWMutex - funcs template.FuncMap - minify bool -} - -func New(logger logrus.FieldLogger, funcs template.FuncMap, minifyHTML bool) *Templates { - return &Templates{ - logger: logger, - cache: make(map[string]*template.Template), - funcs: funcs, - minify: minifyHTML, - } -} - -func (t *Templates) GetTemplate(files ...string) *template.Template { - name := strings.Join(files, "-") - - t.cacheMux.RLock() - - if t.cache[name] != nil { - defer t.cacheMux.RUnlock() - return t.cache[name] - } - - t.cacheMux.RUnlock() - - tmpl := template.New(name).Funcs(t.funcs) - tmpl = template.Must(parseTemplateFiles(tmpl, t.readFileFS(templateFiles), files...)) - - t.cacheMux.Lock() - defer t.cacheMux.Unlock() - - t.cache[name] = tmpl - - return t.cache[name] -} - -func (t *Templates) readFileFS(fsys fs.FS) func(string) (string, []byte, error) { - return func(file string) (name string, b []byte, err error) { - name = path.Base(file) - b, err = fs.ReadFile(fsys, file) - - if t.minify { - // minfiy template - m := minify.New() - m.AddFunc("text/html", t.minifyTemplate) - - b, err = m.Bytes("text/html", b) - if err != nil { - panic(err) - } - } - - return - } -} - -func (t *Templates) minifyTemplate(_ *minify.M, w io.Writer, r io.Reader, _ map[string]string) error { - // remove newlines and spaces - m1 := regexp.MustCompile(`([ \t]+)?[\r\n]+`) - m2 := regexp.MustCompile(`([ \t])[ \t]+`) - rb := bufio.NewReader(r) - - for { - line, err := rb.ReadString('\n') - if err != nil && err != io.EOF { - return err - } - - line = m1.ReplaceAllString(line, "") - line = m2.ReplaceAllString(line, " ") - - if _, errws := io.WriteString(w, line); errws != nil { - return errws - } - - if err == io.EOF { - break - } - } - - return nil -} - -func parseTemplateFiles(t *template.Template, readFile func(string) (string, []byte, error), filenames ...string) (*template.Template, error) { - for _, filename := range filenames { - name, b, err := readFile(filename) - if err != nil { - return nil, err - } - - if t == nil { - t = template.New(name) - } - - var tmpl *template.Template - if name == t.Name() { - tmpl = t - } else { - tmpl = t.New(name) - } - - _, err = tmpl.Parse(string(b)) - if err != nil { - return nil, err - } - } - - return t, nil -} - -func GetTemplateNames() []string { - files, _ := getFileSysNames(templateFiles, ".") - return files -} - -func getFileSysNames(fsys fs.FS, dirname string) ([]string, error) { - files := make([]string, 0, 100) - - entry, err := fs.ReadDir(fsys, dirname) - if err != nil { - return nil, fmt.Errorf("error reading embed directory, err: %w", err) - } - - for _, f := range entry { - info, err := f.Info() - if err != nil { - return nil, fmt.Errorf("error returning file info err: %w", err) - } - - if !f.IsDir() { - files = append(files, filepath.Join(dirname, info.Name())) - } else { - names, err := getFileSysNames(fsys, info.Name()) - if err != nil { - return nil, err - } - - files = append(files, names...) - } - } - - return files, nil -} diff --git a/pkg/web/templates/test/test.html b/pkg/web/templates/test/test.html deleted file mode 100644 index 0fa8914f..00000000 --- a/pkg/web/templates/test/test.html +++ /dev/null @@ -1,157 +0,0 @@ -{{ define "page" }} -
-

{{ .Name }}

- - - - - - - - - - -
- Test ID: - - {{ .ID }} -
- Source: - - {{ .Source }} -
- -
-
-
Test Runs
-
- {{ if .CanStart }} -
- -
- {{ end }} -
- - - - {{ template "test_runs" . }} - -
-{{ end }} - -{{ define "js" }} - -{{ end }} -{{ define "css" }} - -{{ end }} \ No newline at end of file diff --git a/pkg/web/templates/test/test_runs.html b/pkg/web/templates/test/test_runs.html deleted file mode 100644 index c6a3e665..00000000 --- a/pkg/web/templates/test/test_runs.html +++ /dev/null @@ -1,379 +0,0 @@ -{{ define "test_runs" }} - -
- - - - - - - - - - - - - - - {{ $canCancel := .CanCancel }} - {{ range $i, $test := .Tests }} - - - - - - - - - - - - - {{ end }} - -
Run IDTest NameStart TimeRun TimeStatusActions
- - {{ $test.RunID }}{{ $test.Name }}{{ if $test.IsStarted }}{{ formatDateTime $test.StartTime.UTC }}{{ end }}{{ if $test.HasRunTime }}{{ $test.RunTime }}{{ else }}?{{ end }}{{ if $test.HasTimeout }} / {{ $test.Timeout }}{{ end }} - {{ if eq $test.Status "pending" }} - - - - {{ else if eq $test.Status "running" }} - - - - {{ else if eq $test.Status "success" }} - - - - {{ else if eq $test.Status "failure" }} - - - - {{ else if eq $test.Status "aborted" }} - - - - {{ else }} - - {{ $test.Status }} - - {{ end }} - -
- - - - - -
-
-
-
-
-
Status:
-
- {{ if eq $test.Status "pending" }} - - Pending - - {{ if $canCancel }} - - {{ end }} - {{ else if eq $test.Status "running" }} - - Running - - {{ if $canCancel }} - - {{ end }} - {{ else if eq $test.Status "success" }} - - Success - - {{ else if eq $test.Status "failure" }} - - Failure - - {{ else if eq $test.Status "aborted" }} - - Cancelled - - {{ else }} - - {{ $test.Status }} - - {{ end }} -
-
- {{ if $test.IsStarted }} -
-
Start Time:
-
{{ formatDateTime $test.StartTime.UTC }}
-
- {{ end }} - {{ if $test.IsCompleted }} -
-
Finish Time:
-
{{ formatDateTime $test.StopTime.UTC }}
-
- {{ end }} -
-
Tasks:
-
{{ .TaskCount }}
-
- -
-
-
-
-
-
- - -
- -
-
-
- Showing test runs {{ .FirstTestIndex }} to {{ .LastTestIndex }} of {{ .TotalTests }} -
-
-
- -
-
- -
-
-
- - - - -{{ end }} \ No newline at end of file diff --git a/pkg/web/templates/test_run/test_run.html b/pkg/web/templates/test_run/test_run.html deleted file mode 100644 index c7593de9..00000000 --- a/pkg/web/templates/test_run/test_run.html +++ /dev/null @@ -1,1086 +0,0 @@ -{{ define "page" }} - - - - - - - -
-

Test Run {{ .RunID }}: {{ html "" }}{{ html "" }}

- - - - - - - - - - - - {{ html "" }} - - - - - {{ html "" }} - {{ html "" }} - - - - - {{ html "" }} - - - - -
- Test ID: -
- Test Status: - - {{ html "" }} - - Pending - - {{ html "" }} - {{ html "" }} - - Running - - {{ html "" }} - {{ html "" }} - - Success - - {{ html "" }} - {{ html "" }} - - Failed - - {{ html "" }} - {{ html "" }} - - Cancelled - - {{ html "" }} - {{ html "" }} - - {{ html "" }} -
- Start Time: -
- Finish Time: -
- Timeout: -
- - -
-
Tasks
- - - - - - - - - - - - - - {{ html "" }} - - - - - - - - - - - - {{ html "" }} - -
IDAction NameTask TitleRun TimeStatus
-
-
- {{ html "" }} -
- {{ html "" }} -
- {{ html "" }} -
- {{ html "" }} - {{ html "" }} -
- -
-
- {{ html "" }} -
- -
-
-
-
- - {{ html "" }} - / - {{ html "" }} - {{ html "" }} - - () - - {{ html "" }} - - {{ html "" }} - - - - {{ html "" }} - {{ html "" }} - - - - {{ html "" }} - {{ html "" }} - - - - {{ html "" }} - - {{ html "" }} - - - - {{ html "" }} - {{ html "" }} - - - - {{ html "" }} - - -
- {{ html "" }} -
-
- - - - - - - - - - {{ html "" }} - - - - - {{ html "" }} - {{ html "" }} - - - - - {{ html "" }} -
Status: - {{ html "" }} - - Pending - - {{ html "" }} - {{ html "" }} - - Running - - {{ html "" }} - {{ html "" }} - - Complete - - {{ html "" }} - {{ html "" }} - - - - {{ html "" }} -
Result: - {{ html "" }} - - Success - - {{ html "" }} - {{ html "" }} - - Failure - - {{ html "" }} - {{ html "" }} - - None - - {{ html "" }} -
Start Time:
Finish Time:
- - {{ html "" }} - {{ html "" }} - -
-
-
-
- - - - - - - - - -
- {{ html "" }}PANIC{{ html "" }} - {{ html "" }}FATAL{{ html "" }} - {{ html "" }}ERROR{{ html "" }} - {{ html "" }}WARN{{ html "" }} - {{ html "" }}INFO{{ html "" }} - {{ html "" }}DEBUG{{ html "" }} - {{ html "" }}TRACE{{ html "" }} - - Fields -
-
-
-
-

-                      
-
-

-                      
- {{ html "" }} -
- - - - {{ html "" }} - - - - - - - {{ html "" }} - -
TypeNameIndexSize
-
- {{ html "" }} -
-
- {{ html "" }} - {{ html "" }} -
-
- {{ html "" }} -
-
-
-{{ end }} - -{{ define "sidebar" }} -{{ end }} -{{ define "js" }} - - - - - -{{ end }} -{{ define "css" }} - -{{ end }} diff --git a/pkg/web/utils/templateFucs.go b/pkg/web/utils/templateFucs.go deleted file mode 100644 index ec127017..00000000 --- a/pkg/web/utils/templateFucs.go +++ /dev/null @@ -1,101 +0,0 @@ -package utils - -import ( - "fmt" - "html/template" - "math" - "math/big" - "os" - "strings" - "time" - - logger "github.com/sirupsen/logrus" -) - -// GetTemplateFuncs will get the template functions -func GetTemplateFuncs() template.FuncMap { - return template.FuncMap{ - "includeHTML": IncludeHTML, - "html": func(x string) template.HTML { - //nolint:gosec // ignore - return template.HTML(x) - }, - "bigIntCmp": func(i *big.Int, j int) int { return i.Cmp(big.NewInt(int64(j))) }, - "mod": func(i, j int) bool { return i%j == 0 }, - "sub": func(i, j int) int { return i - j }, - "subUI64": func(i, j uint64) uint64 { return i - j }, - "add": func(i, j int) int { return i + j }, - "addI64": func(i, j int64) int64 { return i + j }, - "addUI64": func(i, j uint64) uint64 { return i + j }, - "addFloat64": func(i, j float64) float64 { return i + j }, - "mul": func(i, j float64) float64 { return i * j }, - "div": func(i, j float64) float64 { return i / j }, - "divInt": func(i, j int) float64 { return float64(i) / float64(j) }, - "nef": func(i, j float64) bool { return i != j }, - "gtf": func(i, j float64) bool { return i > j }, - "ltf": func(i, j float64) bool { return i < j }, - "inlist": checkInList, - "round": func(i float64, n int) float64 { - return math.Round(i*math.Pow10(n)) / math.Pow10(n) - }, - "percent": func(i float64) float64 { return i * 100 }, - "contains": strings.Contains, - "formatTimeDiff": FormatTimeDiff, - "formatDateTime": FormatDateTime, - } -} - -func checkInList(item, list string) bool { - items := strings.Split(list, ",") - for _, i := range items { - if i == item { - return true - } - } - - return false -} - -func IncludeHTML(path string) template.HTML { - b, err := os.ReadFile(path) - if err != nil { - logger.Printf("includeHTML - error reading file: %v", err) - return "" - } - - //nolint:gosec // ignore - return template.HTML(string(b)) -} - -func FormatTimeDiff(ts time.Time) template.HTML { - var timeStr string - - duration := time.Until(ts) - absDuraction := duration.Abs() - - switch { - case absDuraction < 1*time.Second: - return template.HTML("now") - case absDuraction < 60*time.Second: - timeStr = fmt.Sprintf("%v sec.", uint(absDuraction.Seconds())) - case absDuraction < 60*time.Minute: - timeStr = fmt.Sprintf("%v min.", uint(absDuraction.Minutes())) - case absDuraction < 24*time.Hour: - timeStr = fmt.Sprintf("%v hr.", uint(absDuraction.Hours())) - default: - timeStr = fmt.Sprintf("%v day.", uint(absDuraction.Hours()/24)) - } - - if duration < 0 { - //nolint:gosec // ignore - return template.HTML(fmt.Sprintf("%v ago", timeStr)) - } - - //nolint:gosec // ignore - return template.HTML(fmt.Sprintf("in %v", timeStr)) -} - -func FormatDateTime(ts time.Time) template.HTML { - //nolint:gosec // ignore - return template.HTML(ts.Format("02-01-2006 15:04:05")) -} diff --git a/web-ui/package-lock.json b/web-ui/package-lock.json index 163f809b..0f58d630 100644 --- a/web-ui/package-lock.json +++ b/web-ui/package-lock.json @@ -20,6 +20,7 @@ "react-dom": "^18.3.1", "react-router-dom": "^6.26.2", "reactflow": "^11.11.4", + "swagger-ui-react": "^5.18.2", "yaml": "^2.8.2", "zustand": "^4.5.5" }, @@ -27,6 +28,7 @@ "@types/js-yaml": "^4.0.9", "@types/react": "^18.3.8", "@types/react-dom": "^18.3.0", + "@types/swagger-ui-react": "^4.18.3", "@typescript-eslint/eslint-plugin": "^8.6.0", "@typescript-eslint/parser": "^8.6.0", "autoprefixer": "^10.4.20", @@ -97,6 +99,18 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.0.tgz", + "integrity": "sha512-TgUkdp71C9pIbBcHudc+gXZnihEDOjUAmXO1VO4HHGES7QLZcShR0stfKIxLSNIYx2fqhmJChOjm/wkF8wv4gA==", + "license": "MIT", + "dependencies": { + "core-js-pure": "^3.48.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@codemirror/autocomplete": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz", @@ -1322,6 +1336,13 @@ "node": ">=14.0.0" } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, "node_modules/@sinclair/typebox": { "version": "0.34.48", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", @@ -1342,6 +1363,639 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@swagger-api/apidom-ast": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.3.0.tgz", + "integrity": "sha512-CfaqcwdkWoJ8RAmmhp3fJVz+weyZ5ByKtGSlBgAlUMhfuo6qOsNIMYbSDiPZilUEbR3aXMdtqqXCpgOgZ6dMjg==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-error": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "unraw": "^3.0.0" + } + }, + "node_modules/@swagger-api/apidom-core": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.3.0.tgz", + "integrity": "sha512-xm98bBpZbaqzsednoVZ51itqby3jVZdHZSOqtucGVixcI+1mXzkUMSOrgyw/XIFeXJUevCBx56v5ihSxAdM8+w==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.3.0", + "@swagger-api/apidom-error": "^1.3.0", + "@types/ramda": "~0.30.0", + "minim": "~0.23.8", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "short-unique-id": "^5.3.2", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-error": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-1.3.0.tgz", + "integrity": "sha512-UX+2W4+kpK53666CxapjnNZyFse5nRO4fVltcFdaqWtUKcsQVoryxlcpmZJA7R/c7djROc3FesoawjAArdZYlw==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7" + } + }, + "node_modules/@swagger-api/apidom-json-pointer": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.3.0.tgz", + "integrity": "sha512-yj2ViZkBla11sRcfxzmXJjv9LSXjYVuHsMy1nqjzWeHHMHsEedVIu9PUWT7QWqTAWXsNEt5BBOU/pY5L1dLkOw==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-error": "^1.3.0", + "@swaggerexpert/json-pointer": "^2.10.1" + } + }, + "node_modules/@swagger-api/apidom-ns-api-design-systems": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.3.0.tgz", + "integrity": "sha512-iNPagRcxfDn9mdpfm6SXMDZjcKfV/LfTokRfp3uPlDD0DKzrJn66me8E1De3uOy7veK8NGsniR7j0GbNg6mMCg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-error": "^1.3.0", + "@swagger-api/apidom-ns-openapi-3-1": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-arazzo-1": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-arazzo-1/-/apidom-ns-arazzo-1-1.3.0.tgz", + "integrity": "sha512-9xgNd6smc46HZxFdnfWTvogFgVusaDl4/XQI2b/cK2i1M2+nxSD8akJaf9Fdjz982YBAsVkagUzl4VBTVdgZZA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-asyncapi-2": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.3.0.tgz", + "integrity": "sha512-gUYMHXh8CQZSIwhLf0IyLF2vApLilr/tmtHDs/rwxN7ZTA+k3jALVZ/EfP4Oq7TcsfHe4lw4vIkS0vAGRUhq4g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-ns-json-schema-draft-7": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-asyncapi-3": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-3/-/apidom-ns-asyncapi-3-1.3.0.tgz", + "integrity": "sha512-A5ZB2N++Kd9sfP7sxQq4wIuVOTh4ZuLAmOYvfqgefMHE8SLZfxDYIIERs7p20NhlsxedWVj9kBNW2aIs4cIIpQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-ns-asyncapi-2": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-2019-09": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2019-09/-/apidom-ns-json-schema-2019-09-1.3.0.tgz", + "integrity": "sha512-ZWKwv5XGsOmrfKPAl+YdbVFi+WjiGhAe4JjPdwpBmIYtzk/QrfmUKcpJPuNGrEphwYxkM1dzI53kkZ7eZvyFRQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-error": "^1.3.0", + "@swagger-api/apidom-ns-json-schema-draft-7": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-2020-12": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2020-12/-/apidom-ns-json-schema-2020-12-1.3.0.tgz", + "integrity": "sha512-Wayw8aPAOT/oYy4gMx5pyIgVOQrQg3p6XH6lxjGo63ODEmVJDrk5aj1XCoLMxwPXrECQI33jYxnqpG0ej2xsNQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-error": "^1.3.0", + "@swagger-api/apidom-ns-json-schema-2019-09": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-4": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.3.0.tgz", + "integrity": "sha512-55Ua8EuNRRLKj6zRw38GruRUmfdlyGSfM/rXyWdCHMh59IXXdRnNERYmXIcMUhU45opTeEr53hCORRFcVnMTow==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.3.0", + "@swagger-api/apidom-core": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-6": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.3.0.tgz", + "integrity": "sha512-ZC6RBv5GQMy5Dcq6aFKt9SkEXMagRPMDstkWc80MKeO7L3i5MRyGiTeo3lN2euDZGwofdEHRcO1jpzJbPUpU5A==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-error": "^1.3.0", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-7": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.3.0.tgz", + "integrity": "sha512-DLEjKKz9y7ht4iat8HeN2o9bHo84iSxKPS1cPmJMq5BavSN3yDYDqPks8Vni+LZI9SeWpzrPA1s9FbgqmDjCEQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-error": "^1.3.0", + "@swagger-api/apidom-ns-json-schema-draft-6": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-2": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.3.0.tgz", + "integrity": "sha512-aRx8PBOQ2Xc3+fkJ6asODjuJk/0/uECrLc1b4X5T2m3eJk1/YGjAjZHcSBYO/isRSLcRgzgqCZG6iFn3mtHakw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-error": "^1.3.0", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-3-0": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.3.0.tgz", + "integrity": "sha512-H9l72st2q8mUOVVftoexIDNJPD6jz6wxfVkJWcrPYsHcnpMdWDKeHxqMZAsoZozAZxM1hMG3p40BzPi/8ZBJhQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-error": "^1.3.0", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-3-1": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.3.0.tgz", + "integrity": "sha512-g9Wzq4Wv7P1tYf1Eo1AQjXwQWjCt47QUOzrIsPDvd6J0BFbNkAZRns1xSwhh9wblFOmAqBioRTcurlMWibR4lQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.3.0", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-json-pointer": "^1.3.0", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.3.0", + "@swagger-api/apidom-ns-openapi-3-0": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-json": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.3.0.tgz", + "integrity": "sha512-ICxejvaXXujP0igfZ3Ielf9/FxBe+lbAlROU/NIYt2eJUWpG7TnG/RzDKuqv2OIgosWm6iqhe5EIZwptCjMpMA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-ns-api-design-systems": "^1.3.0", + "@swagger-api/apidom-parser-adapter-json": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-yaml": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.3.0.tgz", + "integrity": "sha512-wysRnFKThjD/BfVXDxrXH8HtGe0dyjwKyH6WUltE97MyY3NqJjICCEJxfjWlp1DpH4p8ngYM8S6GYDqKlFBLuQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-ns-api-design-systems": "^1.3.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-arazzo-json-1": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-json-1/-/apidom-parser-adapter-arazzo-json-1-1.3.0.tgz", + "integrity": "sha512-S1RR3Ma7h98PQt6gnxi63Ya/826FPIcoa5uJqYBV4y4xO0OId0BWq9JkmKBxxhmrmlmZr+Ztxa7EaGtpvUZ/nA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-ns-arazzo-1": "^1.3.0", + "@swagger-api/apidom-parser-adapter-json": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-arazzo-yaml-1": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-yaml-1/-/apidom-parser-adapter-arazzo-yaml-1-1.3.0.tgz", + "integrity": "sha512-MLpTLMyFgp2BpIpMB5vPf2riW4CsqW5oKL7SuYAkwmpM8GnFxG1x6+4T1YHaZrRjj6l4/6JFpPx+gjJP293OOw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-ns-arazzo-1": "^1.3.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-2": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.3.0.tgz", + "integrity": "sha512-JIFDUmEsTkEneRdeBhSsHSaPd8/F8hkeSp9ePAvWbruI40iCC8gQLbiVs1SgEtaMjU/SNEl2X1yVJqYNKfwWHw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-ns-asyncapi-2": "^1.3.0", + "@swagger-api/apidom-parser-adapter-json": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-3": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-3/-/apidom-parser-adapter-asyncapi-json-3-1.3.0.tgz", + "integrity": "sha512-MYXkj2VJcp7b0wT5rzJDseNkWr8KzjmlxD9yWj9T+RDG76BmZBQSU0Xb1HDmW9TBk3vEWNrNfY7hk/8iD7QbTA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-ns-asyncapi-3": "^1.3.0", + "@swagger-api/apidom-parser-adapter-json": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.3.0.tgz", + "integrity": "sha512-jGg0JF9Y5K7hpRS0Bghl/iJcy14fUye8rMzM62aZlUm6pla/Gc3oIX/UF3WS9mPCTMtY0KLMhExFPs4VgPW7iA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-ns-asyncapi-2": "^1.3.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-3/-/apidom-parser-adapter-asyncapi-yaml-3-1.3.0.tgz", + "integrity": "sha512-/xLKCYbjy3pTIIIbviFs3Zbud8BAS4OU8NhERYf2uoubfzCbxjJge0XyoiwSue0u7cGcponFOZtvxuFIwIRoMg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-ns-asyncapi-3": "^1.3.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-json": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.3.0.tgz", + "integrity": "sha512-tAJ2sKfv5nPDYHXaoXMPE+yBoNF7ribMlMCx5raw9u1CC76wTC65uG1VvmL6DdqlDuyD3hxdlOSMIXLQ3/CLaQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.3.0", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-error": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "tree-sitter": "=0.22.4", + "tree-sitter-json": "=0.24.8", + "web-tree-sitter": "=0.24.5" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-json/node_modules/tree-sitter": { + "version": "0.22.4", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.22.4.tgz", + "integrity": "sha512-usbHZP9/oxNsUY65MQUsduGRqDHQOou1cagUSwjhoSYAmSahjQDAVsh9s+SlZkn8X8+O1FULRGwHu7AFP3kjzg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-2": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.3.0.tgz", + "integrity": "sha512-ncfY8UThDhYA/CfsogLLVeUtUWvQJ0UIN2BKxyfIRkExPSFxinOZRo4rLlice6cpEVuQUh0BWVlwC0x3+sd58w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-ns-openapi-2": "^1.3.0", + "@swagger-api/apidom-parser-adapter-json": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-0": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.3.0.tgz", + "integrity": "sha512-Oy29Pw7F5BLjhmCVUHsjcQizbnNBYj1Hm0nc/XWlu7D/V2lutaLUGE7Z7fankvC5chVhnuOdOOpiY23w/616XQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-ns-openapi-3-0": "^1.3.0", + "@swagger-api/apidom-parser-adapter-json": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-1": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.3.0.tgz", + "integrity": "sha512-NIhIQ+l4adbbXuawZ1VGw5iesBCfFDosd2cxU+H+Q4iEazKm+IXF/gxKEWWJCsw5s9umzt1+oCwzLutSufz4lA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-ns-openapi-3-1": "^1.3.0", + "@swagger-api/apidom-parser-adapter-json": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-2": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.3.0.tgz", + "integrity": "sha512-tmNaZaVEgDuHOray+xV23rD88bvQtDp0k+9uiUhg4Xl/DtA451r47ai/wEduEXmjCSCe+dpdCnR17nhe2FD+CA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-ns-openapi-2": "^1.3.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.3.0.tgz", + "integrity": "sha512-I1BTjMo8HODyeIbwE9ZG7uyc5yfPCL3QvkBGi8N+er26POTBTN/g+n+/IovtT7XlymKSrfnOnTtqCxEBA5+X8g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-ns-openapi-3-0": "^1.3.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.3.0.tgz", + "integrity": "sha512-ZaPsg7yH/2p/kp2VrwAxOQEVsNe57zN5h8vOQAfikLQ2Qpek/B77Y99wnPIl2YscuFKPdn3Vw+tcEGbHe9wmhw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-ns-openapi-3-1": "^1.3.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.3.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.3.0.tgz", + "integrity": "sha512-iGTkPgLQ/gT9jodwierviRbqAQzU8odHNXS8/XG6UbiOALOpbXyd1KhInXNGy54EMFHa3BgvIXNswBGhTRGIzw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.3.0", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-error": "^1.3.0", + "@tree-sitter-grammars/tree-sitter-yaml": "=0.7.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "tree-sitter": "=0.22.4", + "web-tree-sitter": "=0.24.5" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2/node_modules/@tree-sitter-grammars/tree-sitter-yaml": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@tree-sitter-grammars/tree-sitter-yaml/-/tree-sitter-yaml-0.7.1.tgz", + "integrity": "sha512-AynBwkIoQCTgjDR33bDUp9Mqq+YTco0is3n5hRApMqG9of/6A4eQsfC1/uSEeHSUyMQSYawcAWamsexnVpIP4Q==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^8.3.1", + "node-gyp-build": "^4.8.4" + }, + "peerDependencies": { + "tree-sitter": "^0.22.4" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2/node_modules/tree-sitter": { + "version": "0.22.4", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.22.4.tgz", + "integrity": "sha512-usbHZP9/oxNsUY65MQUsduGRqDHQOou1cagUSwjhoSYAmSahjQDAVsh9s+SlZkn8X8+O1FULRGwHu7AFP3kjzg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + } + }, + "node_modules/@swagger-api/apidom-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.3.0.tgz", + "integrity": "sha512-SnSOWLX+dCBJvs6oeNy2BmO1PBpW1x+jQupfETE323tEjf9uemxFo0/gj0hiNB/IAKHRqXQtsYlGM6ciSgyIWw==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-error": "^1.3.0", + "@types/ramda": "~0.30.0", + "axios": "^1.12.2", + "minimatch": "^7.4.3", + "process": "^0.11.10", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + }, + "optionalDependencies": { + "@swagger-api/apidom-json-pointer": "^1.3.0", + "@swagger-api/apidom-ns-arazzo-1": "^1.3.0", + "@swagger-api/apidom-ns-asyncapi-2": "^1.3.0", + "@swagger-api/apidom-ns-openapi-2": "^1.3.0", + "@swagger-api/apidom-ns-openapi-3-0": "^1.3.0", + "@swagger-api/apidom-ns-openapi-3-1": "^1.3.0", + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.3.0", + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.3.0", + "@swagger-api/apidom-parser-adapter-arazzo-json-1": "^1.3.0", + "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "^1.3.0", + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^1.3.0", + "@swagger-api/apidom-parser-adapter-asyncapi-json-3": "^1.3.0", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^1.3.0", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "^1.3.0", + "@swagger-api/apidom-parser-adapter-json": "^1.3.0", + "@swagger-api/apidom-parser-adapter-openapi-json-2": "^1.3.0", + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^1.3.0", + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^1.3.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^1.3.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^1.3.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^1.3.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.3.0" + } + }, + "node_modules/@swagger-api/apidom-reference/node_modules/minimatch": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", + "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@swaggerexpert/cookie": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@swaggerexpert/cookie/-/cookie-2.0.2.tgz", + "integrity": "sha512-DPI8YJ0Vznk4CT+ekn3rcFNq1uQwvUHZhH6WvTSPD0YKBIlMS9ur2RYKghXuxxOiqOam/i4lHJH4xTIiTgs3Mg==", + "license": "Apache-2.0", + "dependencies": { + "apg-lite": "^1.0.3" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@swaggerexpert/json-pointer": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@swaggerexpert/json-pointer/-/json-pointer-2.10.2.tgz", + "integrity": "sha512-qMx1nOrzoB+PF+pzb26Q4Tc2sOlrx9Ba2UBNX9hB31Omrq+QoZ2Gly0KLrQWw4Of1AQ4J9lnD+XOdwOdcdXqqw==", + "license": "Apache-2.0", + "dependencies": { + "apg-lite": "^1.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/@tanstack/query-core": { "version": "5.90.20", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", @@ -1724,6 +2378,15 @@ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", "license": "MIT" }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -1806,6 +2469,12 @@ "undici-types": "~7.16.0" } }, + "node_modules/@types/prismjs": { + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", + "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -1820,6 +2489,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ramda": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", + "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", + "license": "MIT", + "dependencies": { + "types-ramda": "^0.30.1" + } + }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", @@ -1908,6 +2586,35 @@ "@types/node": "*" } }, + "node_modules/@types/swagger-ui-react": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-react/-/swagger-ui-react-4.19.0.tgz", + "integrity": "sha512-uScp1xkLZJej0bt3/lO4U11ywWEBnI5CFCR0tqp+5Rvxl1Mj1v6VkGED0W70jJwqlBvbD+/a6bDiK8rjepCr8g==", + "dev": true, + "license": "MIT", + "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==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -2673,6 +3380,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/apg-lite": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/apg-lite/-/apg-lite-1.0.5.tgz", + "integrity": "sha512-SlI+nLMQDzCZfS39ihzjGp3JNBQfJXyMi6cg9tkLOCPVErgFsUIAEdO9IezR7kbP5Xd0ozcPNQBkf9TO5cHgWw==", + "license": "BSD-2-Clause" + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -2856,6 +3569,21 @@ "node": ">= 0.4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/autolinker": { + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-3.16.2.tgz", + "integrity": "sha512-JiYl7j2Z19F9NdTmirENSUUIIL/9MytEWtmzhfmsKPCp9E+G35Y0UNCMoM9tFigxT59qSc8Ml2dlZXOCVTYwuA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.24", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", @@ -2897,7 +3625,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" @@ -2909,11 +3636,41 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", + "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, + "license": "MIT" + }, + "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" + } + ], "license": "MIT" }, "node_modules/baseline-browser-mapping": { @@ -3010,7 +3767,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -3063,6 +3819,30 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3110,7 +3890,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", @@ -3129,7 +3908,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3143,7 +3921,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3238,6 +4015,36 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -3308,6 +4115,12 @@ "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", "license": "MIT" }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -3385,6 +4198,28 @@ "dev": true, "license": "MIT" }, + "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==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", @@ -3501,6 +4336,15 @@ "dev": true, "license": "MIT" }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, "node_modules/copy-webpack-plugin": { "version": "12.0.2", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", @@ -3526,6 +4370,17 @@ "webpack": "^5.1.0" } }, + "node_modules/core-js-pure": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.48.0.tgz", + "integrity": "sha512-1slJgk89tWC51HQ1AEqG+s2VuwpTRr8ocu4n20QUcH1v9lAN0RXen0Q0AABa/DK1I7RrNWLucplOHMx8hfTGTw==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -3719,6 +4574,12 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "license": "MIT" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -4031,6 +4892,28 @@ } } }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4038,6 +4921,15 @@ "dev": true, "license": "MIT" }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/default-browser": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", @@ -4072,7 +4964,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -4117,6 +5008,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4239,6 +5139,15 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", @@ -4265,11 +5174,19 @@ "tslib": "^2.0.3" } }, + "node_modules/drange": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz", + "integrity": "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -4434,7 +5351,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4444,7 +5360,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4489,7 +5404,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -4502,7 +5416,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4981,6 +5894,12 @@ "node": ">= 6" } }, + "node_modules/fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", + "license": "MIT" + }, "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", @@ -5032,6 +5951,19 @@ "reusify": "^1.0.4" } }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/faye-websocket": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", @@ -5178,7 +6110,6 @@ "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "dev": true, "funding": [ { "type": "individual", @@ -5199,7 +6130,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.2.7" @@ -5211,6 +6141,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -5271,7 +6225,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5322,7 +6275,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5347,7 +6299,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -5516,7 +6467,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5573,7 +6523,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -5602,7 +6551,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5615,7 +6563,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -5631,7 +6578,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -5640,6 +6586,36 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -5650,6 +6626,21 @@ "he": "bin/he" } }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/highlightjs-vue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", + "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", + "license": "CC0-1.0" + }, "node_modules/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -5889,6 +6880,26 @@ "postcss": "^8.1.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", @@ -5899,6 +6910,15 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", + "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -5962,7 +6982,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/internal-slot": { @@ -5990,6 +7009,15 @@ "node": ">=10.13.0" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/ipaddr.js": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", @@ -6000,6 +7028,30 @@ "node": ">= 10" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -6095,7 +7147,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6155,6 +7206,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-docker": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", @@ -6230,6 +7291,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -6438,7 +7509,6 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, "license": "MIT", "dependencies": { "which-typed-array": "^1.1.16" @@ -6516,7 +7586,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, "license": "MIT" }, "node_modules/isexe": { @@ -6625,6 +7694,12 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-file-download": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.12.tgz", + "integrity": "sha512-rML+NkoD08p5Dllpjo0ffy4jRHeY6Zsapvr/W86N7E0yuzAO6qa5X9+xog6zQNlH102J7IXljNY2FtS6Lj3ucg==", + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6786,7 +7861,12 @@ "version": "4.17.23", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "dev": true, + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "license": "MIT" }, "node_modules/lodash.memoize": { @@ -6832,11 +7912,24 @@ "tslib": "^2.0.3" } }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "license": "MIT", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6970,7 +8063,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -6980,7 +8072,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -7010,6 +8101,18 @@ "webpack": "^5.0.0" } }, + "node_modules/minim": { + "version": "0.23.8", + "resolved": "https://registry.npmjs.org/minim/-/minim-0.23.8.tgz", + "integrity": "sha512-bjdr2xW1dBCMsMGGsUeqM4eFI60m94+szhxWys+B1ztIt6gWSfeGBdSVCIawezeHYLYn0j6zrsXdQS/JllBzww==", + "license": "MIT", + "dependencies": { + "lodash": "^4.15.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -7109,6 +8212,15 @@ "dev": true, "license": "MIT" }, + "node_modules/neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -7120,6 +8232,71 @@ "tslib": "^2.0.3" } }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch-commonjs": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch-commonjs/-/node-fetch-commonjs-3.3.2.tgz", + "integrity": "sha512-VBlAiynj3VMLrotgwOS3OyECFxas5y7ltLcK4t41lMUZeaK15Ym4QRkqN0EQKAFL42q9i21EPKjzLUPfltR72A==", + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -7154,7 +8331,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7327,6 +8503,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openapi-path-templating": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/openapi-path-templating/-/openapi-path-templating-2.2.1.tgz", + "integrity": "sha512-eN14VrDvl/YyGxxrkGOHkVkWEoPyhyeydOUrbvjoz8K5eIGgELASwN1eqFOJ2CTQMGCy2EntOK1KdtJ8ZMekcg==", + "license": "Apache-2.0", + "dependencies": { + "apg-lite": "^1.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/openapi-server-url-templating": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/openapi-server-url-templating/-/openapi-server-url-templating-1.3.0.tgz", + "integrity": "sha512-DPlCms3KKEbjVQb0spV6Awfn6UWNheuG/+folQPzh/wUaKwuqvj8zt5gagD7qoyxtE03cIiKPgLFS3Q8Bz00uQ==", + "license": "Apache-2.0", + "dependencies": { + "apg-lite": "^1.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -7447,6 +8647,31 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -7675,7 +8900,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8433,6 +9657,24 @@ "renderkid": "^3.0.0" } }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -8444,7 +9686,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -8452,6 +9693,16 @@ "react-is": "^16.13.1" } }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -8476,6 +9727,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -8522,6 +9779,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -8543,11 +9806,49 @@ ], "license": "MIT" }, + "node_modules/ramda": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", + "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda" + } + }, + "node_modules/ramda-adjunct": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", + "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda-adjunct" + }, + "peerDependencies": { + "ramda": ">= 0.30.0" + } + }, + "node_modules/randexp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz", + "integrity": "sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==", + "license": "MIT", + "dependencies": { + "drange": "^1.0.2", + "ret": "^0.2.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" @@ -8591,6 +9892,32 @@ "node": ">=0.10.0" } }, + "node_modules/react-copy-to-clipboard": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", + "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", + "license": "MIT", + "dependencies": { + "copy-to-clipboard": "^3.3.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^15.3.0 || 16 || 17 || 18" + } + }, + "node_modules/react-debounce-input": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/react-debounce-input/-/react-debounce-input-3.3.0.tgz", + "integrity": "sha512-VEqkvs8JvY/IIZvh71Z0TC+mdbxERvYF33RcebnodlsUZ8RSgyKe2VWaHXv4+/8aoOgXLxWrdsYs2hDhcwbUgA==", + "license": "MIT", + "dependencies": { + "lodash.debounce": "^4", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^15.3.0 || 16 || 17 || 18" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -8604,13 +9931,67 @@ "react": "^18.3.1" } }, + "node_modules/react-immutable-proptypes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-immutable-proptypes/-/react-immutable-proptypes-2.2.0.tgz", + "integrity": "sha512-Vf4gBsePlwdGvSZoLSBfd4HAP93HDauMY4fDjXhreg/vg6F3Fj/MXDNyTbltPC/xZKmZc+cjLu3598DdYK6sgQ==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.2" + }, + "peerDependencies": { + "immutable": ">=3.6.2" + } + }, + "node_modules/react-immutable-pure-component": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/react-immutable-pure-component/-/react-immutable-pure-component-2.2.2.tgz", + "integrity": "sha512-vkgoMJUDqHZfXXnjVlG3keCxSO/U6WeDQ5/Sl0GK2cH8TOxEzQ5jXqDXHEL/jqk6fsNxV05oH5kD7VNMUE2k+A==", + "license": "MIT", + "peerDependencies": { + "immutable": ">= 2 || >= 4.0.0-rc", + "react": ">= 16.6", + "react-dom": ">= 16.6" + } + }, + "node_modules/react-inspector": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-6.0.2.tgz", + "integrity": "sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-router": { "version": "6.30.3", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", @@ -8643,6 +10024,26 @@ "react-dom": ">=16.8" } }, + "node_modules/react-syntax-highlighter": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.0.tgz", + "integrity": "sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "highlight.js": "^10.4.1", + "highlightjs-vue": "^1.0.0", + "lowlight": "^1.17.0", + "prismjs": "^1.30.0", + "refractor": "^5.0.0" + }, + "engines": { + "node": ">= 16.20.2" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/reactflow": { "version": "11.11.4", "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz", @@ -8746,6 +10147,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-immutable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/redux-immutable/-/redux-immutable-4.0.0.tgz", + "integrity": "sha512-SchSn/DWfGb3oAejd+1hhHx01xUoxY+V7TeK0BKqpkLKiQPVFf7DYzEaKmrEVxsWxielKfSK9/Xq66YyxgR1cg==", + "license": "BSD-3-Clause", + "peerDependencies": { + "immutable": "^3.8.1 || ^4.0.0-rc.1" + } + }, "node_modules/reflect-metadata": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", @@ -8776,6 +10192,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/refractor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-5.0.0.tgz", + "integrity": "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/prismjs": "^1.0.0", + "hastscript": "^9.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -8807,6 +10239,31 @@ "node": ">= 0.10" } }, + "node_modules/remarkable": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-2.0.1.tgz", + "integrity": "sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.10", + "autolinker": "^3.11.0" + }, + "bin": { + "remarkable": "bin/remarkable.js" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/remarkable/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -8821,6 +10278,15 @@ "strip-ansi": "^6.0.1" } }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -8835,7 +10301,12 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, + "license": "MIT" + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", "license": "MIT" }, "node_modules/resolve": { @@ -8889,6 +10360,15 @@ "node": ">=4" } }, + "node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -8988,7 +10468,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -9199,6 +10678,21 @@ "dev": true, "license": "MIT" }, + "node_modules/serialize-error": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -9306,7 +10800,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -9358,6 +10851,26 @@ "dev": true, "license": "ISC" }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -9407,6 +10920,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/short-unique-id": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-5.3.2.tgz", + "integrity": "sha512-KRT/hufMSxXKEDSQujfVE0Faa/kZ51ihUcZQAcmP04t00DvPj7Ox5anHke1sJYUtzSuiT/Y5uyzg/W7bBEGhCg==", + "license": "Apache-2.0", + "bin": { + "short-unique-id": "bin/short-unique-id", + "suid": "bin/short-unique-id" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -9539,6 +11062,16 @@ "source-map": "^0.6.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", @@ -9571,6 +11104,12 @@ "wbuf": "^1.7.3" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -9940,6 +11479,78 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/swagger-client": { + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.36.1.tgz", + "integrity": "sha512-bcYpeN4P3sOoKi22zsxIlL9lSgouBAmQmL5hH4g5yeOvyTUvq1+OFtGTs0l1C5Dkb0ZN+2vNgp0FBAFulmUklA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.22.15", + "@scarf/scarf": "=1.4.0", + "@swagger-api/apidom-core": "^1.3.0", + "@swagger-api/apidom-error": "^1.3.0", + "@swagger-api/apidom-json-pointer": "^1.3.0", + "@swagger-api/apidom-ns-openapi-3-1": "^1.3.0", + "@swagger-api/apidom-reference": "^1.3.0", + "@swaggerexpert/cookie": "^2.0.2", + "deepmerge": "~4.3.0", + "fast-json-patch": "^3.0.0-1", + "js-yaml": "^4.1.0", + "neotraverse": "=0.6.18", + "node-abort-controller": "^3.1.1", + "node-fetch-commonjs": "^3.3.2", + "openapi-path-templating": "^2.2.1", + "openapi-server-url-templating": "^1.3.0", + "ramda": "^0.30.1", + "ramda-adjunct": "^5.1.0" + } + }, + "node_modules/swagger-ui-react": { + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/swagger-ui-react/-/swagger-ui-react-5.31.0.tgz", + "integrity": "sha512-E/sTgKADThzpVksaGXbhED0pQCYdajiBNOzvSAan+RhV7pdoi2qvdwWhZsIo8nRvHk9UXJ0nkuxrud854ICr7A==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.27.1", + "@scarf/scarf": "=1.4.0", + "base64-js": "^1.5.1", + "buffer": "^6.0.3", + "classnames": "^2.5.1", + "css.escape": "1.5.1", + "deep-extend": "0.6.0", + "dompurify": "=3.2.6", + "ieee754": "^1.2.1", + "immutable": "^3.x.x", + "js-file-download": "^0.4.12", + "js-yaml": "=4.1.1", + "lodash": "^4.17.21", + "prop-types": "^15.8.1", + "randexp": "^0.5.3", + "randombytes": "^2.1.0", + "react-copy-to-clipboard": "5.1.0", + "react-debounce-input": "=3.3.0", + "react-immutable-proptypes": "2.2.0", + "react-immutable-pure-component": "^2.2.0", + "react-inspector": "^6.0.1", + "react-redux": "^9.2.0", + "react-syntax-highlighter": "^16.0.0", + "redux": "^5.0.1", + "redux-immutable": "^4.0.0", + "remarkable": "^2.0.1", + "reselect": "^5.1.1", + "serialize-error": "^8.1.0", + "sha.js": "^2.4.12", + "swagger-client": "^3.36.0", + "url-parse": "^1.5.10", + "xml": "=1.0.1", + "xml-but-prettier": "^1.0.1", + "zenscroll": "^4.0.2" + }, + "peerDependencies": { + "react": ">=16.8.0 <20", + "react-dom": ">=16.8.0 <20" + } + }, "node_modules/tailwindcss": { "version": "3.4.19", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", @@ -10200,6 +11811,20 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.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", @@ -10213,6 +11838,12 @@ "node": ">=8.0" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -10240,6 +11871,26 @@ "tslib": "2" } }, + "node_modules/tree-sitter-json": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/tree-sitter-json/-/tree-sitter-json-0.24.8.tgz", + "integrity": "sha512-Tc9ZZYwHyWZ3Tt1VEw7Pa2scu1YO7/d2BCBbKTx5hXwig3UfdQjsOPkPyLpDJOn/m1UBEWYAtSdGAwCSyagBqQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^8.2.2", + "node-gyp-build": "^4.8.2" + }, + "peerDependencies": { + "tree-sitter": "^0.21.1" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + } + }, "node_modules/ts-api-utils": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", @@ -10291,6 +11942,18 @@ "node": ">= 12" } }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "license": "MIT" + }, + "node_modules/ts-toolbelt": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", + "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==", + "license": "Apache-2.0" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -10334,7 +11997,6 @@ "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, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" @@ -10361,7 +12023,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -10435,6 +12096,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/types-ramda": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", + "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", + "license": "MIT", + "dependencies": { + "ts-toolbelt": "^9.6.0" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -10498,6 +12168,12 @@ "node": ">= 0.8" } }, + "node_modules/unraw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unraw/-/unraw-3.0.0.tgz", + "integrity": "sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==", + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -10539,6 +12215,16 @@ "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==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/use-sync-external-store": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", @@ -10622,6 +12308,22 @@ "minimalistic-assert": "^1.0.0" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/web-tree-sitter": { + "version": "0.24.5", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.24.5.tgz", + "integrity": "sha512-+J/2VSHN8J47gQUAvF8KDadrfz6uFYVjxoxbKWDoXVsH2u7yLdarCnIURnrMA6uSRkgX3SdmqM5BOoQjPdSh5w==", + "license": "MIT", + "optional": true + }, "node_modules/webpack": { "version": "5.104.1", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", @@ -11003,7 +12705,6 @@ "version": "1.1.20", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", - "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -11083,6 +12784,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "license": "MIT" + }, + "node_modules/xml-but-prettier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-but-prettier/-/xml-but-prettier-1.0.1.tgz", + "integrity": "sha512-C2CJaadHrZTqESlH03WOyw0oZTtoy2uEg6dSDF6YRg+9GnYNub53RRemLpnvtbHDFelxMx4LajiFsYeR6XJHgQ==", + "license": "MIT", + "dependencies": { + "repeat-string": "^1.5.2" + } + }, "node_modules/yaml": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", @@ -11111,6 +12827,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zenscroll": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zenscroll/-/zenscroll-4.0.2.tgz", + "integrity": "sha512-jEA1znR7b4C/NnaycInCU6h/d15ZzCd1jmsruqOKnZP6WXQSMH3W2GL+OXbkruslU4h+Tzuos0HdswzRUk/Vgg==", + "license": "Unlicense" + }, "node_modules/zustand": { "version": "4.5.7", "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", diff --git a/web-ui/package.json b/web-ui/package.json index 33467a72..59052abf 100644 --- a/web-ui/package.json +++ b/web-ui/package.json @@ -23,6 +23,7 @@ "react-dom": "^18.3.1", "react-router-dom": "^6.26.2", "reactflow": "^11.11.4", + "swagger-ui-react": "^5.18.2", "yaml": "^2.8.2", "zustand": "^4.5.5" }, @@ -30,6 +31,7 @@ "@types/js-yaml": "^4.0.9", "@types/react": "^18.3.8", "@types/react-dom": "^18.3.0", + "@types/swagger-ui-react": "^4.18.3", "@typescript-eslint/eslint-plugin": "^8.6.0", "@typescript-eslint/parser": "^8.6.0", "autoprefixer": "^10.4.20", diff --git a/web-ui/public/img/assertoor.png b/web-ui/public/img/assertoor.png new file mode 100644 index 0000000000000000000000000000000000000000..952279bd73de0f04f64cd781edfb3ea3ce58dd72 GIT binary patch literal 68550 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

O@?8lJ`Z`i}?v({n9r+Mz*e?;_= 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 import(/* webpackChunkName: "page-registry" */ './pa const TestPage = lazy(() => import(/* webpackChunkName: "page-test" */ './pages/TestPage')); const Clients = lazy(() => import(/* webpackChunkName: "page-clients" */ './pages/Clients')); const TestBuilder = lazy(() => import(/* webpackChunkName: "page-builder" */ './pages/TestBuilder')); +const ApiDocs = lazy(() => import(/* webpackChunkName: "page-api-docs" */ './pages/ApiDocs')); function PageLoader() { return ( @@ -72,6 +73,14 @@ function App() { } /> + }> + + + } + /> diff --git a/web-ui/src/components/common/Layout.tsx b/web-ui/src/components/common/Layout.tsx index 14e40d29..b373a8d1 100644 --- a/web-ui/src/components/common/Layout.tsx +++ b/web-ui/src/components/common/Layout.tsx @@ -7,6 +7,7 @@ const navItems = [ { path: '/registry', label: 'Registry' }, { path: '/builder', label: 'Builder' }, { path: '/clients', label: 'Clients' }, + { path: '/api-docs', label: 'API' }, ]; function Layout() { @@ -22,10 +23,12 @@ function Layout() {

{/* Logo */} - -
- A -
+ + Assertoor Assertoor diff --git a/web-ui/src/pages/ApiDocs.tsx b/web-ui/src/pages/ApiDocs.tsx new file mode 100644 index 00000000..43b7c356 --- /dev/null +++ b/web-ui/src/pages/ApiDocs.tsx @@ -0,0 +1,495 @@ +import { useEffect, useRef, useState } from 'react'; +import SwaggerUI from 'swagger-ui-react'; +import 'swagger-ui-react/swagger-ui.css'; +import { useTheme } from '../hooks/useTheme'; + +function ApiDocs() { + const containerRef = useRef(null); + const { theme } = useTheme(); + const [isLoaded, setIsLoaded] = useState(false); + + // Apply dark mode styling to swagger UI + useEffect(() => { + if (!containerRef.current) return; + + const applyTheme = () => { + const swaggerContainer = containerRef.current; + if (!swaggerContainer) return; + + if (theme === 'dark') { + swaggerContainer.classList.add('swagger-dark'); + } else { + swaggerContainer.classList.remove('swagger-dark'); + } + }; + + // Apply immediately and after a short delay (for dynamic content) + applyTheme(); + const timeout = setTimeout(applyTheme, 100); + + return () => clearTimeout(timeout); + }, [theme, isLoaded]); + + return ( +
+
+
+

API Documentation

+

+ REST API reference for Assertoor +

+
+
+ +
+ setIsLoaded(true)} + docExpansion="list" + defaultModelsExpandDepth={1} + displayRequestDuration={true} + filter={true} + showExtensions={true} + showCommonExtensions={true} + tryItOutEnabled={true} + /> +
+ + {/* Swagger UI Dark Mode Styles */} + +
+ ); +} + +export default ApiDocs; diff --git a/web-ui/src/pages/Dashboard.tsx b/web-ui/src/pages/Dashboard.tsx index 6bac537c..2b147e5c 100644 --- a/web-ui/src/pages/Dashboard.tsx +++ b/web-ui/src/pages/Dashboard.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback } from 'react'; +import { useState, useCallback, useMemo, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { useTestRuns, useCancelTestRun, useDeleteTestRuns } from '../hooks/useApi'; import { useAuthContext } from '../context/AuthContext'; @@ -8,23 +8,62 @@ import StartTestModal from '../components/test/StartTestModal'; import { formatDuration } from '../utils/time'; import type { TestRun } from '../types/api'; +const PAGE_SIZE_KEY = 'dashboard-page-size'; +const PAGE_SIZES = [25, 50, 100, 200] as const; + function Dashboard() { const [selectedRuns, setSelectedRuns] = useState>(new Set()); const [expandedRun, setExpandedRun] = useState(null); const [showStartTestModal, setShowStartTestModal] = useState(false); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(() => { + const stored = localStorage.getItem(PAGE_SIZE_KEY); + return stored ? parseInt(stored, 10) : 25; + }); const { isLoggedIn } = useAuthContext(); const { data: testRuns, isLoading, error } = useTestRuns(); const cancelMutation = useCancelTestRun(); const deleteMutation = useDeleteTestRuns(); + // Store page size preference + useEffect(() => { + localStorage.setItem(PAGE_SIZE_KEY, String(pageSize)); + }, [pageSize]); + + // Calculate pagination + const totalItems = testRuns?.length ?? 0; + const totalPages = Math.ceil(totalItems / pageSize); + + // Ensure current page is valid when data changes + useEffect(() => { + if (currentPage > totalPages && totalPages > 0) { + setCurrentPage(totalPages); + } + }, [currentPage, totalPages]); + + // Get current page items + const paginatedRuns = useMemo(() => { + if (!testRuns) return []; + const startIndex = (currentPage - 1) * pageSize; + return testRuns.slice(startIndex, startIndex + pageSize); + }, [testRuns, currentPage, pageSize]); + + // Handle page size change + const handlePageSizeChange = useCallback((newSize: number) => { + setPageSize(newSize); + setCurrentPage(1); + setSelectedRuns(new Set()); + }, []); + + // Handle select all - only select items on current page const handleSelectAll = useCallback((checked: boolean) => { - if (checked && testRuns) { - setSelectedRuns(new Set(testRuns.map(t => t.run_id))); + if (checked) { + setSelectedRuns(new Set(paginatedRuns.map(t => t.run_id))); } else { setSelectedRuns(new Set()); } - }, [testRuns]); + }, [paginatedRuns]); const handleSelectRun = useCallback((runId: number, checked: boolean) => { setSelectedRuns(prev => { @@ -81,8 +120,12 @@ function Dashboard() { ); } - const allSelected = testRuns && testRuns.length > 0 && - testRuns.every(t => selectedRuns.has(t.run_id)); + const allSelected = paginatedRuns.length > 0 && + paginatedRuns.every(t => selectedRuns.has(t.run_id)); + const someSelected = paginatedRuns.some(t => selectedRuns.has(t.run_id)); + + const startItem = totalItems === 0 ? 0 : (currentPage - 1) * pageSize + 1; + const endItem = Math.min(currentPage * pageSize, totalItems); return (
@@ -90,7 +133,7 @@ function Dashboard() {

All Test Runs

- {testRuns?.length ?? 0} test runs + {totalItems} test runs {isLoggedIn && (
- {isLoggedIn && ( -
- + {/* Pagination Controls */} +
+
+ {isLoggedIn && ( + + )}
- )} + +
+ {/* Page size selector */} +
+ Show + + per page +
+ + {/* Page info */} + + {startItem}-{endItem} of {totalItems} + + + {/* Page navigation */} +
+ + + + Page {currentPage} of {totalPages || 1} + + + +
+
+
)} @@ -367,4 +477,36 @@ function PlayIcon({ className }: { className?: string }) { ); } +function ChevronLeftIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +function ChevronRightIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +function FirstPageIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +function LastPageIcon({ className }: { className?: string }) { + return ( + + + + ); +} + export default Dashboard; From eed2529cc9653e658c7f7942069bf72da3985af8 Mon Sep 17 00:00:00 2001 From: pk910 Date: Tue, 3 Feb 2026 03:49:00 +0100 Subject: [PATCH 13/32] protect sensitive api endpoints and prepare UI for missing sensitive information --- pkg/db/test_configs.go | 12 + pkg/web/api/docs/docs.go | 463 ++++++++++++--------- pkg/web/api/docs/swagger.json | 463 ++++++++++++--------- pkg/web/api/docs/swagger.yaml | 384 +++++++++-------- pkg/web/api/get_task_result_api.go | 11 +- pkg/web/api/get_test_api.go | 43 +- pkg/web/api/get_test_yaml_api.go | 136 ++++++ pkg/web/server.go | 1 + web-ui/src/api/client.ts | 6 + web-ui/src/components/task/TaskDetails.tsx | 44 +- web-ui/src/hooks/useApi.ts | 10 + web-ui/src/pages/TestBuilder.tsx | 39 +- web-ui/src/pages/TestRun.tsx | 4 +- web-ui/src/stores/builderStore.ts | 4 + web-ui/src/types/api.ts | 6 + 15 files changed, 1028 insertions(+), 598 deletions(-) create mode 100644 pkg/web/api/get_test_yaml_api.go diff --git a/pkg/db/test_configs.go b/pkg/db/test_configs.go index e2e717ab..beb828ff 100644 --- a/pkg/db/test_configs.go +++ b/pkg/db/test_configs.go @@ -76,6 +76,18 @@ func (db *Database) GetTestRunStats() ([]*TestRunStats, error) { return stats, nil } +// GetTestConfig returns a single test config by ID. +func (db *Database) GetTestConfig(testID string) (*TestConfig, error) { + var config TestConfig + + err := db.reader.Get(&config, `SELECT * FROM test_configs WHERE test_id = $1`, testID) + if err != nil { + return nil, err + } + + return &config, nil +} + // DeleteTestConfig deletes a test config from the database. func (db *Database) DeleteTestConfig(tx *sqlx.Tx, testID string) error { _, err := tx.Exec(`DELETE FROM test_configs WHERE test_id = $1`, testID) diff --git a/pkg/web/api/docs/docs.go b/pkg/web/api/docs/docs.go index b5aadfa5..fdf13091 100644 --- a/pkg/web/api/docs/docs.go +++ b/pkg/web/api/docs/docs.go @@ -32,13 +32,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.GetClientsResponse" + "$ref": "#/definitions/api.GetClientsResponse" } } } @@ -48,13 +48,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -85,13 +85,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.GetLogsResponse" + "$ref": "#/definitions/api.GetLogsResponse" } } } @@ -101,13 +101,13 @@ const docTemplate = `{ "401": { "description": "Unauthorized", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -138,7 +138,7 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", @@ -154,7 +154,7 @@ const docTemplate = `{ "404": { "description": "Not Found", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -176,7 +176,7 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", @@ -221,13 +221,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.GetTestResponse" + "$ref": "#/definitions/api.GetTestResponse" } } } @@ -237,13 +237,79 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" + } + } + } + } + }, + "/api/v1/test/{testId}/yaml": { + "get": { + "description": "Returns the full YAML source for a test definition. For API-registered tests, returns the stored YAML. For external tests referenced by file/URL, loads and returns the content from the original source. Requires authentication as YAML may contain sensitive configuration.", + "produces": [ + "application/json" + ], + "tags": [ + "Test" + ], + "summary": "Get test YAML source by test ID", + "operationId": "getTestYaml", + "parameters": [ + { + "type": "string", + "description": "ID of the test definition to get YAML for", + "name": "testId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/api.GetTestYamlResponse" + } + } + } + ] + } + }, + "400": { + "description": "Failure", + "schema": { + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" + } + }, + "500": { + "description": "Server Error", + "schema": { + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -275,13 +341,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.GetTestRunResponse" + "$ref": "#/definitions/api.GetTestRunResponse" } } } @@ -291,13 +357,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -328,7 +394,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/web_api.PostTestRunCancelRequest" + "$ref": "#/definitions/api.PostTestRunCancelRequest" } } ], @@ -338,13 +404,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.PostTestRunCancelResponse" + "$ref": "#/definitions/api.PostTestRunCancelResponse" } } } @@ -354,13 +420,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -392,13 +458,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.GetTestRunDetailsResponse" + "$ref": "#/definitions/api.GetTestRunDetailsResponse" } } } @@ -408,13 +474,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -446,7 +512,7 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", @@ -462,13 +528,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -507,13 +573,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.GetTestRunDetailedTask" + "$ref": "#/definitions/api.GetTestRunDetailedTask" } } } @@ -523,19 +589,19 @@ const docTemplate = `{ "400": { "description": "Bad Request", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "404": { "description": "Not Found", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -543,7 +609,7 @@ const docTemplate = `{ }, "/api/v1/test_run/{runId}/task/{taskIndex}/result/{resultType}/{fileId}": { "get": { - "description": "Returns a specific result file from a task", + "description": "Returns a specific result file from a task. Requires authentication as results may contain sensitive data.", "produces": [ "application/octet-stream" ], @@ -592,19 +658,25 @@ const docTemplate = `{ "400": { "description": "Bad Request", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "404": { "description": "Not Found", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -635,7 +707,7 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", @@ -643,7 +715,7 @@ const docTemplate = `{ "data": { "type": "array", "items": { - "$ref": "#/definitions/web_api.GetTestRunsResponse" + "$ref": "#/definitions/api.GetTestRunsResponse" } } } @@ -654,13 +726,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -688,7 +760,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/web_api.PostTestRunsDeleteRequest" + "$ref": "#/definitions/api.PostTestRunsDeleteRequest" } } ], @@ -698,13 +770,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.PostTestRunsDeleteResponse" + "$ref": "#/definitions/api.PostTestRunsDeleteResponse" } } } @@ -714,13 +786,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -744,7 +816,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/web_api.PostTestRunsScheduleRequest" + "$ref": "#/definitions/api.PostTestRunsScheduleRequest" } } ], @@ -754,13 +826,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.PostTestRunsScheduleResponse" + "$ref": "#/definitions/api.PostTestRunsScheduleResponse" } } } @@ -770,13 +842,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -799,7 +871,7 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", @@ -807,7 +879,7 @@ const docTemplate = `{ "data": { "type": "array", "items": { - "$ref": "#/definitions/web_api.GetTestsResponse" + "$ref": "#/definitions/api.GetTestsResponse" } } } @@ -818,13 +890,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -852,7 +924,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/web_api.PostTestsDeleteRequest" + "$ref": "#/definitions/api.PostTestsDeleteRequest" } } ], @@ -862,13 +934,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.PostTestsDeleteResponse" + "$ref": "#/definitions/api.PostTestsDeleteResponse" } } } @@ -878,13 +950,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -912,7 +984,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/web_api.PostTestsRegisterRequest" + "$ref": "#/definitions/api.PostTestsRegisterRequest" } } ], @@ -922,13 +994,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.PostTestsRegisterResponse" + "$ref": "#/definitions/api.PostTestsRegisterResponse" } } } @@ -938,13 +1010,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -971,7 +1043,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/web_api.PostTestsRegisterExternalRequest" + "$ref": "#/definitions/api.PostTestsRegisterExternalRequest" } } ], @@ -981,13 +1053,13 @@ const docTemplate = `{ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.PostTestsRegisterExternalResponse" + "$ref": "#/definitions/api.PostTestsRegisterExternalResponse" } } } @@ -997,13 +1069,13 @@ const docTemplate = `{ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -1011,92 +1083,7 @@ const docTemplate = `{ } }, "definitions": { - "helper.RawMessage": { - "type": "object" - }, - "tasks.TaskDescriptorAPI": { - "type": "object", - "properties": { - "aliases": { - "type": "array", - "items": { - "type": "string" - } - }, - "category": { - "type": "string" - }, - "configSchema": { - "type": "array", - "items": { - "type": "integer" - } - }, - "description": { - "type": "string" - }, - "name": { - "type": "string" - }, - "outputs": { - "type": "array", - "items": { - "$ref": "#/definitions/types.TaskOutputDefinition" - } - } - } - }, - "types.TaskOutputDefinition": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "name": { - "type": "string" - }, - "type": { - "type": "string" - } - } - }, - "types.TestSchedule": { - "type": "object", - "properties": { - "cron": { - "type": "array", - "items": { - "type": "string" - } - }, - "skipQueue": { - "type": "boolean" - }, - "startup": { - "type": "boolean" - } - } - }, - "types.TestStatus": { - "type": "string", - "enum": [ - "pending", - "running", - "success", - "failure", - "skipped", - "aborted" - ], - "x-enum-varnames": [ - "TestStatusPending", - "TestStatusRunning", - "TestStatusSuccess", - "TestStatusFailure", - "TestStatusSkipped", - "TestStatusAborted" - ] - }, - "web_api.ClientResponse": { + "api.ClientResponse": { "type": "object", "properties": { "cl_error": { @@ -1155,7 +1142,7 @@ const docTemplate = `{ } } }, - "web_api.GetClientsResponse": { + "api.GetClientsResponse": { "type": "object", "properties": { "client_count": { @@ -1164,23 +1151,23 @@ const docTemplate = `{ "clients": { "type": "array", "items": { - "$ref": "#/definitions/web_api.ClientResponse" + "$ref": "#/definitions/api.ClientResponse" } } } }, - "web_api.GetLogsResponse": { + "api.GetLogsResponse": { "type": "object", "properties": { "log": { "type": "array", "items": { - "$ref": "#/definitions/web_api.LogEntry" + "$ref": "#/definitions/api.LogEntry" } } } }, - "web_api.GetTestResponse": { + "api.GetTestResponse": { "type": "object", "properties": { "basePath": { @@ -1218,7 +1205,7 @@ const docTemplate = `{ } } }, - "web_api.GetTestRunDetailedTask": { + "api.GetTestRunDetailedTask": { "type": "object", "properties": { "completed": { @@ -1233,7 +1220,7 @@ const docTemplate = `{ "log": { "type": "array", "items": { - "$ref": "#/definitions/web_api.GetTestRunDetailedTaskLog" + "$ref": "#/definitions/api.GetTestRunDetailedTaskLog" } }, "name": { @@ -1280,7 +1267,7 @@ const docTemplate = `{ } } }, - "web_api.GetTestRunDetailedTaskLog": { + "api.GetTestRunDetailedTaskLog": { "type": "object", "properties": { "data": { @@ -1303,7 +1290,7 @@ const docTemplate = `{ } } }, - "web_api.GetTestRunDetailsResponse": { + "api.GetTestRunDetailsResponse": { "type": "object", "properties": { "name": { @@ -1324,7 +1311,7 @@ const docTemplate = `{ "tasks": { "type": "array", "items": { - "$ref": "#/definitions/web_api.GetTestRunDetailedTask" + "$ref": "#/definitions/api.GetTestRunDetailedTask" } }, "test_id": { @@ -1332,7 +1319,7 @@ const docTemplate = `{ } } }, - "web_api.GetTestRunResponse": { + "api.GetTestRunResponse": { "type": "object", "properties": { "name": { @@ -1353,7 +1340,7 @@ const docTemplate = `{ "tasks": { "type": "array", "items": { - "$ref": "#/definitions/web_api.GetTestRunTask" + "$ref": "#/definitions/api.GetTestRunTask" } }, "test_id": { @@ -1361,7 +1348,7 @@ const docTemplate = `{ } } }, - "web_api.GetTestRunTask": { + "api.GetTestRunTask": { "type": "object", "properties": { "completed": { @@ -1385,7 +1372,7 @@ const docTemplate = `{ "result_files": { "type": "array", "items": { - "$ref": "#/definitions/web_api.GetTestRunTaskResult" + "$ref": "#/definitions/api.GetTestRunTaskResult" } }, "runtime": { @@ -1411,7 +1398,7 @@ const docTemplate = `{ } } }, - "web_api.GetTestRunTaskResult": { + "api.GetTestRunTaskResult": { "type": "object", "properties": { "index": { @@ -1431,7 +1418,7 @@ const docTemplate = `{ } } }, - "web_api.GetTestRunsResponse": { + "api.GetTestRunsResponse": { "type": "object", "properties": { "name": { @@ -1454,7 +1441,18 @@ const docTemplate = `{ } } }, - "web_api.GetTestsResponse": { + "api.GetTestYamlResponse": { + "type": "object", + "properties": { + "source": { + "type": "string" + }, + "yaml": { + "type": "string" + } + } + }, + "api.GetTestsResponse": { "type": "object", "properties": { "basePath": { @@ -1471,7 +1469,7 @@ const docTemplate = `{ } } }, - "web_api.LogEntry": { + "api.LogEntry": { "type": "object", "properties": { "data": { @@ -1497,7 +1495,7 @@ const docTemplate = `{ } } }, - "web_api.PostTestRunCancelRequest": { + "api.PostTestRunCancelRequest": { "type": "object", "properties": { "skip_cleanup": { @@ -1508,7 +1506,7 @@ const docTemplate = `{ } } }, - "web_api.PostTestRunCancelResponse": { + "api.PostTestRunCancelResponse": { "type": "object", "properties": { "name": { @@ -1525,7 +1523,7 @@ const docTemplate = `{ } } }, - "web_api.PostTestRunsDeleteRequest": { + "api.PostTestRunsDeleteRequest": { "type": "object", "properties": { "test_runs": { @@ -1536,7 +1534,7 @@ const docTemplate = `{ } } }, - "web_api.PostTestRunsDeleteResponse": { + "api.PostTestRunsDeleteResponse": { "type": "object", "properties": { "deleted": { @@ -1553,7 +1551,7 @@ const docTemplate = `{ } } }, - "web_api.PostTestRunsScheduleRequest": { + "api.PostTestRunsScheduleRequest": { "type": "object", "properties": { "allow_duplicate": { @@ -1571,7 +1569,7 @@ const docTemplate = `{ } } }, - "web_api.PostTestRunsScheduleResponse": { + "api.PostTestRunsScheduleResponse": { "type": "object", "properties": { "config": { @@ -1589,7 +1587,7 @@ const docTemplate = `{ } } }, - "web_api.PostTestsDeleteRequest": { + "api.PostTestsDeleteRequest": { "type": "object", "properties": { "tests": { @@ -1600,7 +1598,7 @@ const docTemplate = `{ } } }, - "web_api.PostTestsDeleteResponse": { + "api.PostTestsDeleteResponse": { "type": "object", "properties": { "deleted": { @@ -1617,7 +1615,7 @@ const docTemplate = `{ } } }, - "web_api.PostTestsRegisterExternalRequest": { + "api.PostTestsRegisterExternalRequest": { "type": "object", "properties": { "config": { @@ -1644,7 +1642,7 @@ const docTemplate = `{ } } }, - "web_api.PostTestsRegisterExternalResponse": { + "api.PostTestsRegisterExternalResponse": { "type": "object", "properties": { "config": { @@ -1659,7 +1657,7 @@ const docTemplate = `{ } } }, - "web_api.PostTestsRegisterRequest": { + "api.PostTestsRegisterRequest": { "type": "object", "properties": { "cleanupTasks": { @@ -1698,7 +1696,7 @@ const docTemplate = `{ } } }, - "web_api.PostTestsRegisterResponse": { + "api.PostTestsRegisterResponse": { "type": "object", "properties": { "config": { @@ -1713,7 +1711,7 @@ const docTemplate = `{ } } }, - "web_api.Response": { + "github_com_ethpandaops_assertoor_pkg_web_api.Response": { "type": "object", "properties": { "data": {}, @@ -1721,6 +1719,91 @@ const docTemplate = `{ "type": "string" } } + }, + "helper.RawMessage": { + "type": "object" + }, + "tasks.TaskDescriptorAPI": { + "type": "object", + "properties": { + "aliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "category": { + "type": "string" + }, + "configSchema": { + "type": "array", + "items": { + "type": "integer" + } + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "outputs": { + "type": "array", + "items": { + "$ref": "#/definitions/types.TaskOutputDefinition" + } + } + } + }, + "types.TaskOutputDefinition": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "types.TestSchedule": { + "type": "object", + "properties": { + "cron": { + "type": "array", + "items": { + "type": "string" + } + }, + "skipQueue": { + "type": "boolean" + }, + "startup": { + "type": "boolean" + } + } + }, + "types.TestStatus": { + "type": "string", + "enum": [ + "pending", + "running", + "success", + "failure", + "skipped", + "aborted" + ], + "x-enum-varnames": [ + "TestStatusPending", + "TestStatusRunning", + "TestStatusSuccess", + "TestStatusFailure", + "TestStatusSkipped", + "TestStatusAborted" + ] } }, "tags": [ diff --git a/pkg/web/api/docs/swagger.json b/pkg/web/api/docs/swagger.json index ead7d1f4..77d8f2bc 100644 --- a/pkg/web/api/docs/swagger.json +++ b/pkg/web/api/docs/swagger.json @@ -24,13 +24,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.GetClientsResponse" + "$ref": "#/definitions/api.GetClientsResponse" } } } @@ -40,13 +40,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -77,13 +77,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.GetLogsResponse" + "$ref": "#/definitions/api.GetLogsResponse" } } } @@ -93,13 +93,13 @@ "401": { "description": "Unauthorized", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -130,7 +130,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", @@ -146,7 +146,7 @@ "404": { "description": "Not Found", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -168,7 +168,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", @@ -213,13 +213,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.GetTestResponse" + "$ref": "#/definitions/api.GetTestResponse" } } } @@ -229,13 +229,79 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" + } + } + } + } + }, + "/api/v1/test/{testId}/yaml": { + "get": { + "description": "Returns the full YAML source for a test definition. For API-registered tests, returns the stored YAML. For external tests referenced by file/URL, loads and returns the content from the original source. Requires authentication as YAML may contain sensitive configuration.", + "produces": [ + "application/json" + ], + "tags": [ + "Test" + ], + "summary": "Get test YAML source by test ID", + "operationId": "getTestYaml", + "parameters": [ + { + "type": "string", + "description": "ID of the test definition to get YAML for", + "name": "testId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/api.GetTestYamlResponse" + } + } + } + ] + } + }, + "400": { + "description": "Failure", + "schema": { + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" + } + }, + "500": { + "description": "Server Error", + "schema": { + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -267,13 +333,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.GetTestRunResponse" + "$ref": "#/definitions/api.GetTestRunResponse" } } } @@ -283,13 +349,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -320,7 +386,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/web_api.PostTestRunCancelRequest" + "$ref": "#/definitions/api.PostTestRunCancelRequest" } } ], @@ -330,13 +396,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.PostTestRunCancelResponse" + "$ref": "#/definitions/api.PostTestRunCancelResponse" } } } @@ -346,13 +412,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -384,13 +450,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.GetTestRunDetailsResponse" + "$ref": "#/definitions/api.GetTestRunDetailsResponse" } } } @@ -400,13 +466,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -438,7 +504,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", @@ -454,13 +520,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -499,13 +565,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.GetTestRunDetailedTask" + "$ref": "#/definitions/api.GetTestRunDetailedTask" } } } @@ -515,19 +581,19 @@ "400": { "description": "Bad Request", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "404": { "description": "Not Found", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -535,7 +601,7 @@ }, "/api/v1/test_run/{runId}/task/{taskIndex}/result/{resultType}/{fileId}": { "get": { - "description": "Returns a specific result file from a task", + "description": "Returns a specific result file from a task. Requires authentication as results may contain sensitive data.", "produces": [ "application/octet-stream" ], @@ -584,19 +650,25 @@ "400": { "description": "Bad Request", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "404": { "description": "Not Found", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -627,7 +699,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", @@ -635,7 +707,7 @@ "data": { "type": "array", "items": { - "$ref": "#/definitions/web_api.GetTestRunsResponse" + "$ref": "#/definitions/api.GetTestRunsResponse" } } } @@ -646,13 +718,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -680,7 +752,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/web_api.PostTestRunsDeleteRequest" + "$ref": "#/definitions/api.PostTestRunsDeleteRequest" } } ], @@ -690,13 +762,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.PostTestRunsDeleteResponse" + "$ref": "#/definitions/api.PostTestRunsDeleteResponse" } } } @@ -706,13 +778,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -736,7 +808,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/web_api.PostTestRunsScheduleRequest" + "$ref": "#/definitions/api.PostTestRunsScheduleRequest" } } ], @@ -746,13 +818,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.PostTestRunsScheduleResponse" + "$ref": "#/definitions/api.PostTestRunsScheduleResponse" } } } @@ -762,13 +834,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -791,7 +863,7 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", @@ -799,7 +871,7 @@ "data": { "type": "array", "items": { - "$ref": "#/definitions/web_api.GetTestsResponse" + "$ref": "#/definitions/api.GetTestsResponse" } } } @@ -810,13 +882,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -844,7 +916,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/web_api.PostTestsDeleteRequest" + "$ref": "#/definitions/api.PostTestsDeleteRequest" } } ], @@ -854,13 +926,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.PostTestsDeleteResponse" + "$ref": "#/definitions/api.PostTestsDeleteResponse" } } } @@ -870,13 +942,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -904,7 +976,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/web_api.PostTestsRegisterRequest" + "$ref": "#/definitions/api.PostTestsRegisterRequest" } } ], @@ -914,13 +986,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.PostTestsRegisterResponse" + "$ref": "#/definitions/api.PostTestsRegisterResponse" } } } @@ -930,13 +1002,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -963,7 +1035,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/web_api.PostTestsRegisterExternalRequest" + "$ref": "#/definitions/api.PostTestsRegisterExternalRequest" } } ], @@ -973,13 +1045,13 @@ "schema": { "allOf": [ { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" }, { "type": "object", "properties": { "data": { - "$ref": "#/definitions/web_api.PostTestsRegisterExternalResponse" + "$ref": "#/definitions/api.PostTestsRegisterExternalResponse" } } } @@ -989,13 +1061,13 @@ "400": { "description": "Failure", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } }, "500": { "description": "Server Error", "schema": { - "$ref": "#/definitions/web_api.Response" + "$ref": "#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response" } } } @@ -1003,92 +1075,7 @@ } }, "definitions": { - "helper.RawMessage": { - "type": "object" - }, - "tasks.TaskDescriptorAPI": { - "type": "object", - "properties": { - "aliases": { - "type": "array", - "items": { - "type": "string" - } - }, - "category": { - "type": "string" - }, - "configSchema": { - "type": "array", - "items": { - "type": "integer" - } - }, - "description": { - "type": "string" - }, - "name": { - "type": "string" - }, - "outputs": { - "type": "array", - "items": { - "$ref": "#/definitions/types.TaskOutputDefinition" - } - } - } - }, - "types.TaskOutputDefinition": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "name": { - "type": "string" - }, - "type": { - "type": "string" - } - } - }, - "types.TestSchedule": { - "type": "object", - "properties": { - "cron": { - "type": "array", - "items": { - "type": "string" - } - }, - "skipQueue": { - "type": "boolean" - }, - "startup": { - "type": "boolean" - } - } - }, - "types.TestStatus": { - "type": "string", - "enum": [ - "pending", - "running", - "success", - "failure", - "skipped", - "aborted" - ], - "x-enum-varnames": [ - "TestStatusPending", - "TestStatusRunning", - "TestStatusSuccess", - "TestStatusFailure", - "TestStatusSkipped", - "TestStatusAborted" - ] - }, - "web_api.ClientResponse": { + "api.ClientResponse": { "type": "object", "properties": { "cl_error": { @@ -1147,7 +1134,7 @@ } } }, - "web_api.GetClientsResponse": { + "api.GetClientsResponse": { "type": "object", "properties": { "client_count": { @@ -1156,23 +1143,23 @@ "clients": { "type": "array", "items": { - "$ref": "#/definitions/web_api.ClientResponse" + "$ref": "#/definitions/api.ClientResponse" } } } }, - "web_api.GetLogsResponse": { + "api.GetLogsResponse": { "type": "object", "properties": { "log": { "type": "array", "items": { - "$ref": "#/definitions/web_api.LogEntry" + "$ref": "#/definitions/api.LogEntry" } } } }, - "web_api.GetTestResponse": { + "api.GetTestResponse": { "type": "object", "properties": { "basePath": { @@ -1210,7 +1197,7 @@ } } }, - "web_api.GetTestRunDetailedTask": { + "api.GetTestRunDetailedTask": { "type": "object", "properties": { "completed": { @@ -1225,7 +1212,7 @@ "log": { "type": "array", "items": { - "$ref": "#/definitions/web_api.GetTestRunDetailedTaskLog" + "$ref": "#/definitions/api.GetTestRunDetailedTaskLog" } }, "name": { @@ -1272,7 +1259,7 @@ } } }, - "web_api.GetTestRunDetailedTaskLog": { + "api.GetTestRunDetailedTaskLog": { "type": "object", "properties": { "data": { @@ -1295,7 +1282,7 @@ } } }, - "web_api.GetTestRunDetailsResponse": { + "api.GetTestRunDetailsResponse": { "type": "object", "properties": { "name": { @@ -1316,7 +1303,7 @@ "tasks": { "type": "array", "items": { - "$ref": "#/definitions/web_api.GetTestRunDetailedTask" + "$ref": "#/definitions/api.GetTestRunDetailedTask" } }, "test_id": { @@ -1324,7 +1311,7 @@ } } }, - "web_api.GetTestRunResponse": { + "api.GetTestRunResponse": { "type": "object", "properties": { "name": { @@ -1345,7 +1332,7 @@ "tasks": { "type": "array", "items": { - "$ref": "#/definitions/web_api.GetTestRunTask" + "$ref": "#/definitions/api.GetTestRunTask" } }, "test_id": { @@ -1353,7 +1340,7 @@ } } }, - "web_api.GetTestRunTask": { + "api.GetTestRunTask": { "type": "object", "properties": { "completed": { @@ -1377,7 +1364,7 @@ "result_files": { "type": "array", "items": { - "$ref": "#/definitions/web_api.GetTestRunTaskResult" + "$ref": "#/definitions/api.GetTestRunTaskResult" } }, "runtime": { @@ -1403,7 +1390,7 @@ } } }, - "web_api.GetTestRunTaskResult": { + "api.GetTestRunTaskResult": { "type": "object", "properties": { "index": { @@ -1423,7 +1410,7 @@ } } }, - "web_api.GetTestRunsResponse": { + "api.GetTestRunsResponse": { "type": "object", "properties": { "name": { @@ -1446,7 +1433,18 @@ } } }, - "web_api.GetTestsResponse": { + "api.GetTestYamlResponse": { + "type": "object", + "properties": { + "source": { + "type": "string" + }, + "yaml": { + "type": "string" + } + } + }, + "api.GetTestsResponse": { "type": "object", "properties": { "basePath": { @@ -1463,7 +1461,7 @@ } } }, - "web_api.LogEntry": { + "api.LogEntry": { "type": "object", "properties": { "data": { @@ -1489,7 +1487,7 @@ } } }, - "web_api.PostTestRunCancelRequest": { + "api.PostTestRunCancelRequest": { "type": "object", "properties": { "skip_cleanup": { @@ -1500,7 +1498,7 @@ } } }, - "web_api.PostTestRunCancelResponse": { + "api.PostTestRunCancelResponse": { "type": "object", "properties": { "name": { @@ -1517,7 +1515,7 @@ } } }, - "web_api.PostTestRunsDeleteRequest": { + "api.PostTestRunsDeleteRequest": { "type": "object", "properties": { "test_runs": { @@ -1528,7 +1526,7 @@ } } }, - "web_api.PostTestRunsDeleteResponse": { + "api.PostTestRunsDeleteResponse": { "type": "object", "properties": { "deleted": { @@ -1545,7 +1543,7 @@ } } }, - "web_api.PostTestRunsScheduleRequest": { + "api.PostTestRunsScheduleRequest": { "type": "object", "properties": { "allow_duplicate": { @@ -1563,7 +1561,7 @@ } } }, - "web_api.PostTestRunsScheduleResponse": { + "api.PostTestRunsScheduleResponse": { "type": "object", "properties": { "config": { @@ -1581,7 +1579,7 @@ } } }, - "web_api.PostTestsDeleteRequest": { + "api.PostTestsDeleteRequest": { "type": "object", "properties": { "tests": { @@ -1592,7 +1590,7 @@ } } }, - "web_api.PostTestsDeleteResponse": { + "api.PostTestsDeleteResponse": { "type": "object", "properties": { "deleted": { @@ -1609,7 +1607,7 @@ } } }, - "web_api.PostTestsRegisterExternalRequest": { + "api.PostTestsRegisterExternalRequest": { "type": "object", "properties": { "config": { @@ -1636,7 +1634,7 @@ } } }, - "web_api.PostTestsRegisterExternalResponse": { + "api.PostTestsRegisterExternalResponse": { "type": "object", "properties": { "config": { @@ -1651,7 +1649,7 @@ } } }, - "web_api.PostTestsRegisterRequest": { + "api.PostTestsRegisterRequest": { "type": "object", "properties": { "cleanupTasks": { @@ -1690,7 +1688,7 @@ } } }, - "web_api.PostTestsRegisterResponse": { + "api.PostTestsRegisterResponse": { "type": "object", "properties": { "config": { @@ -1705,7 +1703,7 @@ } } }, - "web_api.Response": { + "github_com_ethpandaops_assertoor_pkg_web_api.Response": { "type": "object", "properties": { "data": {}, @@ -1713,6 +1711,91 @@ "type": "string" } } + }, + "helper.RawMessage": { + "type": "object" + }, + "tasks.TaskDescriptorAPI": { + "type": "object", + "properties": { + "aliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "category": { + "type": "string" + }, + "configSchema": { + "type": "array", + "items": { + "type": "integer" + } + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "outputs": { + "type": "array", + "items": { + "$ref": "#/definitions/types.TaskOutputDefinition" + } + } + } + }, + "types.TaskOutputDefinition": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "types.TestSchedule": { + "type": "object", + "properties": { + "cron": { + "type": "array", + "items": { + "type": "string" + } + }, + "skipQueue": { + "type": "boolean" + }, + "startup": { + "type": "boolean" + } + } + }, + "types.TestStatus": { + "type": "string", + "enum": [ + "pending", + "running", + "success", + "failure", + "skipped", + "aborted" + ], + "x-enum-varnames": [ + "TestStatusPending", + "TestStatusRunning", + "TestStatusSuccess", + "TestStatusFailure", + "TestStatusSkipped", + "TestStatusAborted" + ] } }, "tags": [ diff --git a/pkg/web/api/docs/swagger.yaml b/pkg/web/api/docs/swagger.yaml index f26f9dac..ad4d22ea 100644 --- a/pkg/web/api/docs/swagger.yaml +++ b/pkg/web/api/docs/swagger.yaml @@ -1,64 +1,5 @@ definitions: - helper.RawMessage: - type: object - tasks.TaskDescriptorAPI: - properties: - aliases: - items: - type: string - type: array - category: - type: string - configSchema: - items: - type: integer - type: array - description: - type: string - name: - type: string - outputs: - items: - $ref: '#/definitions/types.TaskOutputDefinition' - type: array - type: object - types.TaskOutputDefinition: - properties: - description: - type: string - name: - type: string - type: - type: string - type: object - types.TestSchedule: - properties: - cron: - items: - type: string - type: array - skipQueue: - type: boolean - startup: - type: boolean - type: object - types.TestStatus: - enum: - - pending - - running - - success - - failure - - skipped - - aborted - type: string - x-enum-varnames: - - TestStatusPending - - TestStatusRunning - - TestStatusSuccess - - TestStatusFailure - - TestStatusSkipped - - TestStatusAborted - web_api.ClientResponse: + api.ClientResponse: properties: cl_error: type: string @@ -97,23 +38,23 @@ definitions: name: type: string type: object - web_api.GetClientsResponse: + api.GetClientsResponse: properties: client_count: type: integer clients: items: - $ref: '#/definitions/web_api.ClientResponse' + $ref: '#/definitions/api.ClientResponse' type: array type: object - web_api.GetLogsResponse: + api.GetLogsResponse: properties: log: items: - $ref: '#/definitions/web_api.LogEntry' + $ref: '#/definitions/api.LogEntry' type: array type: object - web_api.GetTestResponse: + api.GetTestResponse: properties: basePath: type: string @@ -139,7 +80,7 @@ definitions: description: Config with global vars merged in type: object type: object - web_api.GetTestRunDetailedTask: + api.GetTestRunDetailedTask: properties: completed: type: boolean @@ -149,7 +90,7 @@ definitions: type: integer log: items: - $ref: '#/definitions/web_api.GetTestRunDetailedTaskLog' + $ref: '#/definitions/api.GetTestRunDetailedTaskLog' type: array name: type: string @@ -180,7 +121,7 @@ definitions: title: type: string type: object - web_api.GetTestRunDetailedTaskLog: + api.GetTestRunDetailedTaskLog: properties: data: additionalProperties: @@ -195,7 +136,7 @@ definitions: time: type: string type: object - web_api.GetTestRunDetailsResponse: + api.GetTestRunDetailsResponse: properties: name: type: string @@ -209,12 +150,12 @@ definitions: type: integer tasks: items: - $ref: '#/definitions/web_api.GetTestRunDetailedTask' + $ref: '#/definitions/api.GetTestRunDetailedTask' type: array test_id: type: string type: object - web_api.GetTestRunResponse: + api.GetTestRunResponse: properties: name: type: string @@ -228,12 +169,12 @@ definitions: type: integer tasks: items: - $ref: '#/definitions/web_api.GetTestRunTask' + $ref: '#/definitions/api.GetTestRunTask' type: array test_id: type: string type: object - web_api.GetTestRunTask: + api.GetTestRunTask: properties: completed: type: boolean @@ -249,7 +190,7 @@ definitions: type: string result_files: items: - $ref: '#/definitions/web_api.GetTestRunTaskResult' + $ref: '#/definitions/api.GetTestRunTaskResult' type: array runtime: type: integer @@ -266,7 +207,7 @@ definitions: title: type: string type: object - web_api.GetTestRunTaskResult: + api.GetTestRunTaskResult: properties: index: type: integer @@ -279,7 +220,7 @@ definitions: url: type: string type: object - web_api.GetTestRunsResponse: + api.GetTestRunsResponse: properties: name: type: string @@ -294,7 +235,14 @@ definitions: test_id: type: string type: object - web_api.GetTestsResponse: + api.GetTestYamlResponse: + properties: + source: + type: string + yaml: + type: string + type: object + api.GetTestsResponse: properties: basePath: type: string @@ -305,7 +253,7 @@ definitions: source: type: string type: object - web_api.LogEntry: + api.LogEntry: properties: data: additionalProperties: @@ -322,14 +270,14 @@ definitions: time: type: string type: object - web_api.PostTestRunCancelRequest: + api.PostTestRunCancelRequest: properties: skip_cleanup: type: boolean test_id: type: string type: object - web_api.PostTestRunCancelResponse: + api.PostTestRunCancelResponse: properties: name: type: string @@ -340,14 +288,14 @@ definitions: test_id: type: string type: object - web_api.PostTestRunsDeleteRequest: + api.PostTestRunsDeleteRequest: properties: test_runs: items: type: integer type: array type: object - web_api.PostTestRunsDeleteResponse: + api.PostTestRunsDeleteResponse: properties: deleted: items: @@ -358,7 +306,7 @@ definitions: type: string type: array type: object - web_api.PostTestRunsScheduleRequest: + api.PostTestRunsScheduleRequest: properties: allow_duplicate: type: boolean @@ -370,7 +318,7 @@ definitions: test_id: type: string type: object - web_api.PostTestRunsScheduleResponse: + api.PostTestRunsScheduleResponse: properties: config: additionalProperties: {} @@ -382,14 +330,14 @@ definitions: test_id: type: string type: object - web_api.PostTestsDeleteRequest: + api.PostTestsDeleteRequest: properties: tests: items: type: string type: array type: object - web_api.PostTestsDeleteResponse: + api.PostTestsDeleteResponse: properties: deleted: items: @@ -400,7 +348,7 @@ definitions: type: string type: array type: object - web_api.PostTestsRegisterExternalRequest: + api.PostTestsRegisterExternalRequest: properties: config: additionalProperties: true @@ -418,7 +366,7 @@ definitions: timeout: type: integer type: object - web_api.PostTestsRegisterExternalResponse: + api.PostTestsRegisterExternalResponse: properties: config: additionalProperties: {} @@ -428,7 +376,7 @@ definitions: test_id: type: string type: object - web_api.PostTestsRegisterRequest: + api.PostTestsRegisterRequest: properties: cleanupTasks: items: @@ -454,7 +402,7 @@ definitions: timeout: type: string type: object - web_api.PostTestsRegisterResponse: + api.PostTestsRegisterResponse: properties: config: additionalProperties: {} @@ -464,12 +412,71 @@ definitions: test_id: type: string type: object - web_api.Response: + github_com_ethpandaops_assertoor_pkg_web_api.Response: properties: data: {} status: type: string type: object + helper.RawMessage: + type: object + tasks.TaskDescriptorAPI: + properties: + aliases: + items: + type: string + type: array + category: + type: string + configSchema: + items: + type: integer + type: array + description: + type: string + name: + type: string + outputs: + items: + $ref: '#/definitions/types.TaskOutputDefinition' + type: array + type: object + types.TaskOutputDefinition: + properties: + description: + type: string + name: + type: string + type: + type: string + type: object + types.TestSchedule: + properties: + cron: + items: + type: string + type: array + skipQueue: + type: boolean + startup: + type: boolean + type: object + types.TestStatus: + enum: + - pending + - running + - success + - failure + - skipped + - aborted + type: string + x-enum-varnames: + - TestStatusPending + - TestStatusRunning + - TestStatusSuccess + - TestStatusFailure + - TestStatusSkipped + - TestStatusAborted info: contact: {} description: API for querying information about Assertoor tests @@ -488,19 +495,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/web_api.Response' + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' - properties: data: - $ref: '#/definitions/web_api.GetClientsResponse' + $ref: '#/definitions/api.GetClientsResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' summary: Get list of configured clients tags: - Client @@ -521,19 +528,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/web_api.Response' + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' - properties: data: - $ref: '#/definitions/web_api.GetLogsResponse' + $ref: '#/definitions/api.GetLogsResponse' type: object "401": description: Unauthorized schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' summary: Get application logs tags: - Logs @@ -553,7 +560,7 @@ paths: description: OK schema: allOf: - - $ref: '#/definitions/web_api.Response' + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' - properties: data: $ref: '#/definitions/tasks.TaskDescriptorAPI' @@ -561,7 +568,7 @@ paths: "404": description: Not Found schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' summary: Get a specific task descriptor tags: - Task @@ -576,7 +583,7 @@ paths: description: OK schema: allOf: - - $ref: '#/definitions/web_api.Response' + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' - properties: data: items: @@ -603,22 +610,66 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/web_api.Response' + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' - properties: data: - $ref: '#/definitions/web_api.GetTestResponse' + $ref: '#/definitions/api.GetTestResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' summary: Get test definition by test ID tags: - Test + /api/v1/test/{testId}/yaml: + get: + description: Returns the full YAML source for a test definition. For API-registered + tests, returns the stored YAML. For external tests referenced by file/URL, + loads and returns the content from the original source. Requires authentication + as YAML may contain sensitive configuration. + operationId: getTestYaml + parameters: + - description: ID of the test definition to get YAML for + in: path + name: testId + required: true + type: string + produces: + - application/json + responses: + "200": + description: Success + schema: + allOf: + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' + - properties: + data: + $ref: '#/definitions/api.GetTestYamlResponse' + type: object + "400": + description: Failure + schema: + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' + "500": + description: Server Error + schema: + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' + summary: Get test YAML source by test ID + tags: + - Test /api/v1/test_run/{runId}: get: description: Returns the run details with given ID. Includes a summary and a @@ -637,19 +688,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/web_api.Response' + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' - properties: data: - $ref: '#/definitions/web_api.GetTestRunResponse' + $ref: '#/definitions/api.GetTestRunResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' summary: Get test run by run ID tags: - TestRun @@ -668,7 +719,7 @@ paths: name: cancelOptions required: true schema: - $ref: '#/definitions/web_api.PostTestRunCancelRequest' + $ref: '#/definitions/api.PostTestRunCancelRequest' produces: - application/json responses: @@ -676,19 +727,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/web_api.Response' + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' - properties: data: - $ref: '#/definitions/web_api.PostTestRunCancelResponse' + $ref: '#/definitions/api.PostTestRunCancelResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' summary: Cancel test run by test ID tags: - TestRun @@ -710,19 +761,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/web_api.Response' + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' - properties: data: - $ref: '#/definitions/web_api.GetTestRunDetailsResponse' + $ref: '#/definitions/api.GetTestRunDetailsResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' summary: Get detailed test run by run ID tags: - TestRun @@ -743,7 +794,7 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/web_api.Response' + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' - properties: data: type: string @@ -751,11 +802,11 @@ paths: "400": description: Failure schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' summary: Get test run status by run ID tags: - TestRun @@ -782,29 +833,30 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/web_api.Response' + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' - properties: data: - $ref: '#/definitions/web_api.GetTestRunDetailedTask' + $ref: '#/definitions/api.GetTestRunDetailedTask' type: object "400": description: Bad Request schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "404": description: Not Found schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' summary: Get detailed task of a given test run tags: - TestRun /api/v1/test_run/{runId}/task/{taskIndex}/result/{resultType}/{fileId}: get: - description: Returns a specific result file from a task + description: Returns a specific result file from a task. Requires authentication + as results may contain sensitive data. operationId: getTaskResult parameters: - description: ID of the test run @@ -837,15 +889,19 @@ paths: "400": description: Bad Request schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "404": description: Not Found schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' summary: Get task result file tags: - TestRun @@ -865,21 +921,21 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/web_api.Response' + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' - properties: data: items: - $ref: '#/definitions/web_api.GetTestRunsResponse' + $ref: '#/definitions/api.GetTestRunsResponse' type: array type: object "400": description: Failure schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' summary: Get list of test runs tags: - TestRun @@ -896,7 +952,7 @@ paths: name: testConfig required: true schema: - $ref: '#/definitions/web_api.PostTestRunsDeleteRequest' + $ref: '#/definitions/api.PostTestRunsDeleteRequest' produces: - application/json responses: @@ -904,19 +960,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/web_api.Response' + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' - properties: data: - $ref: '#/definitions/web_api.PostTestRunsDeleteResponse' + $ref: '#/definitions/api.PostTestRunsDeleteResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' summary: Delete test runs tags: - TestRun @@ -930,7 +986,7 @@ paths: name: runOptions required: true schema: - $ref: '#/definitions/web_api.PostTestRunsScheduleRequest' + $ref: '#/definitions/api.PostTestRunsScheduleRequest' produces: - application/json responses: @@ -938,19 +994,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/web_api.Response' + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' - properties: data: - $ref: '#/definitions/web_api.PostTestRunsScheduleResponse' + $ref: '#/definitions/api.PostTestRunsScheduleResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' summary: Schedule new test run by test ID tags: - TestRun @@ -966,21 +1022,21 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/web_api.Response' + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' - properties: data: items: - $ref: '#/definitions/web_api.GetTestsResponse' + $ref: '#/definitions/api.GetTestsResponse' type: array type: object "400": description: Failure schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' summary: Get list of test definitions tags: - Test @@ -997,7 +1053,7 @@ paths: name: testConfig required: true schema: - $ref: '#/definitions/web_api.PostTestsDeleteRequest' + $ref: '#/definitions/api.PostTestsDeleteRequest' produces: - application/json responses: @@ -1005,19 +1061,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/web_api.Response' + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' - properties: data: - $ref: '#/definitions/web_api.PostTestsDeleteResponse' + $ref: '#/definitions/api.PostTestsDeleteResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' summary: Delete tests tags: - Test @@ -1034,7 +1090,7 @@ paths: name: testConfig required: true schema: - $ref: '#/definitions/web_api.PostTestsRegisterRequest' + $ref: '#/definitions/api.PostTestsRegisterRequest' produces: - application/json responses: @@ -1042,19 +1098,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/web_api.Response' + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' - properties: data: - $ref: '#/definitions/web_api.PostTestsRegisterResponse' + $ref: '#/definitions/api.PostTestsRegisterResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' summary: Register new test via yaml configuration tags: - Test @@ -1070,7 +1126,7 @@ paths: name: externalTestConfig required: true schema: - $ref: '#/definitions/web_api.PostTestsRegisterExternalRequest' + $ref: '#/definitions/api.PostTestsRegisterExternalRequest' produces: - application/json responses: @@ -1078,19 +1134,19 @@ paths: description: Success schema: allOf: - - $ref: '#/definitions/web_api.Response' + - $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' - properties: data: - $ref: '#/definitions/web_api.PostTestsRegisterExternalResponse' + $ref: '#/definitions/api.PostTestsRegisterExternalResponse' type: object "400": description: Failure schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' "500": description: Server Error schema: - $ref: '#/definitions/web_api.Response' + $ref: '#/definitions/github_com_ethpandaops_assertoor_pkg_web_api.Response' summary: Register new test via external test configuration tags: - Test diff --git a/pkg/web/api/get_task_result_api.go b/pkg/web/api/get_task_result_api.go index f687079d..be426dbb 100644 --- a/pkg/web/api/get_task_result_api.go +++ b/pkg/web/api/get_task_result_api.go @@ -18,7 +18,7 @@ import ( // @Id getTaskResult // @Summary Get task result file // @Tags TestRun -// @Description Returns a specific result file from a task +// @Description Returns a specific result file from a task. Requires authentication as results may contain sensitive data. // @Produce octet-stream // @Param runId path string true "ID of the test run" // @Param taskId path string true "ID of the task" @@ -26,10 +26,19 @@ import ( // @Param fileId path string true "Index or name of the result file" // @Success 200 {file} binary "Success" // @Failure 400 {object} Response "Bad Request" +// @Failure 401 {object} Response "Unauthorized" // @Failure 404 {object} Response "Not Found" // @Failure 500 {object} Response "Server Error" // @Router /api/v1/test_run/{runId}/task/{taskIndex}/result/{resultType}/{fileId} [get] func (ah *APIHandler) GetTaskResult(w http.ResponseWriter, r *http.Request) { + // Check authentication - task results may contain sensitive data + if !ah.checkAuth(r) { + w.Header().Set("Content-Type", contentTypeJSON) + ah.sendUnauthorizedResponse(w, r.URL.String()) + + return + } + vars := mux.Vars(r) runID, err := strconv.ParseUint(vars["runId"], 10, 64) diff --git a/pkg/web/api/get_test_api.go b/pkg/web/api/get_test_api.go index de4a32c5..cf5b195c 100644 --- a/pkg/web/api/get_test_api.go +++ b/pkg/web/api/get_test_api.go @@ -60,29 +60,34 @@ func (ah *APIHandler) GetTest(w http.ResponseWriter, r *http.Request) { testConfig := testDescriptor.Config() response := &GetTestResponse{ - ID: testDescriptor.ID(), - Source: testDescriptor.Source(), - BasePath: testDescriptor.BasePath(), - Name: testConfig.Name, - Timeout: uint64(testConfig.Timeout.Seconds()), - Config: testConfig.Config, - ConfigVars: testConfig.ConfigVars, - Schedule: testConfig.Schedule, + ID: testDescriptor.ID(), + Source: testDescriptor.Source(), + BasePath: testDescriptor.BasePath(), + Name: testConfig.Name, + Timeout: uint64(testConfig.Timeout.Seconds()), + Schedule: testConfig.Schedule, } - // Include vars with global variables merged in (only for authenticated users) - // Start with config defaults, overlay with resolved values from variable scope - if ah.checkAuth(r) && len(testConfig.Config) > 0 { - resolvedVars := make(map[string]any, len(testConfig.Config)) - for key, defaultValue := range testConfig.Config { - if resolved := testDescriptor.Vars().GetVar(key); resolved != nil { - resolvedVars[key] = resolved - } else { - resolvedVars[key] = defaultValue + // Config, ConfigVars, and Vars contain potentially sensitive information + // Only include them for authenticated users + if ah.checkAuth(r) { + response.Config = testConfig.Config + response.ConfigVars = testConfig.ConfigVars + + // Include vars with global variables merged in + // Start with config defaults, overlay with resolved values from variable scope + if len(testConfig.Config) > 0 { + resolvedVars := make(map[string]any, len(testConfig.Config)) + for key, defaultValue := range testConfig.Config { + if resolved := testDescriptor.Vars().GetVar(key); resolved != nil { + resolvedVars[key] = resolved + } else { + resolvedVars[key] = defaultValue + } } - } - response.Vars = resolvedVars + response.Vars = resolvedVars + } } ah.sendOKResponse(w, r.URL.String(), response) diff --git a/pkg/web/api/get_test_yaml_api.go b/pkg/web/api/get_test_yaml_api.go new file mode 100644 index 00000000..211ce057 --- /dev/null +++ b/pkg/web/api/get_test_yaml_api.go @@ -0,0 +1,136 @@ +package api + +import ( + "context" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/gorilla/mux" +) + +type GetTestYamlResponse struct { + Yaml string `json:"yaml"` + Source string `json:"source"` +} + +// GetTestYaml godoc +// @Id getTestYaml +// @Summary Get test YAML source by test ID +// @Tags Test +// @Description Returns the full YAML source for a test definition. For API-registered tests, returns the stored YAML. For external tests referenced by file/URL, loads and returns the content from the original source. Requires authentication as YAML may contain sensitive configuration. +// @Produce json +// @Param testId path string true "ID of the test definition to get YAML for" +// @Success 200 {object} Response{data=GetTestYamlResponse} "Success" +// @Failure 400 {object} Response "Failure" +// @Failure 401 {object} Response "Unauthorized" +// @Failure 404 {object} Response "Not Found" +// @Failure 500 {object} Response "Server Error" +// @Router /api/v1/test/{testId}/yaml [get] +func (ah *APIHandler) GetTestYaml(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", contentTypeJSON) + + // Check authentication - YAML may contain sensitive configuration + if !ah.checkAuth(r) { + ah.sendUnauthorizedResponse(w, r.URL.String()) + return + } + + vars := mux.Vars(r) + if vars["testId"] == "" { + ah.sendErrorResponse(w, r.URL.String(), "testId missing", http.StatusBadRequest) + return + } + + testID := vars["testId"] + + // Get test config from database + testConfig, err := ah.coordinator.Database().GetTestConfig(testID) + if err != nil { + ah.sendErrorResponse(w, r.URL.String(), fmt.Sprintf("test not found: %v", err), http.StatusNotFound) + return + } + + var yamlContent string + + var source string + + // Determine YAML source type and load content + switch { + case testConfig.YamlSource != "": + // Stored YAML source (API-registered tests) + yamlContent = testConfig.YamlSource + source = "database" + + case testConfig.Source != "" && testConfig.Source != "api-call": + // Load from external file/URL + var loadErr error + + yamlContent, loadErr = ah.loadExternalYaml(r.Context(), testConfig.Source) + if loadErr != nil { + ah.sendErrorResponse(w, r.URL.String(), fmt.Sprintf("failed to load external YAML: %v", loadErr), http.StatusInternalServerError) + return + } + + source = testConfig.Source + + default: + // No YAML source available + ah.sendErrorResponse(w, r.URL.String(), "no YAML source available for this test", http.StatusNotFound) + return + } + + response := &GetTestYamlResponse{ + Yaml: yamlContent, + Source: source, + } + + ah.sendOKResponse(w, r.URL.String(), response) +} + +// loadExternalYaml loads YAML content from a file path or URL. +func (ah *APIHandler) loadExternalYaml(ctx context.Context, source string) (string, error) { + // Check if source is a URL + if strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://") { + // Handle "external:" prefix if present + cleanSource := strings.TrimPrefix(source, "external:") + + client := &http.Client{Timeout: time.Second * 120} + + req, err := http.NewRequestWithContext(ctx, "GET", cleanSource, http.NoBody) + if err != nil { + return "", fmt.Errorf("failed to create request: %w", err) + } + + resp, err := client.Do(req) + if err != nil { + return "", fmt.Errorf("failed to fetch URL: %w", err) + } + + defer func() { + if closeErr := resp.Body.Close(); closeErr != nil { + ah.coordinator.Logger().WithError(closeErr).Warn("failed to close response body") + } + }() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("HTTP error: %v %v", resp.StatusCode, resp.Status) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("failed to read response: %w", err) + } + + return string(body), nil + } + + // It's a local file path - strip "external:" prefix if present + filePath := strings.TrimPrefix(source, "external:") + + // For security, we don't allow reading arbitrary files + // Only return error indicating the test needs to be loaded from file + return "", fmt.Errorf("cannot load local file %s via API - test must be re-registered with YAML source", filePath) +} diff --git a/pkg/web/server.go b/pkg/web/server.go index 9255cea3..9281580a 100644 --- a/pkg/web/server.go +++ b/pkg/web/server.go @@ -95,6 +95,7 @@ func (ws *Server) ConfigureRoutes(frontendConfig *types.FrontendConfig, apiConfi // public apis ws.router.HandleFunc("/api/v1/tests", apiHandler.GetTests).Methods("GET") ws.router.HandleFunc("/api/v1/test/{testId}", apiHandler.GetTest).Methods("GET") + ws.router.HandleFunc("/api/v1/test/{testId}/yaml", apiHandler.GetTestYaml).Methods("GET") ws.router.HandleFunc("/api/v1/test_runs", apiHandler.GetTestRuns).Methods("GET") ws.router.HandleFunc("/api/v1/test_run/{runId}", apiHandler.GetTestRun).Methods("GET") ws.router.HandleFunc("/api/v1/test_run/{runId}/status", apiHandler.GetTestRunStatus).Methods("GET") diff --git a/web-ui/src/api/client.ts b/web-ui/src/api/client.ts index c938d802..d851ba5b 100644 --- a/web-ui/src/api/client.ts +++ b/web-ui/src/api/client.ts @@ -3,6 +3,7 @@ import type { TestRun, Test, TestDetails, + TestYamlResponse, TestRunDetails, TaskDetails, TaskDescriptor, @@ -85,6 +86,11 @@ export async function getTestDetails(testId: string): Promise { return fetchApiWithAuth(`/test/${encodeURIComponent(testId)}`); } +// Test YAML source (for loading existing tests in builder) +export async function getTestYaml(testId: string): Promise { + return fetchApiWithAuth(`/test/${encodeURIComponent(testId)}/yaml`); +} + // Clients list export async function getClients(): Promise { return fetchApi('/clients'); diff --git a/web-ui/src/components/task/TaskDetails.tsx b/web-ui/src/components/task/TaskDetails.tsx index 1554109d..eb5179a9 100644 --- a/web-ui/src/components/task/TaskDetails.tsx +++ b/web-ui/src/components/task/TaskDetails.tsx @@ -1,5 +1,6 @@ import { useState, useRef, useEffect, useCallback } from 'react'; import { useTaskDetails } from '../../hooks/useApi'; +import { useAuthContext } from '../../context/AuthContext'; import type { TaskState, TaskLogEntry } from '../../types/api'; import { formatDurationMs, formatTime } from '../../utils/time'; @@ -10,16 +11,21 @@ interface TaskDetailsProps { function TaskDetails({ runId, task }: TaskDetailsProps) { const [activeTab, setActiveTab] = useState<'overview' | 'logs' | 'config' | 'result'>('overview'); - const { data: details } = useTaskDetails(runId, task.index); + const { isLoggedIn } = useAuthContext(); + const { data: details, error: detailsError } = useTaskDetails(runId, task.index, { enabled: isLoggedIn }); const scrollContainerRef = useRef(null); const tabs = [ { id: 'overview' as const, label: 'Overview' }, - { id: 'logs' as const, label: 'Logs' }, - { id: 'config' as const, label: 'Config' }, - { id: 'result' as const, label: 'Result' }, + { id: 'logs' as const, label: 'Logs', requiresAuth: true }, + { id: 'config' as const, label: 'Config', requiresAuth: true }, + { id: 'result' as const, label: 'Result', requiresAuth: true }, ]; + // Check if current tab requires auth + const currentTabRequiresAuth = tabs.find(t => t.id === activeTab)?.requiresAuth; + const showAuthMessage = currentTabRequiresAuth && !isLoggedIn; + return (
{/* Tabs */} @@ -35,16 +41,38 @@ function TaskDetails({ runId, task }: TaskDetailsProps) { }`} > {tab.label} + {tab.requiresAuth && !isLoggedIn && ( + 🔒 + )} ))}
{/* Tab content */}
- {activeTab === 'overview' && } - {activeTab === 'logs' && } - {activeTab === 'config' && } - {activeTab === 'result' && } + {showAuthMessage ? ( +
+

+ Log in to view task {activeTab} +

+

+ Task details contain potentially sensitive configuration and logs +

+
+ ) : detailsError && activeTab !== 'overview' ? ( +
+

+ Failed to load task details +

+
+ ) : ( + <> + {activeTab === 'overview' && } + {activeTab === 'logs' && } + {activeTab === 'config' && } + {activeTab === 'result' && } + + )}
); diff --git a/web-ui/src/hooks/useApi.ts b/web-ui/src/hooks/useApi.ts index 02ba06ac..c3dc7544 100644 --- a/web-ui/src/hooks/useApi.ts +++ b/web-ui/src/hooks/useApi.ts @@ -100,6 +100,16 @@ export function useTestDetails(testId: string, options?: { enabled?: boolean }) }); } +// Get test YAML source (for loading existing tests in builder) +export function useTestYaml(testId: string, options?: { enabled?: boolean }) { + return useQuery({ + queryKey: ['testYaml', testId] as const, + queryFn: () => api.getTestYaml(testId), + enabled: options?.enabled !== false && !!testId, + staleTime: 60000, + }); +} + export function useCancelTestRun() { const queryClient = useQueryClient(); diff --git a/web-ui/src/pages/TestBuilder.tsx b/web-ui/src/pages/TestBuilder.tsx index f9f03cd0..2f70be88 100644 --- a/web-ui/src/pages/TestBuilder.tsx +++ b/web-ui/src/pages/TestBuilder.tsx @@ -1,10 +1,9 @@ -import { useEffect, useMemo } from 'react'; +import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; import { useBuilderStore } from '../stores/builderStore'; -import { useTestDetails, useTaskDescriptors } from '../hooks/useApi'; +import { useTestYaml } from '../hooks/useApi'; import { useAuthContext } from '../context/AuthContext'; import BuilderLayout from '../components/builder/BuilderLayout'; -import type { TaskDescriptor } from '../types/api'; function TestBuilder() { const [searchParams] = useSearchParams(); @@ -12,39 +11,29 @@ function TestBuilder() { const { isLoggedIn } = useAuthContext(); const reset = useBuilderStore((state) => state.reset); - const loadTest = useBuilderStore((state) => state.loadTest); + const loadFromYaml = useBuilderStore((state) => state.loadFromYaml); + const setSourceTestId = useBuilderStore((state) => state.setSourceTestId); const sourceTestId = useBuilderStore((state) => state.sourceTestId); - // Fetch test details if editing existing test - const { data: testDetails, isLoading: testLoading, error: testError } = useTestDetails(testId || '', { + // Fetch test YAML if editing existing test + const { data: testYaml, isLoading: yamlLoading, error: testError } = useTestYaml(testId || '', { enabled: !!testId, }); - // Fetch task descriptors for loading - const { data: descriptors, isLoading: descriptorsLoading } = useTaskDescriptors(); - - // Build descriptor map - const descriptorMap = useMemo(() => { - const map = new Map(); - if (descriptors) { - for (const d of descriptors) { - map.set(d.name, d); - } - } - return map; - }, [descriptors]); - - // Load test on initial mount or when testId changes + // Load test YAML on initial mount or when testId changes useEffect(() => { - if (testId && testDetails && !descriptorsLoading && sourceTestId !== testId) { - loadTest(testDetails, descriptorMap); + if (testId && testYaml && sourceTestId !== testId) { + const success = loadFromYaml(testYaml.yaml); + if (success) { + setSourceTestId(testId); + } } else if (!testId && sourceTestId) { // If no testId in URL but we have a source, reset for new test reset(); } - }, [testId, testDetails, descriptorsLoading, descriptorMap, sourceTestId, loadTest, reset]); + }, [testId, testYaml, sourceTestId, loadFromYaml, setSourceTestId, reset]); - const isLoading = testLoading || (testId && descriptorsLoading); + const isLoading = yamlLoading; // Handle error loading test if (testError) { diff --git a/web-ui/src/pages/TestRun.tsx b/web-ui/src/pages/TestRun.tsx index 5f531d4a..47b5be2d 100644 --- a/web-ui/src/pages/TestRun.tsx +++ b/web-ui/src/pages/TestRun.tsx @@ -3,6 +3,7 @@ import { useParams, Link } from 'react-router-dom'; import { useQueryClient } from '@tanstack/react-query'; import { useTestRunDetails, useCancelTestRun, queryKeys } from '../hooks/useApi'; import { useEventStream } from '../hooks/useEventStream'; +import { useAuthContext } from '../context/AuthContext'; import StatusBadge from '../components/common/StatusBadge'; import SplitPane from '../components/common/SplitPane'; import TaskList from '../components/task/TaskList'; @@ -24,6 +25,7 @@ function TestRun() { const stored = localStorage.getItem(VIEW_MODE_STORAGE_KEY); return (stored === 'list' || stored === 'graph') ? stored : 'list'; }); + const { isLoggedIn } = useAuthContext(); const queryClient = useQueryClient(); const pendingTaskRefreshRef = useRef>(new Set()); const refreshTimerRef = useRef | null>(null); @@ -257,7 +259,7 @@ function TestRun() { ? Math.floor(Date.now() / 1000) - details.start_time : 0; - const canCancel = details.status === 'pending' || details.status === 'running'; + const canCancel = isLoggedIn && (details.status === 'pending' || details.status === 'running'); return (
diff --git a/web-ui/src/stores/builderStore.ts b/web-ui/src/stores/builderStore.ts index 994fdd66..5acb3a8e 100644 --- a/web-ui/src/stores/builderStore.ts +++ b/web-ui/src/stores/builderStore.ts @@ -138,6 +138,7 @@ export interface BuilderState { // Load/export loadTest: (testDetails: TestDetails, descriptors: Map) => void; loadFromYaml: (yaml: string) => boolean; + setSourceTestId: (testId: string | null) => void; reset: () => void; exportYaml: () => string; @@ -822,6 +823,9 @@ export const useBuilderStore = create((set, get) => ({ } }, + // Set source test ID (for tracking when editing existing tests) + setSourceTestId: (testId) => set({ sourceTestId: testId }), + // Reset to empty state reset: () => set({ testConfig: createEmptyTestConfig(), diff --git a/web-ui/src/types/api.ts b/web-ui/src/types/api.ts index 31e36bce..13e9a719 100644 --- a/web-ui/src/types/api.ts +++ b/web-ui/src/types/api.ts @@ -80,6 +80,12 @@ export interface TestDetails { vars?: Record; // Config with global vars merged in (only for authenticated users) } +// Test YAML source (from /api/v1/test/{testId}/yaml) +export interface TestYamlResponse { + yaml: string; + source: string; +} + // Test schedule configuration export interface TestSchedule { startup: boolean; From 751a5253da302dfeba84c2f9a9ad3539d4e6c587 Mon Sep 17 00:00:00 2001 From: pk910 Date: Tue, 3 Feb 2026 03:50:56 +0100 Subject: [PATCH 14/32] remove spamoor replacement --- go.mod | 4 +--- go.sum | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4ff5a83c..a26eef59 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 github.com/ethereum/go-ethereum v1.16.8 github.com/ethpandaops/ethwallclock v0.4.0 - github.com/ethpandaops/spamoor v1.1.15 + github.com/ethpandaops/spamoor v1.1.16-0.20260202043555-04648b790c5a github.com/glebarez/go-sqlite v1.22.0 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/uuid v1.6.0 @@ -123,5 +123,3 @@ require ( modernc.org/memory v1.11.0 // indirect modernc.org/sqlite v1.38.2 // indirect ) - -replace github.com/ethpandaops/spamoor => ../spamoor diff --git a/go.sum b/go.sum index 42f08ce5..638af4e9 100644 --- a/go.sum +++ b/go.sum @@ -73,6 +73,8 @@ github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cn github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= github.com/ethpandaops/ethwallclock v0.4.0 h1:+sgnhf4pk6hLPukP076VxkiLloE4L0Yk1yat+ZyHh1g= github.com/ethpandaops/ethwallclock v0.4.0/go.mod h1:y0Cu+mhGLlem19vnAV2x0hpFS5KZ7oOi2SWYayv9l24= +github.com/ethpandaops/spamoor v1.1.16-0.20260202043555-04648b790c5a h1:z5RDsUKOdg2yLlNfLRRLdfrCJub7M3L9fQYnL8C+iQ0= +github.com/ethpandaops/spamoor v1.1.16-0.20260202043555-04648b790c5a/go.mod h1:LW2DX5PlJKGM1vTeCjbm7rczdqYXwPRN9xJHE6bnZNY= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= From 20ca8688892a3155657b90fd68e14e1a32d9e652 Mon Sep 17 00:00:00 2001 From: pk910 Date: Tue, 3 Feb 2026 03:59:21 +0100 Subject: [PATCH 15/32] fix golangci-lint issues --- .golangci.yml | 2 +- pkg/assertoor/config.go | 2 +- pkg/tasks/generate_transaction/task.go | 69 ++++++++++++++------------ pkg/tasks/get_wallet_details/task.go | 6 ++- pkg/tasks/run_tasks/task.go | 1 - pkg/txmgr/spamoor.go | 3 ++ pkg/web/api/post_tests_register_api.go | 19 +++---- 7 files changed, 55 insertions(+), 47 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index eac672bc..80302d08 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -66,7 +66,7 @@ linters: - linters: - revive text: "var-naming: avoid meaningless package names" - path: "pkg/coordinator/(types|web/types|web/utils|web/api)/" + path: "pkg/(types|web/types|web/api)/" formatters: enable: - gofmt diff --git a/pkg/assertoor/config.go b/pkg/assertoor/config.go index cdd3b20d..aa52ad42 100644 --- a/pkg/assertoor/config.go +++ b/pkg/assertoor/config.go @@ -46,7 +46,6 @@ type Config struct { ExternalTests []*types.ExternalTestConfig `yaml:"externalTests" json:"externalTests"` } -//nolint:revive // ignore type CoordinatorConfig struct { // Maximum number of tests executed concurrently MaxConcurrentTests uint64 `yaml:"maxConcurrentTests" json:"maxConcurrentTests"` @@ -97,6 +96,7 @@ func NewConfig(path string) (*Config, error) { return config, nil } +//nolint:gocyclo // validation logic is inherently complex func (c *Config) Validate() error { var errs []error diff --git a/pkg/tasks/generate_transaction/task.go b/pkg/tasks/generate_transaction/task.go index add88512..4d339f66 100644 --- a/pkg/tasks/generate_transaction/task.go +++ b/pkg/tasks/generate_transaction/task.go @@ -201,25 +201,25 @@ func (t *Task) Execute(ctx context.Context) error { } } - err = nil if len(clients) == 0 { - err = fmt.Errorf("no ready clients available") - } else { - walletMgr := t.ctx.Scheduler.GetServices().WalletManager() - for i := 0; i < len(clients); i++ { - client := clients[i%len(clients)] - - t.logger.WithFields(logrus.Fields{ - "client": client.GetName(), - }).Infof("sending tx: %v", tx.Hash().Hex()) - - err := walletMgr.GetTxPool().SendTransaction(ctx, t.wallet, tx, &spamoor.SendTransactionOptions{ - Client: walletMgr.GetClient(client), - ClientsStartOffset: i, - }) - if err == nil { - break - } + return fmt.Errorf("no ready clients available") + } + + walletMgr := t.ctx.Scheduler.GetServices().WalletManager() + + for i := 0; i < len(clients); i++ { + client := clients[i%len(clients)] + + t.logger.WithFields(logrus.Fields{ + "client": client.GetName(), + }).Infof("sending tx: %v", tx.Hash().Hex()) + + err = walletMgr.GetTxPool().SendTransaction(ctx, t.wallet, tx, &spamoor.SendTransactionOptions{ + Client: walletMgr.GetClient(client), + ClientsStartOffset: i, + }) + if err == nil { + break } } @@ -234,10 +234,12 @@ func (t *Task) Execute(ctx context.Context) error { t.ctx.Outputs.SetVar("transactionHash", tx.Hash().Hex()) if t.config.AwaitReceipt { - receipt, err := t.ctx.Scheduler.GetServices().WalletManager().GetTxPool().AwaitTransaction(ctx, t.wallet, tx) + var receipt *ethtypes.Receipt + + receipt, err = t.ctx.Scheduler.GetServices().WalletManager().GetTxPool().AwaitTransaction(ctx, t.wallet, tx) if err != nil { t.logger.Warnf("failed waiting for tx receipt: %v", err) - return fmt.Errorf("failed waiting for tx receipt: %v", err) + return fmt.Errorf("failed waiting for tx receipt: %w", err) } if receipt == nil { @@ -260,14 +262,15 @@ func (t *Task) Execute(ctx context.Context) error { t.ctx.Outputs.SetVar("contractAddress", receipt.ContractAddress.Hex()) - if receiptData, err := vars.GeneralizeData(receipt); err == nil { + receiptData, generalizeErr := vars.GeneralizeData(receipt) + if generalizeErr == nil { t.ctx.Outputs.SetVar("receipt", receiptData) if t.config.TransactionReceiptResultVar != "" { t.ctx.Vars.SetVar(t.config.TransactionReceiptResultVar, receiptData) } } else { - t.logger.Warnf("Failed setting `receipt` output: %v", err) + t.logger.Warnf("Failed setting `receipt` output: %v", generalizeErr) } if len(t.config.ExpectEvents) > 0 { @@ -306,7 +309,8 @@ func (t *Task) Execute(ctx context.Context) error { for _, authorizationWallet := range t.authorizationWallets { // resync nonces of authorization wallets (might be increased by more than one, so we need to resync) client := t.ctx.Scheduler.GetServices().WalletManager().GetReadyClient() - err := authorizationWallet.UpdateWallet(ctx, client, true) + + err = authorizationWallet.UpdateWallet(ctx, client, true) if err != nil { return fmt.Errorf("cannot update authorization wallet: %w", err) } @@ -317,6 +321,7 @@ func (t *Task) Execute(ctx context.Context) error { return nil } +//nolint:gocyclo // transaction generation has multiple branches for different tx types func (t *Task) generateTransaction() (*ethtypes.Transaction, error) { var toAddr *common.Address @@ -368,6 +373,7 @@ func (t *Task) generateTransaction() (*ethtypes.Transaction, error) { } else { txObj, err = t.wallet.BuildLegacyTx(txData) } + if err != nil { return nil, err } @@ -377,27 +383,25 @@ func (t *Task) generateTransaction() (*ethtypes.Transaction, error) { return nil, fmt.Errorf("contract deployment not supported with blob transactions") } - blobData := t.config.BlobData - if blobData == "" { - blobData = "identifier" - } - blobCount := t.config.BlobSidecars + blobRefs := make([][]string, blobCount) - for i := 0; i < int(blobCount); i++ { + + for i := uint64(0); i < blobCount; i++ { blobLabel := fmt.Sprintf("0x1611AA0000%08dFF%02dFF%04dFEED", t.ctx.Index, i, 0) if t.config.BlobData != "" { blobRefs[i] = []string{} + for _, blob := range strings.Split(t.config.BlobData, ",") { if blob == "label" { blob = blobLabel } + blobRefs[i] = append(blobRefs[i], blob) } - } else { - specialBlob := mrand.Intn(50) + specialBlob := mrand.Intn(50) //nolint:gosec // weak random is fine here switch specialBlob { case 0: // special blob commitment - all 0x0 blobRefs[i] = []string{"0x0"} @@ -432,6 +436,7 @@ func (t *Task) generateTransaction() (*ethtypes.Transaction, error) { } else { txObj, err = t.wallet.BuildBlobTx(blobTx) } + if err != nil { return nil, err } @@ -479,6 +484,7 @@ func (t *Task) generateTransaction() (*ethtypes.Transaction, error) { } else { txObj, err = t.wallet.BuildSetCodeTx(setCodeTx) } + if err != nil { return nil, err } @@ -501,6 +507,7 @@ func (t *Task) generateTransaction() (*ethtypes.Transaction, error) { } else { txObj, err = t.wallet.BuildDynamicFeeTx(txData) } + if err != nil { return nil, err } diff --git a/pkg/tasks/get_wallet_details/task.go b/pkg/tasks/get_wallet_details/task.go index 98c3d649..13a270e4 100644 --- a/pkg/tasks/get_wallet_details/task.go +++ b/pkg/tasks/get_wallet_details/task.go @@ -102,7 +102,7 @@ func (t *Task) LoadConfig() error { return nil } -func (t *Task) Execute(ctx context.Context) error { +func (t *Task) Execute(_ context.Context) error { var wal *spamoor.Wallet if t.config.PrivateKey != "" { @@ -116,8 +116,10 @@ func (t *Task) Execute(ctx context.Context) error { return fmt.Errorf("cannot initialize wallet: %w", err) } } else { - var err error address := common.HexToAddress(t.config.Address) + + var err error + wal, err = t.ctx.Scheduler.GetServices().WalletManager().GetWalletByAddress(t.ctx.Scheduler.GetTestRunCtx(), address) if err != nil { return fmt.Errorf("cannot initialize wallet: %w", err) diff --git a/pkg/tasks/run_tasks/task.go b/pkg/tasks/run_tasks/task.go index f3ab5a23..d217cc7f 100644 --- a/pkg/tasks/run_tasks/task.go +++ b/pkg/tasks/run_tasks/task.go @@ -104,7 +104,6 @@ func (t *Task) Execute(ctx context.Context) error { for i, task := range t.tasks { err := t.ctx.Scheduler.ExecuteTask(ctx, task, nil) - if err != nil { if t.config.ContinueOnFailure { t.logger.Warnf("child task #%v failed: %v", i+1, err) diff --git a/pkg/txmgr/spamoor.go b/pkg/txmgr/spamoor.go index 798cf837..f77ac2f7 100644 --- a/pkg/txmgr/spamoor.go +++ b/pkg/txmgr/spamoor.go @@ -104,8 +104,11 @@ func (s *Spamoor) forwardBlocks(ctx context.Context, blockSubscription *executio return case block := <-blockSubscription.Channel(): time.Sleep(1 * time.Second) // wait for block to be seen by all clients + seenBy := block.GetSeenBy() + spamoorClients := make([]*spamoor.Client, 0, len(seenBy)) + for _, client := range seenBy { spamoorClient := s.GetClient(client) if spamoorClient == nil { diff --git a/pkg/web/api/post_tests_register_api.go b/pkg/web/api/post_tests_register_api.go index 0838b825..8a688c1f 100644 --- a/pkg/web/api/post_tests_register_api.go +++ b/pkg/web/api/post_tests_register_api.go @@ -63,9 +63,8 @@ func (ah *APIHandler) PostTestsRegister(w http.ResponseWriter, r *http.Request) if r.Header.Get("Content-Type") == contentTypeYAML { decoder := yaml.NewDecoder(bytes.NewReader(rawBody)) - err := decoder.Decode(req) - if err != nil { - ah.sendErrorResponse(w, r.URL.String(), fmt.Sprintf("error decoding request body yaml: %v", err), http.StatusBadRequest) + if decodeErr := decoder.Decode(req); decodeErr != nil { + ah.sendErrorResponse(w, r.URL.String(), fmt.Sprintf("error decoding request body yaml: %v", decodeErr), http.StatusBadRequest) return } @@ -73,15 +72,14 @@ func (ah *APIHandler) PostTestsRegister(w http.ResponseWriter, r *http.Request) } else { decoder := json.NewDecoder(bytes.NewReader(rawBody)) - err := decoder.Decode(req) - if err != nil { - ah.sendErrorResponse(w, r.URL.String(), fmt.Sprintf("error decoding request body json: %v", err), http.StatusBadRequest) + if decodeErr := decoder.Decode(req); decodeErr != nil { + ah.sendErrorResponse(w, r.URL.String(), fmt.Sprintf("error decoding request body json: %v", decodeErr), http.StatusBadRequest) return } // Convert JSON to YAML for storage - yamlBytes, err := yaml.Marshal(req) - if err == nil { + yamlBytes, marshalErr := yaml.Marshal(req) + if marshalErr == nil { yamlSource = string(yamlBytes) } } @@ -113,9 +111,8 @@ func (ah *APIHandler) PostTestsRegister(w http.ResponseWriter, r *http.Request) } if req.Timeout != "" { - err := testConfig.Timeout.Unmarshal(req.Timeout) - if err != nil { - ah.sendErrorResponse(w, r.URL.String(), fmt.Sprintf("failed decoding timeout: %v", err), http.StatusBadRequest) + if unmarshalErr := testConfig.Timeout.Unmarshal(req.Timeout); unmarshalErr != nil { + ah.sendErrorResponse(w, r.URL.String(), fmt.Sprintf("failed decoding timeout: %v", unmarshalErr), http.StatusBadRequest) return } } From b3a574aced09191e4eac44680e67e244d5cc2438 Mon Sep 17 00:00:00 2001 From: pk910 Date: Tue, 3 Feb 2026 04:16:38 +0100 Subject: [PATCH 16/32] update check workflow --- .github/workflows/_shared-check.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_shared-check.yaml b/.github/workflows/_shared-check.yaml index 24c5af80..f302a481 100644 --- a/.github/workflows/_shared-check.yaml +++ b/.github/workflows/_shared-check.yaml @@ -62,7 +62,7 @@ jobs: - name: Check if all tasks have a README.md run: | - for taskdir in pkg/coordinator/tasks/*/; do + for taskdir in pkg/tasks/*/; do taskdir=${taskdir%*/} taskname=${taskdir##*/} From 1d3245b6b6c8fe76c3809292b826873d9d79dda0 Mon Sep 17 00:00:00 2001 From: pk910 Date: Thu, 12 Feb 2026 01:51:25 +0100 Subject: [PATCH 17/32] improve builder UX --- pkg/tasks/run_external_tasks/config.go | 2 +- pkg/tasks/run_shell/config.go | 4 +- pkg/tasks/schema.go | 116 +++++++++++- pkg/web/api/get_global_variables_api.go | 41 +++++ pkg/web/api/get_test_yaml_api.go | 20 ++- pkg/web/server.go | 1 + web-ui/src/api/client.ts | 6 + .../src/components/builder/BuilderLayout.tsx | 9 + .../components/builder/config/ConfigPanel.tsx | 114 ++++++++---- .../builder/config/TaskConfigForm.tsx | 160 +++++++++-------- .../config/fields/ExpressionMapField.tsx | 101 +++++++++++ .../builder/config/fields/ObjectField.tsx | 80 ++++++--- .../builder/config/fields/StringField.tsx | 123 +++++++++++-- .../builder/config/fields/TextEditorModal.tsx | 99 +++++++++++ .../builder/toolbar/BuilderToolbar.tsx | 165 ++++++++++++++---- .../components/builder/yaml/BuilderYaml.tsx | 18 +- web-ui/src/components/test/StartTestModal.tsx | 18 +- web-ui/src/hooks/useApi.ts | 10 ++ web-ui/src/hooks/useDarkMode.ts | 18 ++ web-ui/src/pages/Registry.tsx | 26 ++- web-ui/src/pages/TestBuilder.tsx | 11 +- web-ui/src/stores/builderStore.ts | 13 +- web-ui/src/types/api.ts | 5 + web-ui/src/utils/builder/yamlSerializer.ts | 11 +- 24 files changed, 928 insertions(+), 243 deletions(-) create mode 100644 pkg/web/api/get_global_variables_api.go create mode 100644 web-ui/src/components/builder/config/fields/ExpressionMapField.tsx create mode 100644 web-ui/src/components/builder/config/fields/TextEditorModal.tsx create mode 100644 web-ui/src/hooks/useDarkMode.ts diff --git a/pkg/tasks/run_external_tasks/config.go b/pkg/tasks/run_external_tasks/config.go index ed338529..2f7ded31 100644 --- a/pkg/tasks/run_external_tasks/config.go +++ b/pkg/tasks/run_external_tasks/config.go @@ -7,7 +7,7 @@ import ( type Config struct { TestFile string `yaml:"testFile" json:"testFile" require:"A" desc:"Path to the external test file to execute."` TestConfig map[string]any `yaml:"testConfig" json:"testConfig" desc:"Configuration values to pass to the external test."` - TestConfigVars map[string]string `yaml:"testConfigVars" json:"testConfigVars" desc:"Variable mappings for external test configuration."` + TestConfigVars map[string]string `yaml:"testConfigVars" json:"testConfigVars" format:"expressionMap" desc:"Variable mappings for external test configuration."` ExpectFailure bool `yaml:"expectFailure" json:"expectFailure" desc:"If true, expect the external test to fail (inverts success condition)."` IgnoreFailure bool `yaml:"ignoreFailure" json:"ignoreFailure" desc:"If true, ignore failures from the external test."` } diff --git a/pkg/tasks/run_shell/config.go b/pkg/tasks/run_shell/config.go index ade270fa..77e7b8c7 100644 --- a/pkg/tasks/run_shell/config.go +++ b/pkg/tasks/run_shell/config.go @@ -5,8 +5,8 @@ import "errors" type Config struct { Shell string `yaml:"shell" json:"shell" desc:"Shell interpreter to use (e.g., bash, sh, zsh)."` ShellArgs []string `yaml:"shellArgs" json:"shellArgs" desc:"Additional arguments to pass to the shell."` - EnvVars map[string]string `yaml:"envVars" json:"envVars" desc:"Environment variables to set for the shell command."` - Command string `yaml:"command" json:"command" require:"A" desc:"The shell command to execute."` + EnvVars map[string]string `yaml:"envVars" json:"envVars" format:"expressionMap" desc:"Environment variables to set for the shell command."` + Command string `yaml:"command" json:"command" require:"A" format:"shell" desc:"The shell command to execute."` } func DefaultConfig() Config { diff --git a/pkg/tasks/schema.go b/pkg/tasks/schema.go index deb70da1..8b32f80e 100644 --- a/pkg/tasks/schema.go +++ b/pkg/tasks/schema.go @@ -2,6 +2,7 @@ package tasks import ( "encoding/json" + "fmt" "reflect" "strings" @@ -69,11 +70,15 @@ func GetTaskDescriptorAPI(name string) *TaskDescriptorAPI { return apiDesc } +const schemaTypeString = "string" + // JSONSchema represents a JSON Schema object. type JSONSchema struct { Type string `json:"type,omitempty"` + Format string `json:"format,omitempty"` Description string `json:"description,omitempty"` Properties map[string]*JSONSchema `json:"properties,omitempty"` + PropertyOrder []string `json:"propertyOrder,omitempty"` Required []string `json:"required,omitempty"` Items *JSONSchema `json:"items,omitempty"` Default any `json:"default,omitempty"` @@ -87,16 +92,26 @@ type JSONSchema struct { } // GenerateJSONSchema generates a JSON Schema from a Go struct via reflection. +// The value v is used both for type information and for extracting default values. func GenerateJSONSchema(v any) (json.RawMessage, error) { - schema := generateSchemaFromType(reflect.TypeOf(v)) + schema := generateSchema(reflect.TypeOf(v), reflect.ValueOf(v)) return json.Marshal(schema) } -func generateSchemaFromType(t reflect.Type) *JSONSchema { +// invalidValue is a zero reflect.Value used when no default value is available. +var invalidValue reflect.Value + +func generateSchema(t reflect.Type, v reflect.Value) *JSONSchema { // Handle pointer types if t.Kind() == reflect.Pointer { t = t.Elem() + + if v.IsValid() && !v.IsNil() { + v = v.Elem() + } else { + v = invalidValue + } } schema := &JSONSchema{} @@ -105,6 +120,7 @@ func generateSchemaFromType(t reflect.Type) *JSONSchema { case reflect.Struct: schema.Type = "object" schema.Properties = make(map[string]*JSONSchema, t.NumField()) + schema.PropertyOrder = make([]string, 0, t.NumField()) for i := range t.NumField() { field := t.Field(i) @@ -127,8 +143,14 @@ func generateSchemaFromType(t reflect.Type) *JSONSchema { fieldName := getJSONFieldName(&field, jsonTag) + // Get the field value for defaults (if available) + var fieldValue reflect.Value + if v.IsValid() { + fieldValue = v.Field(i) + } + // Generate schema for field type - fieldSchema := generateSchemaFromType(field.Type) + fieldSchema := generateSchema(field.Type, fieldValue) // Add description from desc tag, falling back to yaml tag if descTag := field.Tag.Get("desc"); descTag != "" { @@ -145,19 +167,41 @@ func generateSchemaFromType(t reflect.Type) *JSONSchema { fieldSchema.RequireGroup = requireTag } + // Add format annotation from format tag + if formatTag := field.Tag.Get("format"); formatTag != "" { + fieldSchema.Format = formatTag + } + + // Extract default value from the config instance + if fieldValue.IsValid() { + if def := extractDefault(fieldValue); def != nil { + fieldSchema.Default = def + } + } + schema.Properties[fieldName] = fieldSchema + schema.PropertyOrder = append(schema.PropertyOrder, fieldName) + } + + // Structs with no exported fields (e.g. big.Int) serialize as + // simple values in JSON, so represent them as strings rather + // than empty objects. + if len(schema.Properties) == 0 { + schema.Type = schemaTypeString + schema.Properties = nil + schema.PropertyOrder = nil } case reflect.Slice, reflect.Array: schema.Type = "array" - schema.Items = generateSchemaFromType(t.Elem()) + schema.Items = generateSchema(t.Elem(), invalidValue) case reflect.Map: schema.Type = "object" - schema.AdditionalProperties = generateSchemaFromType(t.Elem()) + schema.AdditionalProperties = generateSchema(t.Elem(), invalidValue) case reflect.String: - schema.Type = "string" + schema.Type = schemaTypeString case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: schema.Type = "integer" @@ -176,7 +220,7 @@ func generateSchemaFromType(t reflect.Type) *JSONSchema { schema.Type = "" case reflect.Complex64, reflect.Complex128: - schema.Type = "string" + schema.Type = schemaTypeString case reflect.Chan, reflect.Func, reflect.UnsafePointer, reflect.Invalid: // These types are not representable in JSON Schema @@ -184,12 +228,68 @@ func generateSchemaFromType(t reflect.Type) *JSONSchema { case reflect.Pointer: // Already handled above, but included for exhaustiveness - return generateSchemaFromType(t.Elem()) + return generateSchema(t.Elem(), invalidValue) } return schema } +// extractDefault extracts a JSON-serializable default value from a reflect.Value. +// Returns nil for zero values or types that should not have defaults. +func extractDefault(v reflect.Value) any { + if !v.IsValid() { + return nil + } + + // Dereference pointers + if v.Kind() == reflect.Pointer { + if v.IsNil() { + return nil + } + + v = v.Elem() + } + + // Skip zero values (they represent "no default") + if v.IsZero() { + return nil + } + + switch v.Kind() { + case reflect.String: + return v.String() + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() + + case reflect.Float32, reflect.Float64: + return v.Float() + + case reflect.Bool: + return v.Bool() + + case reflect.Struct: + // For structs with no exported fields (like big.Int), use + // fmt.Stringer if available to get a string representation. + iface := v.Interface() + if s, ok := iface.(fmt.Stringer); ok { + return s.String() + } + + return nil + + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Array, + reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, + reflect.Pointer, reflect.Slice, reflect.UnsafePointer: + return nil + } + + return nil +} + func getJSONFieldName(field *reflect.StructField, jsonTag string) string { if jsonTag != "" { parts := strings.Split(jsonTag, ",") diff --git a/pkg/web/api/get_global_variables_api.go b/pkg/web/api/get_global_variables_api.go new file mode 100644 index 00000000..3aefba93 --- /dev/null +++ b/pkg/web/api/get_global_variables_api.go @@ -0,0 +1,41 @@ +package api + +import ( + "net/http" +) + +// GetGlobalVariablesResponse contains the names of all configured global variables. +type GetGlobalVariablesResponse struct { + Names []string `json:"names"` +} + +// GetGlobalVariables godoc +// @Id getGlobalVariables +// @Summary Get global variable names +// @Tags Config +// @Description Returns the names of all configured global variables. Values are not included as they may contain sensitive data. +// @Produce json +// @Success 200 {object} Response{data=GetGlobalVariablesResponse} "Success" +// @Failure 401 {object} Response "Unauthorized" +// @Router /api/v1/global_variables [get] +func (ah *APIHandler) GetGlobalVariables(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", contentTypeJSON) + + // Require authentication - variable names can hint at infrastructure details + if !ah.checkAuth(r) { + ah.sendUnauthorizedResponse(w, r.URL.String()) + return + } + + globalVars := ah.coordinator.GlobalVariables() + varsMap := globalVars.GetVarsMap(nil, true) + + names := make([]string, 0, len(varsMap)) + for name := range varsMap { + names = append(names, name) + } + + ah.sendOKResponse(w, r.URL.String(), &GetGlobalVariablesResponse{ + Names: names, + }) +} diff --git a/pkg/web/api/get_test_yaml_api.go b/pkg/web/api/get_test_yaml_api.go index 211ce057..069fa6fc 100644 --- a/pkg/web/api/get_test_yaml_api.go +++ b/pkg/web/api/get_test_yaml_api.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net/http" + "os" "strings" "time" @@ -92,11 +93,11 @@ func (ah *APIHandler) GetTestYaml(w http.ResponseWriter, r *http.Request) { // loadExternalYaml loads YAML content from a file path or URL. func (ah *APIHandler) loadExternalYaml(ctx context.Context, source string) (string, error) { - // Check if source is a URL - if strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://") { - // Handle "external:" prefix if present - cleanSource := strings.TrimPrefix(source, "external:") + // Strip "external:" prefix if present (added by test registry) + cleanSource := strings.TrimPrefix(source, "external:") + // Check if source is a URL + if strings.HasPrefix(cleanSource, "http://") || strings.HasPrefix(cleanSource, "https://") { client := &http.Client{Timeout: time.Second * 120} req, err := http.NewRequestWithContext(ctx, "GET", cleanSource, http.NoBody) @@ -127,10 +128,11 @@ func (ah *APIHandler) loadExternalYaml(ctx context.Context, source string) (stri return string(body), nil } - // It's a local file path - strip "external:" prefix if present - filePath := strings.TrimPrefix(source, "external:") + // It's a local file path - read the file (this endpoint is auth-protected) + body, err := os.ReadFile(cleanSource) + if err != nil { + return "", fmt.Errorf("cannot load local file %s: %w", cleanSource, err) + } - // For security, we don't allow reading arbitrary files - // Only return error indicating the test needs to be loaded from file - return "", fmt.Errorf("cannot load local file %s via API - test must be re-registered with YAML source", filePath) + return string(body), nil } diff --git a/pkg/web/server.go b/pkg/web/server.go index 9281580a..4a01a4eb 100644 --- a/pkg/web/server.go +++ b/pkg/web/server.go @@ -102,6 +102,7 @@ func (ws *Server) ConfigureRoutes(frontendConfig *types.FrontendConfig, apiConfi ws.router.HandleFunc("/api/v1/task_descriptors", apiHandler.GetTaskDescriptors).Methods("GET") ws.router.HandleFunc("/api/v1/task_descriptor/{name}", apiHandler.GetTaskDescriptor).Methods("GET") ws.router.HandleFunc("/api/v1/clients", apiHandler.GetClients).Methods("GET") + ws.router.HandleFunc("/api/v1/global_variables", apiHandler.GetGlobalVariables).Methods("GET") // SSE event stream endpoints if eventBus != nil { diff --git a/web-ui/src/api/client.ts b/web-ui/src/api/client.ts index d851ba5b..731519c8 100644 --- a/web-ui/src/api/client.ts +++ b/web-ui/src/api/client.ts @@ -8,6 +8,7 @@ import type { TaskDetails, TaskDescriptor, ClientsPage, + GlobalVariablesResponse, ScheduleTestRunRequest, } from '../types/api'; import { authStore } from '../stores/authStore'; @@ -115,6 +116,11 @@ export async function getTaskDescriptor(name: string): Promise { return fetchApi(`/task_descriptor/${encodeURIComponent(name)}`); } +// Global variable names (auth required) +export async function getGlobalVariables(): Promise { + return fetchApiWithAuth('/global_variables'); +} + // Admin operations (require authentication) export async function scheduleTestRun(request: ScheduleTestRunRequest): Promise<{ run_id: number }> { return fetchApiWithAuth<{ run_id: number }>('/test_runs/schedule', { diff --git a/web-ui/src/components/builder/BuilderLayout.tsx b/web-ui/src/components/builder/BuilderLayout.tsx index e7fbeec8..14bc86eb 100644 --- a/web-ui/src/components/builder/BuilderLayout.tsx +++ b/web-ui/src/components/builder/BuilderLayout.tsx @@ -543,6 +543,14 @@ function BuilderLayout({ isLoading }: BuilderLayoutProps) { // DnD context value const dndContext: BuilderDndContext = { activeId, overId }; + // Handle opening the test settings panel (from toolbar ID click) + const handleOpenTestSettings = useCallback(() => { + setSelection(['__test_header__'], '__test_header__'); + if (!showConfig) { + setShowConfig(true); + } + }, [setSelection, showConfig]); + // Check if we should show config panel const showConfigPanel = showConfig && selection.primaryTaskId && selection.taskIds.size === 1; @@ -571,6 +579,7 @@ function BuilderLayout({ isLoading }: BuilderLayoutProps) { onTogglePalette={() => setShowPalette(!showPalette)} onToggleConfig={() => setShowConfig(!showConfig)} onToggleAI={() => setShowAI(!showAI)} + onOpenTestSettings={handleOpenTestSettings} /> {/* Main content area */} diff --git a/web-ui/src/components/builder/config/ConfigPanel.tsx b/web-ui/src/components/builder/config/ConfigPanel.tsx index 9209f919..18853410 100644 --- a/web-ui/src/components/builder/config/ConfigPanel.tsx +++ b/web-ui/src/components/builder/config/ConfigPanel.tsx @@ -1,6 +1,7 @@ import { useMemo, useCallback, useState } from 'react'; import { useBuilderStore } from '../../../stores/builderStore'; -import { useTaskDescriptors } from '../../../hooks/useApi'; +import { useTaskDescriptors, useGlobalVariables } from '../../../hooks/useApi'; +import { useAuthContext } from '../../../context/AuthContext'; import { findTaskById, canHaveChildren } from '../../../utils/builder/taskUtils'; import TaskConfigForm from './TaskConfigForm'; import type { TaskDescriptor } from '../../../types/api'; @@ -25,6 +26,8 @@ function TestHeaderConfig() { const setTestTimeout = useBuilderStore((state) => state.setTestTimeout); const setTestVars = useBuilderStore((state) => state.setTestVars); const setTestId = useBuilderStore((state) => state.setTestId); + const { isLoggedIn } = useAuthContext(); + const { data: globalVarsData } = useGlobalVariables({ enabled: isLoggedIn }); const [newVarKey, setNewVarKey] = useState(''); const [newVarValue, setNewVarValue] = useState(''); @@ -50,6 +53,22 @@ function TestHeaderConfig() { setNewVarValue(''); }, [newVarKey, newVarValue, testConfig.testVars, setTestVars]); + // Handle adding a global variable suggestion (pass-through with empty default) + const handleAddGlobalVar = useCallback((name: string) => { + const newVars = { + ...(testConfig.testVars || {}), + [name]: '', + }; + setTestVars(newVars); + }, [testConfig.testVars, setTestVars]); + + // Global variable names that haven't been added yet + const availableGlobalVars = useMemo(() => { + if (!globalVarsData?.names) return []; + const existing = new Set(Object.keys(testConfig.testVars || {})); + return globalVarsData.names.filter((name) => !existing.has(name)).sort(); + }, [globalVarsData, testConfig.testVars]); + // Handle removing a variable const handleRemoveVar = useCallback((key: string) => { const newVars = { ...(testConfig.testVars || {}) }; @@ -97,11 +116,11 @@ function TestHeaderConfig() { {/* Test ID */}
setTestId(e.target.value)} placeholder="e.g., my-test-id" className="w-full px-2 py-1.5 text-sm bg-[var(--color-bg-primary)] border border-[var(--color-border)] rounded focus:outline-hidden focus:ring-2 focus:ring-primary-500 focus:border-transparent" @@ -157,55 +176,76 @@ function TestHeaderConfig() { {testConfig.testVars && Object.keys(testConfig.testVars).length > 0 && (
{Object.entries(testConfig.testVars).map(([key, value]) => ( -
-
-
{key}
- handleUpdateVar(key, e.target.value)} - className="w-full mt-1 px-2 py-1 text-xs font-mono bg-[var(--color-bg-primary)] border border-[var(--color-border)] rounded focus:outline-hidden focus:ring-1 focus:ring-primary-500" - /> +
+
+
{key}
+
+ handleUpdateVar(key, e.target.value)} + className="w-full px-2 py-1 text-xs font-mono bg-[var(--color-bg-primary)] border border-[var(--color-border)] rounded-xs focus:outline-hidden focus:ring-1 focus:ring-primary-500" + /> +
+ ))} +
+ )} + + {/* Global variable suggestions */} + {availableGlobalVars.length > 0 && ( +
+
Global Variables
+
+ {availableGlobalVars.map((name) => ( -
- ))} + ))} +
)} {/* Add new variable */} -
-
+
+
setNewVarKey(e.target.value)} placeholder="Variable name" - className="flex-1 px-2 py-1 text-xs bg-[var(--color-bg-primary)] border border-[var(--color-border)] rounded focus:outline-hidden focus:ring-1 focus:ring-primary-500" - /> - setNewVarValue(e.target.value)} - placeholder="Value" - className="flex-1 px-2 py-1 text-xs font-mono bg-[var(--color-bg-primary)] border border-[var(--color-border)] rounded focus:outline-hidden focus:ring-1 focus:ring-primary-500" - onKeyDown={(e) => e.key === 'Enter' && handleAddVar()} + className="w-full px-2 py-1 text-xs bg-[var(--color-bg-primary)] border border-[var(--color-border)] rounded-xs focus:outline-hidden focus:ring-1 focus:ring-primary-500" /> - +
+ setNewVarValue(e.target.value)} + placeholder="Value (optional)" + className="flex-1 px-2 py-1 text-xs font-mono bg-[var(--color-bg-primary)] border border-[var(--color-border)] rounded-xs focus:outline-hidden focus:ring-1 focus:ring-primary-500" + onKeyDown={(e) => e.key === 'Enter' && handleAddVar()} + /> + +
-

+

Values can be JSON (objects, arrays, numbers) or plain strings

diff --git a/web-ui/src/components/builder/config/TaskConfigForm.tsx b/web-ui/src/components/builder/config/TaskConfigForm.tsx index e422a874..80ad4a69 100644 --- a/web-ui/src/components/builder/config/TaskConfigForm.tsx +++ b/web-ui/src/components/builder/config/TaskConfigForm.tsx @@ -4,6 +4,7 @@ import NumberField from './fields/NumberField'; import BooleanField from './fields/BooleanField'; import ArrayField from './fields/ArrayField'; import ObjectField from './fields/ObjectField'; +import ExpressionMapField from './fields/ExpressionMapField'; import DurationField from './fields/DurationField'; import VariableSelector from './VariableSelector'; import ExpressionInput from './ExpressionInput'; @@ -11,6 +12,7 @@ import ExpressionInput from './ExpressionInput'; interface JSONSchema { type?: string | string[]; properties?: Record; + propertyOrder?: string[]; required?: string[]; items?: JSONSchema; default?: unknown; @@ -24,6 +26,7 @@ interface JSONSchema { maxLength?: number; pattern?: string; additionalProperties?: boolean | JSONSchema; + requireGroup?: string; } interface TaskConfigFormProps { @@ -36,7 +39,7 @@ interface TaskConfigFormProps { } // String format types for different rendering -export type StringFormat = 'text' | 'address' | 'hash' | 'bigint' | 'yaml' | 'multiline'; +export type StringFormat = 'text' | 'address' | 'hash' | 'bigint' | 'yaml' | 'multiline' | 'shell'; // Parse the schema to determine field types function getFieldType(schema: JSONSchema): string { @@ -44,6 +47,7 @@ function getFieldType(schema: JSONSchema): string { if (schema.enum) return 'enum'; if (schema.format === 'duration') return 'duration'; + if (schema.format === 'expressionMap') return 'expressionMap'; switch (type) { case 'string': @@ -70,59 +74,32 @@ function isDurationField(name: string, schema: JSONSchema): boolean { return durationNames.some((d) => name.toLowerCase().includes(d)); } -// Determine string format based on schema format or field name patterns -function getStringFormat(name: string, schema: JSONSchema): StringFormat { - // Explicit format annotations - if (schema.format) { - switch (schema.format.toLowerCase()) { - case 'address': - case 'eth-address': - return 'address'; - case 'hash': - case 'bytes32': - case 'hex': - return 'hash'; - case 'bigint': - case 'uint256': - case 'int256': - return 'bigint'; - case 'yaml': - case 'json': - case 'multiline': - return 'multiline'; - } - } - - // Infer from field name patterns - const lowerName = name.toLowerCase(); - - // Address patterns - if (lowerName.includes('address') || lowerName.includes('recipient') || lowerName.includes('sender')) { - return 'address'; - } - - // Hash/key patterns - if (lowerName.includes('hash') || lowerName.includes('pubkey') || lowerName.includes('privatekey') || - lowerName.includes('secret') || lowerName.includes('signature') || lowerName.includes('root') || - lowerName.includes('blockroot') || lowerName.includes('stateroot')) { - return 'hash'; - } +// Determine string format based on explicit schema format annotations only. +function getStringFormat(_name: string, schema: JSONSchema): StringFormat { + if (!schema.format) return 'text'; - // Big integer patterns (values that exceed JS number precision) - if (lowerName.includes('wei') || lowerName.includes('gwei') || lowerName.includes('amount') || - lowerName.includes('balance') || lowerName.includes('value') || lowerName.includes('gasPrice') || - lowerName.includes('gaslimit') || lowerName.includes('maxfee') || lowerName.includes('tip')) { - return 'bigint'; - } - - // Multiline patterns - if (lowerName.includes('script') || lowerName.includes('yaml') || lowerName.includes('json') || - lowerName.includes('config') || lowerName.includes('template') || lowerName.includes('body') || - lowerName.includes('payload') || lowerName.includes('data') || lowerName.includes('calldata')) { - return 'multiline'; + switch (schema.format.toLowerCase()) { + case 'address': + case 'eth-address': + return 'address'; + case 'hash': + case 'bytes32': + case 'hex': + return 'hash'; + case 'bigint': + case 'uint256': + case 'int256': + return 'bigint'; + case 'yaml': + case 'json': + case 'multiline': + return 'multiline'; + case 'shell': + case 'script': + return 'shell'; + default: + return 'text'; } - - return 'text'; } function TaskConfigForm({ @@ -133,25 +110,31 @@ function TaskConfigForm({ onConfigVarChange, taskId, }: TaskConfigFormProps) { - // Parse schema properties + // Parse schema properties, preserving struct field order via propertyOrder const properties = useMemo(() => { const parsed = schema as JSONSchema; if (!parsed.properties) return []; const required = new Set(parsed.required || []); - return Object.entries(parsed.properties).map(([name, propSchema]) => { - const ps = propSchema as JSONSchema; - const fieldType = getFieldType(ps); - return { - name, - schema: ps, - required: required.has(name), - fieldType, - isDuration: isDurationField(name, ps), - stringFormat: fieldType === 'string' ? getStringFormat(name, ps) : undefined, - }; - }); + // Use propertyOrder to maintain Go struct field order, fall back to Object.keys + const orderedNames = parsed.propertyOrder || Object.keys(parsed.properties); + + return orderedNames + .filter((name) => name in parsed.properties!) + .map((name) => { + const ps = parsed.properties![name] as JSONSchema; + const fieldType = getFieldType(ps); + return { + name, + schema: ps, + required: required.has(name), + requireGroup: ps.requireGroup, + fieldType, + isDuration: isDurationField(name, ps), + stringFormat: fieldType === 'string' ? getStringFormat(name, ps) : undefined, + }; + }); }, [schema]); if (properties.length === 0) { @@ -173,10 +156,14 @@ function TaskConfigForm({
{/* Field label */}
- +
+ + {prop.requireGroup && ( + + )} +
onConfigChange(prop.name, v)} stringFormat={prop.stringFormat} + taskId={taskId} /> )}
@@ -215,15 +203,37 @@ function TaskConfigForm({ ); } +// Badge colors for requirement groups (A, B, C, ...) +const groupColors: Record = { + A: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400', + B: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400', + C: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400', + D: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400', +}; + +function RequireGroupBadge({ group }: { group: string }) { + // Parse "A" or "A.1" format + const groupLetter = group.split('.')[0]; + const subGroup = group.includes('.') ? group.split('.')[1] : null; + const colorClass = groupColors[groupLetter] || 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-400'; + + return ( + + {subGroup ? `required ${groupLetter}.${subGroup}` : 'required'} + + ); +} + interface FieldRendererProps { fieldType: string; schema: JSONSchema; value: unknown; onChange: (value: unknown) => void; stringFormat?: StringFormat; + taskId: string; } -function FieldRenderer({ fieldType, schema, value, onChange, stringFormat }: FieldRendererProps) { +function FieldRenderer({ fieldType, schema, value, onChange, stringFormat, taskId }: FieldRendererProps) { switch (fieldType) { case 'boolean': return ( @@ -279,6 +289,15 @@ function FieldRenderer({ fieldType, schema, value, onChange, stringFormat }: Fie /> ); + case 'expressionMap': + return ( + | undefined} + taskId={taskId} + onChange={onChange} + /> + ); + case 'object': return ( | undefined; + taskId: string; + onChange: (value: unknown) => void; +} + +function ExpressionMapField({ value, taskId, onChange }: ExpressionMapFieldProps) { + const [newKey, setNewKey] = useState(''); + const entries = Object.entries(value || {}); + + const handleValueChange = useCallback((key: string, newVal: string) => { + const updated = { ...(value || {}), [key]: newVal }; + onChange(updated); + }, [value, onChange]); + + const handleRemove = useCallback((key: string) => { + const updated = { ...(value || {}) }; + delete updated[key]; + onChange(Object.keys(updated).length > 0 ? updated : undefined); + }, [value, onChange]); + + const handleAdd = useCallback(() => { + const trimmed = newKey.trim(); + if (!trimmed) return; + if (value && trimmed in value) return; + + const updated = { ...(value || {}), [trimmed]: '' }; + onChange(updated); + setNewKey(''); + }, [newKey, value, onChange]); + + const handleAddKeyDown = useCallback((e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault(); + handleAdd(); + } + }, [handleAdd]); + + return ( +
+ {entries.map(([key, val]) => ( +
+
+ + {key} + +
+
+ handleValueChange(key, v)} + placeholder="Expression for value" + /> +
+ +
+ ))} + + {/* Add new entry */} +
+ setNewKey(e.target.value)} + onKeyDown={handleAddKeyDown} + placeholder="Variable name" + className="w-28 px-2 py-1.5 text-xs font-mono bg-[var(--color-bg-primary)] border border-[var(--color-border)] rounded-sm focus:outline-hidden focus:ring-2 focus:ring-primary-500 focus:border-transparent" + /> + +
+
+ ); +} + +function RemoveIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +export default ExpressionMapField; diff --git a/web-ui/src/components/builder/config/fields/ObjectField.tsx b/web-ui/src/components/builder/config/fields/ObjectField.tsx index fb6ae89a..f48e02e4 100644 --- a/web-ui/src/components/builder/config/fields/ObjectField.tsx +++ b/web-ui/src/components/builder/config/fields/ObjectField.tsx @@ -1,5 +1,6 @@ -import { useState, useCallback } from 'react'; +import { useState, useCallback, useMemo } from 'react'; import yaml from 'js-yaml'; +import TextEditorModal from './TextEditorModal'; interface ObjectFieldProps { value: Record | undefined; @@ -11,15 +12,23 @@ function ObjectField({ value, onChange, }: ObjectFieldProps) { - // For complex objects, use YAML editing - const [yamlValue, setYamlValue] = useState(() => { + const [isEditorOpen, setIsEditorOpen] = useState(false); + const [error, setError] = useState(null); + + const yamlValue = useMemo(() => { if (!value || Object.keys(value).length === 0) return ''; return yaml.dump(value, { indent: 2, lineWidth: -1 }); - }); - const [error, setError] = useState(null); + }, [value]); + + const [localYaml, setLocalYaml] = useState(yamlValue); - const handleChange = useCallback((newYaml: string) => { - setYamlValue(newYaml); + // Keep local yaml in sync when value changes externally + useMemo(() => { + setLocalYaml(yamlValue); + }, [yamlValue]); + + const handleInlineChange = useCallback((newYaml: string) => { + setLocalYaml(newYaml); if (!newYaml.trim()) { setError(null); @@ -40,27 +49,56 @@ function ObjectField({ } }, [onChange]); + const handleModalSave = useCallback((newYaml: string) => { + handleInlineChange(newYaml); + }, [handleInlineChange]); + return (
-