From 6a8dbeb2e955566a6c3d6ba11ac60034a6d1117c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=EB=8C=80=EC=9D=BC?= Date: Wed, 10 Jun 2026 15:51:34 +0900 Subject: [PATCH] =?UTF-8?q?=EC=B2=AB=20=EC=BB=A4=EB=B0=8B:=20=EB=A1=9C?= =?UTF-8?q?=EC=BB=AC=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- baron-sso/.dockerignore | 19 + baron-sso/.env.sample | 186 + baron-sso/.gitea/coverage.json | 7 + .../workflows/backend_coverage_check.yml | 29 + baron-sso/.gitea/workflows/build_RC.yml | 165 + baron-sso/.gitea/workflows/code_check.yml | 1874 ++++ .../.gitea/workflows/production_release.yml | 172 + .../.gitea/workflows/staging_build_check.yml | 83 + .../.gitea/workflows/staging_code_pull.yml | 256 + .../.gitea/workflows/staging_release.yml | 238 + .../workflows/userfront_e2e_full_nightly.yml | 273 + baron-sso/.gitignore | 61 + baron-sso/Makefile | 484 + baron-sso/README.md | 875 ++ baron-sso/README_en.md | 110 + baron-sso/adminfront/.gitignore | 24 + baron-sso/adminfront/Dockerfile | 40 + baron-sso/adminfront/README.md | 29 + baron-sso/adminfront/biome.json | 7 + baron-sso/adminfront/index.html | 13 + baron-sso/adminfront/package-lock.json | 6081 +++++++++++ baron-sso/adminfront/package.json | 68 + baron-sso/adminfront/playwright.config.ts | 94 + baron-sso/adminfront/pnpm-lock.yaml | 3722 +++++++ baron-sso/adminfront/postcss.config.js | 6 + .../adminfront/public/LINE_WORKS_Appicon.svg | 7 + baron-sso/adminfront/public/vite.svg | 1 + baron-sso/adminfront/scripts/runtime-mode.sh | 144 + baron-sso/adminfront/scripts/serve-prod.mjs | 160 + baron-sso/adminfront/seed-tenant.csv | 13 + baron-sso/adminfront/src/app/queryClient.ts | 7 + baron-sso/adminfront/src/app/routes.test.tsx | 62 + baron-sso/adminfront/src/app/routes.tsx | 81 + baron-sso/adminfront/src/assets/react.svg | 1 + .../src/components/auth/RoleGuard.tsx | 43 + .../common/LanguageSelector.test.tsx | 32 + .../components/common/LanguageSelector.tsx | 69 + .../common/LocaleRefreshBoundary.test.tsx | 34 + .../common/LocaleRefreshBoundary.tsx | 27 + .../src/components/layout/AppLayout.test.tsx | 186 + .../src/components/layout/AppLayout.tsx | 837 ++ .../src/components/ui/avatar.test.tsx | 49 + .../adminfront/src/components/ui/avatar.tsx | 47 + .../src/components/ui/badge.test.tsx | 26 + .../adminfront/src/components/ui/badge.tsx | 21 + .../src/components/ui/button.test.tsx | 38 + .../adminfront/src/components/ui/button.tsx | 31 + .../src/components/ui/card.test.tsx | 35 + .../adminfront/src/components/ui/card.tsx | 58 + .../src/components/ui/checkbox.test.tsx | 19 + .../adminfront/src/components/ui/checkbox.tsx | 36 + .../components/ui/dialog.focus-scope.test.tsx | 23 + .../adminfront/src/components/ui/dialog.tsx | 287 + .../src/components/ui/dropdown-menu.tsx | 200 + .../src/components/ui/input.test.tsx | 42 + .../adminfront/src/components/ui/input.tsx | 27 + .../src/components/ui/label.test.tsx | 27 + .../adminfront/src/components/ui/label.tsx | 19 + .../src/components/ui/scroll-area.tsx | 44 + .../adminfront/src/components/ui/select.tsx | 158 + .../src/components/ui/separator.test.tsx | 41 + .../src/components/ui/separator.tsx | 16 + .../adminfront/src/components/ui/switch.tsx | 68 + .../adminfront/src/components/ui/table.tsx | 102 + .../adminfront/src/components/ui/tabs.tsx | 87 + .../src/components/ui/textarea.test.tsx | 19 + .../adminfront/src/components/ui/textarea.tsx | 28 + .../adminfront/src/components/ui/toaster.tsx | 35 + .../adminfront/src/components/ui/use-toast.ts | 66 + .../api-keys/ApiKeyCreatePage.test.tsx | 72 + .../features/api-keys/ApiKeyCreatePage.tsx | 350 + .../features/api-keys/ApiKeyListPage.test.tsx | 125 + .../src/features/api-keys/ApiKeyListPage.tsx | 467 + .../src/features/api-keys/apiKeyScopes.ts | 59 + .../src/features/audit/AuditLogsPage.tsx | 197 + .../audit/VirtualizedAuditLogTable.tsx | 475 + .../src/features/auth/AuthCallbackPage.tsx | 56 + .../src/features/auth/AuthGuard.test.tsx | 56 + .../src/features/auth/AuthGuard.tsx | 59 + .../src/features/auth/AuthPage.test.tsx | 38 + .../adminfront/src/features/auth/AuthPage.tsx | 24 + .../src/features/auth/LoginPage.test.tsx | 76 + .../src/features/auth/LoginPage.tsx | 206 + .../auth/components/PermissionChecker.tsx | 188 + .../features/coverage/adminAuditAuth.test.tsx | 192 + .../coverage/adminLargePages.test.tsx | 506 + .../coverage/adminTenantDetailPages.test.tsx | 129 + .../coverage/adminTenantGroupsPage.test.tsx | 116 + .../coverage/adminTenantTabs.test.tsx | 196 + .../integrity/DataIntegrityPage.test.tsx | 250 + .../features/integrity/DataIntegrityPage.tsx | 640 ++ .../overview/GlobalOverviewPage.test.tsx | 266 + .../features/overview/GlobalOverviewPage.tsx | 610 ++ .../projections/UserProjectionPage.test.tsx | 120 + .../projections/UserProjectionPage.tsx | 340 + .../components/DomainTagInput.test.tsx | 53 + .../tenants/components/DomainTagInput.tsx | 187 + .../ParentTenantSelector.helpers.ts | 21 + .../ParentTenantSelector.picker.test.tsx | 137 + .../components/ParentTenantSelector.test.ts | 57 + .../components/ParentTenantSelector.tsx | 257 + .../routes/TenantAdminsAndOwnersTab.tsx | 673 ++ .../tenants/routes/TenantCreatePage.tsx | 515 + .../tenants/routes/TenantDetailPage.tsx | 135 + .../TenantDetailPage.worksmobile.test.tsx | 51 + .../tenants/routes/TenantGroupsPage.tsx | 903 ++ .../tenants/routes/TenantListPage.test.ts | 100 + .../tenants/routes/TenantListPage.tsx | 2136 ++++ .../tenants/routes/TenantProfilePage.tsx | 525 + .../tenants/routes/TenantSchemaPage.test.ts | 35 + .../tenants/routes/TenantSchemaPage.tsx | 400 + .../tenants/routes/TenantSubTenantsPage.tsx | 128 + .../routes/TenantUsersPage.export.test.tsx | 148 + .../tenants/routes/TenantUsersPage.tsx | 475 + .../routes/TenantWorksmobilePage.test.ts | 627 ++ .../tenants/routes/TenantWorksmobilePage.tsx | 1870 ++++ .../features/tenants/routes/tenantListView.ts | 142 + .../tenants/routes/tenantSchemaFields.ts | 74 + .../tenants/routes/worksmobileAccess.test.ts | 57 + .../tenants/routes/worksmobileAccess.ts | 61 + .../tenants/routes/worksmobileComparison.ts | 462 + .../features/tenants/utils/domainTags.test.ts | 67 + .../src/features/tenants/utils/domainTags.ts | 62 + .../features/tenants/utils/orgConfig.test.ts | 126 + .../src/features/tenants/utils/orgConfig.ts | 118 + .../tenants/utils/protectedTenants.test.ts | 12 + .../tenants/utils/protectedTenants.ts | 19 + .../tenants/utils/tenantCsvImport.test.ts | 383 + .../features/tenants/utils/tenantCsvImport.ts | 679 ++ .../routes/GlobalUserGroupListPage.tsx | 149 + .../routes/TenantUserGroupsTab.tsx | 995 ++ .../routes/UserGroupDetailPage.tsx | 628 ++ .../features/users/GlobalCustomClaimsPage.tsx | 323 + .../src/features/users/UserCreatePage.tsx | 1196 +++ .../UserDetailPage.employeeNumber.test.tsx | 211 + .../src/features/users/UserDetailPage.tsx | 2397 +++++ .../users/UserListPage.render.test.tsx | 236 + .../src/features/users/UserListPage.tsx | 1318 +++ .../components/UserBulkMoveGroupModal.tsx | 334 + .../users/components/UserBulkUploadModal.tsx | 815 ++ .../src/features/users/orgChartPicker.test.ts | 319 + .../src/features/users/orgChartPicker.ts | 362 + .../src/features/users/userSchemaFields.ts | 18 + .../src/features/users/userStatus.test.ts | 43 + .../src/features/users/userStatus.ts | 45 + .../features/users/utils/csvParser.test.ts | 190 + .../src/features/users/utils/csvParser.ts | 462 + .../generalPlanningOfficePriority.test.ts | 160 + .../utils/generalPlanningOfficePriority.ts | 184 + .../users/utils/hanmacImportEmail.test.ts | 73 + .../features/users/utils/hanmacImportEmail.ts | 296 + .../users/utils/personalTenant.test.ts | 37 + .../features/users/utils/personalTenant.ts | 34 + baron-sso/adminfront/src/index.css | 46 + .../src/lib/adminApi.contract.test.ts | 229 + baron-sso/adminfront/src/lib/adminApi.test.ts | 77 + baron-sso/adminfront/src/lib/adminApi.ts | 1315 +++ baron-sso/adminfront/src/lib/apiClient.ts | 73 + baron-sso/adminfront/src/lib/auth.ts | 51 + .../adminfront/src/lib/authConfig.test.ts | 63 + baron-sso/adminfront/src/lib/authConfig.ts | 88 + .../adminfront/src/lib/cursorFetch.test.ts | 73 + baron-sso/adminfront/src/lib/debugLog.ts | 10 + baron-sso/adminfront/src/lib/i18n.test.ts | 35 + baron-sso/adminfront/src/lib/i18n.ts | 21 + baron-sso/adminfront/src/lib/locale.ts | 36 + .../src/lib/loginRedirectGuard.test.ts | 37 + baron-sso/adminfront/src/lib/roles.test.ts | 76 + baron-sso/adminfront/src/lib/roles.ts | 104 + .../adminfront/src/lib/sessionSliding.test.ts | 179 + .../adminfront/src/lib/sessionSliding.ts | 9 + baron-sso/adminfront/src/lib/sort.test.ts | 72 + .../adminfront/src/lib/tenantTree.test.ts | 135 + baron-sso/adminfront/src/lib/tenantTree.ts | 93 + baron-sso/adminfront/src/lib/utils.test.ts | 38 + baron-sso/adminfront/src/lib/utils.ts | 27 + baron-sso/adminfront/src/locales/en.toml | 2002 ++++ baron-sso/adminfront/src/locales/ko.toml | 2002 ++++ .../adminfront/src/locales/template.toml | 1961 ++++ baron-sso/adminfront/src/main.tsx | 30 + .../src/test/formFieldDiagnostics.test.ts | 43 + .../src/test/formFieldDiagnostics.ts | 27 + baron-sso/adminfront/src/test/i18nMock.ts | 207 + baron-sso/adminfront/src/test/setup.ts | 8 + baron-sso/adminfront/tailwind.config.ts | 14 + baron-sso/adminfront/tests/audit.spec.ts | 234 + baron-sso/adminfront/tests/auth.spec.ts | 144 + .../adminfront/tests/bulk_actions.spec.ts | 457 + .../adminfront/tests/data_integrity.spec.ts | 235 + baron-sso/adminfront/tests/owners.spec.ts | 131 + .../adminfront/tests/security_roles.spec.ts | 294 + .../adminfront/tests/shell_layout.spec.ts | 80 + .../adminfront/tests/tenant_domains.spec.ts | 158 + .../adminfront/tests/tenant_schema.spec.ts | 124 + .../tests/tenant_seed_protection.spec.ts | 113 + baron-sso/adminfront/tests/tenants.spec.ts | 1578 +++ .../adminfront/tests/tenants_live.spec.ts | 120 + baron-sso/adminfront/tests/users.spec.ts | 1229 +++ baron-sso/adminfront/tests/users_bulk.spec.ts | 459 + .../tests/users_bulk_secondary.spec.ts | 124 + .../adminfront/tests/users_bulk_uuid.spec.ts | 139 + baron-sso/adminfront/tests/users_live.spec.ts | 85 + .../adminfront/tests/users_schema.spec.ts | 233 + .../adminfront/tests/worksmobile.spec.ts | 1138 +++ baron-sso/adminfront/tsconfig.app.json | 37 + baron-sso/adminfront/tsconfig.json | 7 + baron-sso/adminfront/tsconfig.node.json | 26 + baron-sso/adminfront/vite.config.ts | 43 + baron-sso/adminfront/vitest.config.ts | 53 + baron-sso/backend/Dockerfile | 21 + baron-sso/backend/check_aaa2.go | 36 + baron-sso/backend/cmd/adminctl/main.go | 236 + baron-sso/backend/cmd/adminctl/main_test.go | 140 + .../backend/cmd/adminctl/worksmobile_sync.go | 1709 ++++ .../cmd/adminctl/worksmobile_sync_test.go | 38 + baron-sso/backend/cmd/fix_kratos_roles.go | 70 + baron-sso/backend/cmd/keto_test/main.go | 42 + baron-sso/backend/cmd/keygen/main.go | 90 + baron-sso/backend/cmd/server/error_handler.go | 33 + .../backend/cmd/server/error_handler_test.go | 139 + .../cmd/server/headless_login_e2e_test.go | 554 + .../backend/cmd/server/health_monitor.go | 132 + baron-sso/backend/cmd/server/main.go | 920 ++ .../backend/cmd/server/openapi_static_test.go | 41 + .../cmd/server/worksmobile_config_test.go | 51 + baron-sso/backend/docs/openapi.yaml | 2044 ++++ baron-sso/backend/docs/redoc/index.html | 37 + .../backend/docs/redoc/redoc.standalone.js | 1832 ++++ baron-sso/backend/docs/swagger-ui/index.html | 63 + .../docs/swagger-ui/swagger-ui-bundle.js | 2 + .../swagger-ui-standalone-preset.js | 2 + .../backend/docs/swagger-ui/swagger-ui.css | 3 + baron-sso/backend/go.mod | 119 + baron-sso/backend/go.sum | 344 + .../internal/bootstrap/admin_account.go | 203 + .../internal/bootstrap/admin_account_test.go | 157 + .../backend/internal/bootstrap/bootstrap.go | 116 + .../backend/internal/bootstrap/keto_sync.go | 84 + .../backend/internal/bootstrap/kratos_seed.go | 75 + .../backend/internal/bootstrap/sync_admin.go | 77 + .../backend/internal/bootstrap/tenant_seed.go | 524 + .../internal/bootstrap/tenant_seed_test.go | 388 + .../bootstrap/user_metadata_sanitize.go | 34 + .../bootstrap/user_metadata_sanitize_test.go | 203 + baron-sso/backend/internal/domain/api_key.go | 30 + .../backend/internal/domain/auth_models.go | 123 + .../backend/internal/domain/client_consent.go | 33 + .../backend/internal/domain/client_secret.go | 21 + .../backend/internal/domain/data_integrity.go | 60 + .../internal/domain/developer_request.go | 29 + .../backend/internal/domain/email_models.go | 6 + .../internal/domain/federation_models.go | 50 + .../backend/internal/domain/hanmac_email.go | 196 + .../internal/domain/hanmac_email_test.go | 76 + .../internal/domain/headless_jwks_cache.go | 30 + .../backend/internal/domain/hydra_models.go | 145 + .../internal/domain/hydra_models_test.go | 182 + .../backend/internal/domain/identity_cache.go | 19 + .../backend/internal/domain/idp_models.go | 92 + baron-sso/backend/internal/domain/json_map.go | 42 + .../backend/internal/domain/json_map_test.go | 93 + .../backend/internal/domain/keto_outbox.go | 48 + .../internal/domain/model_hooks_test.go | 357 + baron-sso/backend/internal/domain/models.go | 48 + .../internal/domain/oathkeeper_models.go | 31 + .../backend/internal/domain/relying_party.go | 19 + .../backend/internal/domain/rp_usage_event.go | 101 + .../internal/domain/rp_user_metadata.go | 16 + .../backend/internal/domain/shared_link.go | 53 + .../internal/domain/shared_link_test.go | 80 + .../backend/internal/domain/sms_models.go | 42 + .../backend/internal/domain/system_setting.go | 11 + baron-sso/backend/internal/domain/tenant.go | 53 + .../backend/internal/domain/tenant_domain.go | 27 + baron-sso/backend/internal/domain/user.go | 247 + .../backend/internal/domain/user_group.go | 50 + .../internal/domain/user_projection.go | 29 + .../backend/internal/domain/user_test.go | 73 + .../internal/domain/user_validate_test.go | 64 + .../backend/internal/domain/worksmobile.go | 73 + .../backend/internal/handler/admin_handler.go | 491 + .../internal/handler/admin_handler_test.go | 343 + .../internal/handler/admin_integrity_test.go | 196 + .../internal/handler/api_key_handler.go | 288 + .../internal/handler/api_key_handler_test.go | 133 + .../backend/internal/handler/audit_handler.go | 139 + .../backend/internal/handler/auth_handler.go | 9066 +++++++++++++++++ .../handler/auth_handler_async_test.go | 371 + .../handler/auth_handler_client_test.go | 200 + .../handler/auth_handler_consent_test.go | 515 + .../auth_handler_dynamic_claims_test.go | 829 ++ .../handler/auth_handler_link_test.go | 904 ++ .../handler/auth_handler_linked_test.go | 287 + .../handler/auth_handler_login_code_test.go | 206 + .../handler/auth_handler_login_test.go | 2398 +++++ .../handler/auth_handler_oidc_test.go | 178 + .../internal/handler/auth_handler_otp_test.go | 110 + .../auth_handler_profile_cache_test.go | 244 + .../internal/handler/auth_handler_qr_test.go | 206 + .../auth_handler_session_profile_test.go | 105 + .../handler/auth_handler_sessions_test.go | 944 ++ .../handler/auth_handler_signup_test.go | 144 + .../internal/handler/auth_handler_test.go | 521 + .../internal/handler/client_tenant_access.go | 537 + .../handler/client_tenant_access_test.go | 458 + .../backend/internal/handler/common_test.go | 303 + .../backend/internal/handler/dev_handler.go | 4165 ++++++++ .../handler/dev_handler_isolation_test.go | 242 + .../handler/dev_handler_rp_metadata_test.go | 278 + .../internal/handler/dev_handler_test.go | 3791 +++++++ .../backend/internal/handler/error_helper.go | 17 + .../internal/handler/federation_handler.go | 161 + .../internal/handler/hanmac_email_policy.go | 243 + .../internal/handler/password_policy_test.go | 98 + .../internal/handler/relying_party_handler.go | 114 + .../internal/handler/rp_manifest_handler.go | 255 + .../handler/rp_manifest_handler_test.go | 125 + .../internal/handler/tenant_access_cleanup.go | 154 + .../handler/tenant_access_cleanup_test.go | 178 + .../handler/tenant_assignment_policy.go | 155 + .../internal/handler/tenant_handler.go | 3154 ++++++ .../tenant_handler_seed_delete_test.go | 159 + .../internal/handler/tenant_handler_test.go | 1732 ++++ .../internal/handler/user_group_handler.go | 133 + .../handler/user_group_handler_test.go | 155 + .../backend/internal/handler/user_handler.go | 3702 +++++++ .../internal/handler/user_handler_test.go | 2707 +++++ .../handler/user_handler_uuid_test.go | 68 + .../handler/user_projection_failure.go | 16 + .../internal/handler/worksmobile_handler.go | 217 + .../handler/worksmobile_handler_test.go | 267 + .../dev_handler_headless_secret_test.go | 560 + baron-sso/backend/internal/idp/factory.go | 305 + .../backend/internal/idp/factory_test.go | 167 + .../backend/internal/logger/audit_logger.go | 240 + .../internal/logger/audit_logger_test.go | 80 + .../internal/logger/client_log_policy.go | 147 + .../internal/logger/client_log_policy_test.go | 96 + baron-sso/backend/internal/logger/logger.go | 78 + .../backend/internal/logger/logger_test.go | 68 + .../internal/middleware/api_key_auth.go | 121 + .../internal/middleware/api_key_auth_test.go | 15 + .../internal/middleware/audit_middleware.go | 224 + .../middleware/audit_middleware_test.go | 265 + .../middleware/error_code_enricher.go | 49 + .../middleware/error_code_enricher_test.go | 91 + .../internal/middleware/error_helper.go | 17 + baron-sso/backend/internal/middleware/rbac.go | 166 + .../backend/internal/middleware/rbac_test.go | 266 + .../internal/middleware/tenant_middleware.go | 69 + .../middleware/tenant_middleware_test.go | 123 + .../backend/internal/pagination/cursor.go | 103 + .../internal/pagination/cursor_test.go | 142 + .../internal/repository/clickhouse_repo.go | 449 + .../repository/client_consent_repository.go | 138 + .../client_consent_repository_test.go | 31 + .../repository/client_secret_repository.go | 40 + .../repository/data_integrity_repository.go | 380 + .../data_integrity_repository_test.go | 312 + .../repository/federation_repository.go | 10 + .../repository/gorm_federation_repository.go | 24 + .../repository/keto_outbox_repository.go | 88 + .../repository/keto_outbox_repository_test.go | 68 + .../backend/internal/repository/main_test.go | 74 + .../repository/oathkeeper_clickhouse_repo.go | 160 + .../repository/relying_party_repository.go | 61 + .../repository/rp_usage_outbox_repository.go | 91 + .../repository/rp_user_metadata_repository.go | 40 + .../repository/shared_link_repository.go | 51 + .../internal/repository/tenant_repository.go | 185 + .../repository/tenant_repository_test.go | 192 + .../repository/user_group_repository.go | 53 + .../repository/user_membership_maintenance.go | 51 + .../user_membership_maintenance_test.go | 64 + .../repository/user_projection_repository.go | 227 + .../user_projection_repository_test.go | 168 + .../internal/repository/user_repository.go | 335 + .../repository/user_repository_test.go | 273 + .../worksmobile_outbox_repository.go | 208 + .../worksmobile_outbox_repository_test.go | 125 + .../internal/response/error_response.go | 44 + .../internal/response/error_response_test.go | 113 + .../service/backchannel_logout_service.go | 192 + .../backchannel_logout_service_test.go | 85 + .../internal/service/developer_service.go | 86 + .../internal/service/dry_run_service.go | 19 + .../internal/service/dry_run_service_test.go | 43 + .../internal/service/federation_service.go | 90 + .../internal/service/headless_jwks_cache.go | 545 + .../service/headless_jwks_cache_test.go | 450 + .../internal/service/hydra_admin_service.go | 639 ++ .../service/hydra_admin_service_test.go | 331 + .../internal/service/keto_relay_worker.go | 78 + .../backend/internal/service/keto_service.go | 267 + .../internal/service/keto_service_test.go | 156 + .../internal/service/kratos_admin_service.go | 556 + .../service/kratos_admin_service_test.go | 63 + .../internal/service/mock_common_test.go | 139 + .../backend/internal/service/ory_service.go | 967 ++ .../internal/service/ory_service_test.go | 226 + .../backend/internal/service/redis_service.go | 238 + .../internal/service/redis_service_test.go | 150 + .../internal/service/relying_party_service.go | 215 + .../service/relying_party_service_test.go | 217 + .../service/rp_usage_event_emitter.go | 67 + .../service/rp_usage_event_emitter_test.go | 132 + .../service/rp_usage_projector_worker.go | 82 + .../backend/internal/service/ses_service.go | 78 + .../internal/service/shared_link_service.go | 63 + .../backend/internal/service/sms_service.go | 134 + .../internal/service/sms_service_test.go | 26 + .../internal/service/tenant_service.go | 394 + .../service/tenant_service_edge_test.go | 114 + .../internal/service/tenant_service_test.go | 345 + .../internal/service/user_group_service.go | 466 + .../service/user_group_service_edge_test.go | 103 + .../service/user_group_service_test.go | 463 + .../service/user_projection_sync_service.go | 153 + .../user_projection_sync_service_test.go | 142 + .../internal/service/worksmobile_client.go | 1527 +++ .../service/worksmobile_client_test.go | 1596 +++ .../service/worksmobile_live_flow_test.go | 1124 ++ .../internal/service/worksmobile_mapper.go | 972 ++ .../service/worksmobile_mapper_test.go | 856 ++ .../service/worksmobile_relay_leader_lock.go | 79 + .../service/worksmobile_relay_worker.go | 302 + .../service/worksmobile_sync_service.go | 2066 ++++ .../service/worksmobile_sync_service_test.go | 2224 ++++ baron-sso/backend/internal/testsupport/env.go | 34 + baron-sso/backend/internal/utils/audit.go | 17 + .../backend/internal/utils/audit_test.go | 27 + baron-sso/backend/internal/utils/client_ip.go | 87 + .../backend/internal/utils/client_ip_test.go | 69 + baron-sso/backend/internal/utils/masking.go | 80 + .../backend/internal/utils/masking_test.go | 59 + .../backend/internal/utils/password_policy.go | 200 + .../internal/utils/password_policy_test.go | 128 + baron-sso/backend/internal/utils/slug.go | 121 + baron-sso/backend/internal/utils/slug_test.go | 115 + .../internal/validator/schema_validator.go | 62 + .../validator/schema_validator_test.go | 145 + baron-sso/backend/seed-tenant.csv | 0 .../checksums.sha256 | 11 + .../manifest.json | 14 + .../postgres/baron.dump | Bin 0 -> 2864143 bytes .../postgres/globals.sql | 35 + .../postgres/ory_hydra.dump | Bin 0 -> 106885 bytes .../postgres/ory_keto.dump | Bin 0 -> 2162526 bytes .../postgres/ory_kratos.dump | Bin 0 -> 296886 bytes .../checksums.sha256 | 11 + .../manifest.json | 14 + .../postgres/baron.dump | Bin 0 -> 4204616 bytes .../postgres/globals.sql | 35 + .../postgres/ory_hydra.dump | Bin 0 -> 78002 bytes .../postgres/ory_keto.dump | Bin 0 -> 3022615 bytes .../postgres/ory_kratos.dump | Bin 0 -> 1378636 bytes baron-sso/common/.npmrc | 1 + baron-sso/common/biome.json | 3 + baron-sso/common/config/biome.base.json | 36 + baron-sso/common/config/vite.base.ts | 67 + baron-sso/common/core/audit/index.ts | 92 + baron-sso/common/core/auth/index.ts | 87 + .../components/audit/AuditLogTable.test.tsx | 148 + .../core/components/audit/AuditLogTable.tsx | 409 + .../common/core/components/audit/index.ts | 1 + .../components/overview/OverviewAxisNotes.tsx | 14 + .../components/overview/OverviewMetric.tsx | 19 + .../overview/OverviewSelectionChips.tsx | 50 + .../common/core/components/overview/index.ts | 3 + .../core/components/page/PageHeader.tsx | 68 + .../common/core/components/page/index.ts | 1 + .../components/sort/SortableTableHead.tsx | 170 + .../common/core/components/sort/index.ts | 1 + baron-sso/common/core/i18n/index.ts | 11 + baron-sso/common/core/i18n/loader.ts | 182 + baron-sso/common/core/i18n/types.ts | 20 + .../common/core/pagination/cursorFetch.ts | 85 + .../core/pagination/cursorFetch.worker.ts | 44 + .../common/core/pagination/cursorFetchCore.ts | 107 + baron-sso/common/core/pagination/index.ts | 6 + baron-sso/common/core/query/queryClient.ts | 7 + baron-sso/common/core/session/index.ts | 114 + baron-sso/common/core/utils/index.ts | 8 + baron-sso/common/core/utils/sort.ts | 97 + baron-sso/common/locales/en.toml | 206 + baron-sso/common/locales/ko.toml | 206 + baron-sso/common/locales/template.toml | 206 + baron-sso/common/package-lock.json | 5507 ++++++++++ baron-sso/common/package.json | 50 + baron-sso/common/pnpm-lock.yaml | 4556 +++++++++ baron-sso/common/shell/AppSidebar.tsx | 105 + baron-sso/common/shell/index.ts | 147 + baron-sso/common/shell/layout.ts | 66 + baron-sso/common/theme/base.css | 64 + baron-sso/common/theme/tailwind.preset.ts | 68 + baron-sso/common/tsconfig.base.json | 8 + baron-sso/common/ui/badge.ts | 28 + baron-sso/common/ui/button.ts | 37 + baron-sso/common/ui/card.ts | 7 + baron-sso/common/ui/input.ts | 2 + baron-sso/common/ui/search-filter-bar.tsx | 44 + baron-sso/common/ui/table.ts | 18 + baron-sso/compose.infra.yaml | 82 + baron-sso/compose.ory.yaml | 310 + .../compose/compose.infra.yaml | 82 + .../config-restored/compose/compose.ory.yaml | 319 + .../compose/docker-compose.yaml | 184 + baron-sso/config-restored/env.redacted | 119 + baron-sso/config-restored/gateway.tar.zst | Bin 0 -> 1954 bytes .../config-restored/generated-ory.tar.zst | Bin 0 -> 6516 bytes baron-sso/config/.gitkeep | 1 + baron-sso/deploy/create-instance.sh | 97 + baron-sso/deploy/deploy_guide.md | 64 + baron-sso/deploy/templates/.env.template | 77 + .../templates/adminfront/vite.config.ts | 32 + baron-sso/deploy/templates/auth.template.ts | 21 + .../deploy/templates/devfront/vite.config.ts | 29 + .../deploy/templates/docker-compose.yaml | 375 + baron-sso/deploy/templates/gateway/nginx.conf | 42 + baron-sso/deploy/templates/orgfront/auth.ts | 23 + .../deploy/templates/orgfront/vite.config.ts | 36 + .../templates/ory/kratos/kratos.yml.template | 102 + .../templates/ory/oathkeeper/rules.json | 159 + .../deploy/templates/userfront/nginx.conf | 71 + baron-sso/devfront/.gitignore | 24 + baron-sso/devfront/Dockerfile | 38 + baron-sso/devfront/README.md | 29 + baron-sso/devfront/biome.json | 7 + .../tenant-access-allowed-tenant-added.png | Bin 0 -> 821520 bytes .../tenant-access-allowed-tenant-deleted.png | Bin 0 -> 829880 bytes baron-sso/devfront/hydra-rp-dummy.py | 188 + baron-sso/devfront/index.html | 13 + baron-sso/devfront/package-lock.json | 5455 ++++++++++ baron-sso/devfront/package.json | 60 + baron-sso/devfront/playwright.config.ts | 81 + baron-sso/devfront/pnpm-lock.yaml | 3474 +++++++ baron-sso/devfront/postcss.config.js | 6 + baron-sso/devfront/public/vite.svg | 1 + baron-sso/devfront/scripts/runtime-mode.sh | 143 + baron-sso/devfront/src/app/queryClient.ts | 7 + baron-sso/devfront/src/app/routes.test.tsx | 13 + baron-sso/devfront/src/app/routes.tsx | 66 + baron-sso/devfront/src/assets/react.svg | 1 + .../DeveloperAccessRequestCard.test.tsx | 66 + .../common/DeveloperAccessRequestCard.tsx | 48 + .../common/ForbiddenMessage.test.tsx | 81 + .../components/common/ForbiddenMessage.tsx | 67 + .../common/LanguageSelector.test.tsx | 77 + .../components/common/LanguageSelector.tsx | 58 + .../src/components/layout/AppLayout.test.tsx | 184 + .../src/components/layout/AppLayout.tsx | 601 ++ .../devfront/src/components/ui/avatar.tsx | 47 + .../devfront/src/components/ui/badge.tsx | 21 + .../devfront/src/components/ui/button.tsx | 31 + baron-sso/devfront/src/components/ui/card.tsx | 58 + .../src/components/ui/copy-button.test.tsx | 107 + .../src/components/ui/copy-button.tsx | 75 + .../devfront/src/components/ui/input.tsx | 22 + .../devfront/src/components/ui/label.tsx | 19 + .../src/components/ui/scroll-area.tsx | 44 + .../devfront/src/components/ui/separator.tsx | 16 + .../devfront/src/components/ui/switch.tsx | 26 + .../devfront/src/components/ui/table.tsx | 102 + .../devfront/src/components/ui/textarea.tsx | 23 + .../devfront/src/components/ui/toaster.tsx | 35 + .../devfront/src/components/ui/use-toast.ts | 42 + .../src/features/audit/AuditLogsPage.test.tsx | 212 + .../src/features/audit/AuditLogsPage.tsx | 311 + .../src/features/auth/AuthCallbackPage.tsx | 35 + .../devfront/src/features/auth/AuthGuard.tsx | 84 + .../devfront/src/features/auth/AuthPage.tsx | 111 + .../devfront/src/features/auth/LoginPage.tsx | 177 + .../src/features/auth/authApi.test.ts | 19 + .../devfront/src/features/auth/authApi.ts | 24 + .../src/features/auth/authPages.test.tsx | 162 + .../features/clients/ClientConsentsPage.tsx | 1140 +++ .../clients/ClientDetailTabs.test.tsx | 29 + .../src/features/clients/ClientDetailTabs.tsx | 61 + .../features/clients/ClientDetailsPage.tsx | 547 + .../features/clients/ClientGeneralPage.tsx | 2928 ++++++ .../features/clients/ClientRelationsPage.tsx | 766 ++ .../src/features/clients/ClientsPage.test.tsx | 280 + .../src/features/clients/ClientsPage.tsx | 828 ++ .../clients/clientCreateAccess.test.ts | 64 + .../features/clients/clientCreateAccess.ts | 48 + .../clients/clientSecretPolicy.test.ts | 28 + .../features/clients/clientSecretPolicy.ts | 7 + .../components/AllowedTenantBadge.test.tsx | 82 + .../clients/components/AllowedTenantBadge.tsx | 59 + .../clients/components/ClientLogo.test.tsx | 65 + .../clients/components/ClientLogo.tsx | 59 + .../routes/ClientFederationPage.test.tsx | 179 + .../clients/routes/ClientFederationPage.tsx | 305 + .../features/coverage/AuditLogTable.test.tsx | 123 + .../src/features/coverage/commonAudit.test.ts | 72 + .../src/features/coverage/commonAuth.test.ts | 72 + .../src/features/coverage/commonSort.test.ts | 54 + .../src/features/coverage/pageSmoke.test.tsx | 513 + .../developerAccessGate.test.ts | 48 + .../developer-access/developerAccessGate.ts | 85 + .../DeveloperRequestPage.test.tsx | 188 + .../DeveloperRequestPage.tsx | 599 ++ .../features/overview/GlobalOverviewPage.tsx | 1609 +++ .../overview/recentClientChanges.test.ts | 253 + .../features/overview/recentClientChanges.ts | 203 + .../src/features/profile/ProfilePage.tsx | 224 + .../profile/ProfileTenantSwitcher.tsx | 92 + baron-sso/devfront/src/index.css | 46 + baron-sso/devfront/src/lib/apiClient.test.ts | 88 + baron-sso/devfront/src/lib/apiClient.ts | 88 + baron-sso/devfront/src/lib/auth.ts | 23 + baron-sso/devfront/src/lib/authConfig.test.ts | 102 + baron-sso/devfront/src/lib/authConfig.ts | 88 + baron-sso/devfront/src/lib/devApi.test.ts | 261 + baron-sso/devfront/src/lib/devApi.ts | 597 ++ baron-sso/devfront/src/lib/i18n.ts | 16 + .../devfront/src/lib/oidcStorage.test.ts | 76 + baron-sso/devfront/src/lib/oidcStorage.ts | 42 + baron-sso/devfront/src/lib/role.test.ts | 33 + baron-sso/devfront/src/lib/role.ts | 38 + baron-sso/devfront/src/lib/sessionSliding.ts | 6 + baron-sso/devfront/src/lib/utils.ts | 8 + baron-sso/devfront/src/locales/en.toml | 2076 ++++ baron-sso/devfront/src/locales/ko.toml | 2072 ++++ baron-sso/devfront/src/locales/template.toml | 2105 ++++ baron-sso/devfront/src/main.tsx | 27 + baron-sso/devfront/tailwind.config.ts | 14 + baron-sso/devfront/tests/clients.spec.ts | 258 + .../devfront/tests/devfront-audit.spec.ts | 124 + .../tests/devfront-client-tabs.spec.ts | 68 + .../devfront-client-tenant-access.spec.ts | 138 + .../tests/devfront-clients-lifecycle.spec.ts | 606 ++ .../devfront/tests/devfront-consents.spec.ts | 109 + .../tests/devfront-developer-request.spec.ts | 169 + .../devfront/tests/devfront-login.spec.ts | 36 + .../tests/devfront-relationships.spec.ts | 145 + .../tests/devfront-role-switch-report.spec.ts | 239 + .../devfront/tests/devfront-security.spec.ts | 222 + .../tests/devfront-tenant-switch.spec.ts | 123 + baron-sso/devfront/tests/example.spec.ts | 15 + .../tests/helpers/devfront-fixtures.ts | 833 ++ baron-sso/devfront/tests/helpers/evidence.ts | 24 + baron-sso/devfront/tsconfig.app.json | 37 + baron-sso/devfront/tsconfig.json | 7 + baron-sso/devfront/tsconfig.node.json | 26 + baron-sso/devfront/vite.config.ts | 48 + baron-sso/devfront/vitest.config.ts | 52 + baron-sso/docker-compose.yaml | 223 + baron-sso/docker/README.md | 67 + baron-sso/docker/backup-tools/Dockerfile | 24 + baron-sso/docker/compose.infra.prd.yaml | 46 + baron-sso/docker/compose.infra.yaml | 80 + baron-sso/docker/compose.ory.yaml | 251 + .../docker-compose.staging.template.yaml | 147 + baron-sso/docker/docker-compose.template.yaml | 121 + .../docker/init-metadata/01_init_metadata.sql | 42 + baron-sso/docker/monitor/blackbox.yml | 10 + .../dashboards/baron_sso_dashboard.json | 161 + baron-sso/docker/ory/clickhouse/init.sql | 32 + baron-sso/docker/ory/hydra/hydra.yml.template | 98 + baron-sso/docker/ory/init-db/01_create_dbs.sh | 24 + baron-sso/docker/ory/keto/keto.yml.template | 15 + baron-sso/docker/ory/keto/namespaces.ts | 150 + baron-sso/docker/ory/keto/namespaces.yml | 6 + .../docker/ory/kratos/courier-http.jsonnet | 8 + .../login_code/valid/email.body.gotmpl | 17 + .../valid/email.body.plaintext.gotmpl | 10 + .../login_code/valid/email.subject.gotmpl | 1 + .../login_code/valid/sms.body.gotmpl | 4 + .../docker/ory/kratos/identity.schema.json | 126 + .../docker/ory/kratos/kratos.yml.template | 98 + baron-sso/docker/ory/oathkeeper/entrypoint.sh | 45 + .../ory/oathkeeper/oathkeeper.yml.template | 69 + .../docker/ory/oathkeeper/rules.active.json | 159 + .../docker/ory/oathkeeper/rules.draft.json | 91 + baron-sso/docker/ory/oathkeeper/rules.json | 159 + .../docker/ory/oathkeeper/rules.prod.json | 159 + .../docker/ory/oathkeeper/rules.stage.json | 159 + baron-sso/docker/ory/vector/vector.toml | 183 + .../docker/promtail-config.template.yaml | 41 + .../docker/staging_pull_compose.template.yaml | 590 ++ baron-sso/docs/AGENTS.md | 66 + baron-sso/docs/API_DESIGN_POLICY.md | 121 + baron-sso/docs/Gemini.md | 50 + baron-sso/docs/SoT_Architecture_Policy.md | 44 + baron-sso/docs/TEST_GUIDE.md | 92 + baron-sso/docs/UI_DESIGN_POLICY.md | 118 + ...login-policy-p0-mobile-installed-webapp.md | 79 + .../docs/b2b2b_dynamic_provisioning_flow.md | 59 + baron-sso/docs/backend-log-policy.md | 74 + baron-sso/docs/backup-restore-design.md | 590 ++ baron-sso/docs/badges/adminfront.svg | 19 + baron-sso/docs/badges/backend-tests.svg | 19 + baron-sso/docs/badges/badges.json | 69 + baron-sso/docs/badges/biome.svg | 19 + baron-sso/docs/badges/code-check.svg | 19 + baron-sso/docs/badges/dev-sha.svg | 19 + baron-sso/docs/badges/devfront.svg | 19 + baron-sso/docs/badges/orgfront.svg | 19 + baron-sso/docs/badges/userfront-chrome.svg | 19 + baron-sso/docs/badges/userfront-firefox.svg | 19 + baron-sso/docs/badges/userfront-safari.svg | 19 + baron-sso/docs/badges/userfront.svg | 19 + baron-sso/docs/baron-orgchart-data-flow.md | 57 + baron-sso/docs/client-log-policy.md | 71 + baron-sso/docs/client_deactivation_flow.md | 91 + .../docs/complete-password-reset-refactor.md | 48 + baron-sso/docs/compose-ory.md | 147 + baron-sso/docs/consent_flow_explanation.md | 77 + baron-sso/docs/consent_listing_flow.md | 74 + baron-sso/docs/consent_loop_fix_report.md | 117 + baron-sso/docs/consent_reject_flow.md | 62 + .../docs/consent_revoke_implementation.md | 138 + .../docs/consent_scope_selection_flow.md | 110 + .../docs/custom-field-jsonb-index-policy.md | 241 + baron-sso/docs/data-integrity-management.md | 70 + .../docs/devfront-rp-relationships-guide.md | 173 + .../docs/devfront_auth_flow_explanation.md | 91 + baron-sso/docs/employee_id_login_db_design.md | 53 + baron-sso/docs/employee_id_login_design.md | 47 + .../external_healthcheck_monitoring_design.md | 213 + .../docs/frontend-monorepo-masterplan.md | 76 + .../docs/frontend_hydra_testing_guide.md | 45 + baron-sso/docs/hydra-rp-dummy.md | 51 + baron-sso/docs/hydra_backend_test_guide.md | 147 + baron-sso/docs/hydra_be_test_guide.md | 144 + baron-sso/docs/i18n.md | 269 + ...identity-redis-mirror-policy-2026-06-09.md | 245 + baron-sso/docs/initial_PRD.md | 56 + .../docs/integrations-org-context-json-api.md | 166 + .../docs/keto-rebac-namespaces-diagram.md | 124 + baron-sso/docs/keto-rebac-policy-guide.md | 87 + baron-sso/docs/keto-tuple-samples.md | 72 + .../kratos-user-traits-field-inventory.md | 82 + ...oidc_redirect_mapping_validation_policy.md | 100 + baron-sso/docs/organization-chart-policy.md | 111 + baron-sso/docs/ory-stack-guide.md | 84 + baron-sso/docs/ory-usage.md | 140 + .../docs/pkce-backchannel-logout-guide.md | 321 + baron-sso/docs/rbac-rebac-policy.md | 104 + baron-sso/docs/rp-auto-login-guide.md | 244 + baron-sso/docs/rp-iam-integration-guide.md | 305 + .../docs/rp_activity_ux_expansion_flow.md | 105 + .../docs/rp_redirection_implementation.md | 138 + ...erver-side-app-backchannel-logout-guide.md | 322 + .../worksmobile-change-log-2026-06-02.png | Bin 0 -> 210418 bytes ...-502-restart-policy-incident-2026-06-08.md | 80 + baron-sso/docs/staging-deployment-flow.md | 85 + ...-30-headless-password-login-backend-api.md | 77 + ...-04-01-headless-login-debug-improvement.md | 150 + .../2026-04-01-headless-login-debug-design.md | 146 + .../docs/tenant-maintenance-procedures.md | 158 + baron-sso/docs/tenant-policy.md | 32 + baron-sso/docs/tenant-usergroup-policy.md | 163 + .../docs/tenant-visibility-exposure-policy.md | 59 + .../docs/test-plan/backend-test-inventory.md | 132 + .../userfront-wasm-e2e-expansion-plan.md | 103 + .../docs/test-plan/web-e2e-test-inventory.md | 12 + .../dev-branch-conflict-policy.md | 53 + .../trouble-shooting/hydra-rp-consent-try.md | 77 + .../issue-146-remote-login.md | 35 + .../issue-269-locale-query-loss.md | 51 + .../issue-269-test-scenarios.md | 83 + .../issue-277-null-check-dashboard-routing.md | 76 + .../issue-281-locale-storage-refactor-plan.md | 94 + ...e-303-login-link-shortcode-locale-route.md | 47 + .../issue-434-dashboard-session-start-time.md | 45 + .../issue-614-skip-consent.md | 36 + .../issue-663-hanmac-family-orgfront-sync.md | 43 + .../issue_489_completion_summary.md | 28 + .../docs/trouble-shooting/userfront-locale.md | 86 + .../docs/user-group-rebac-architecture.md | 89 + ...-projection-visibility-audit-2026-06-08.md | 123 + .../docs/userfront_error_handling_policy.md | 82 + ...rror-handling-policy-backend-log-update.md | 307 + .../works-only-user-recovery-2026-06-09.md | 112 + ...mobile-api-rate-limit-policy-2026-06-09.md | 64 + ...smobile-directory-sync-technical-review.md | 449 + ...worksmobile-halla-domain-migration-plan.md | 126 + baron-sso/docs/개발완료보고서.md | 2471 +++++ baron-sso/fix_toml.py | 43 + baron-sso/gateway/Dockerfile | 6 + baron-sso/gateway/entrypoint.sh | 31 + baron-sso/gateway/nginx.conf | 116 + baron-sso/image.png | Bin 0 -> 58287 bytes baron-sso/locales/en.toml | 3039 ++++++ baron-sso/locales/ko.toml | 3465 +++++++ baron-sso/locales/template.toml | 3362 ++++++ baron-sso/mcp/compose.mcp.ory.yaml | 43 + baron-sso/mcp/hydra-mcp/Dockerfile | 12 + baron-sso/mcp/hydra-mcp/biome.json | 3 + baron-sso/mcp/hydra-mcp/package-lock.json | 1337 +++ baron-sso/mcp/hydra-mcp/package.json | 21 + baron-sso/mcp/hydra-mcp/src/index.js | 349 + baron-sso/mcp/hydra-mcp/src/runner.js | 77 + baron-sso/mcp/keto-mcp/Dockerfile | 12 + baron-sso/mcp/keto-mcp/biome.json | 3 + baron-sso/mcp/keto-mcp/package-lock.json | 1337 +++ baron-sso/mcp/keto-mcp/package.json | 21 + baron-sso/mcp/keto-mcp/src/index.js | 344 + baron-sso/mcp/keto-mcp/src/runner.js | 77 + baron-sso/mcp/kratos-mcp/Dockerfile | 9 + baron-sso/orgfront/.dockerignore | 8 + baron-sso/orgfront/.gitignore | 24 + baron-sso/orgfront/Dockerfile | 38 + baron-sso/orgfront/Dockerfile2 | 20 + baron-sso/orgfront/README.md | 106 + baron-sso/orgfront/biome.json | 7 + baron-sso/orgfront/docker-compose.yml | 19 + .../docs/orgchart-embedding-token-design.md | 125 + baron-sso/orgfront/hydra-rp-dummy.py | 188 + baron-sso/orgfront/index.html | 13 + baron-sso/orgfront/package-lock.json | 5636 ++++++++++ baron-sso/orgfront/package.json | 64 + baron-sso/orgfront/playwright.config.ts | 89 + baron-sso/orgfront/pnpm-lock.yaml | 3635 +++++++ baron-sso/orgfront/postcss.config.js | 6 + baron-sso/orgfront/public/vite.svg | 1 + .../scripts/build-org-context-chart.mjs | 29 + baron-sso/orgfront/scripts/runtime-mode.sh | 143 + baron-sso/orgfront/src/app/queryClient.ts | 7 + baron-sso/orgfront/src/app/routes.test.tsx | 13 + baron-sso/orgfront/src/app/routes.tsx | 50 + baron-sso/orgfront/src/assets/react.svg | 1 + .../components/common/ForbiddenMessage.tsx | 51 + .../components/common/LanguageSelector.tsx | 58 + .../src/components/layout/AppLayout.test.tsx | 166 + .../src/components/layout/AppLayout.tsx | 571 ++ .../orgfront/src/components/ui/avatar.tsx | 47 + .../orgfront/src/components/ui/badge.tsx | 21 + .../orgfront/src/components/ui/basic.test.tsx | 95 + .../orgfront/src/components/ui/button.tsx | 31 + baron-sso/orgfront/src/components/ui/card.tsx | 58 + .../src/components/ui/copy-button.tsx | 75 + .../orgfront/src/components/ui/input.tsx | 22 + .../orgfront/src/components/ui/label.tsx | 19 + .../src/components/ui/scroll-area.tsx | 44 + .../orgfront/src/components/ui/separator.tsx | 16 + .../orgfront/src/components/ui/switch.tsx | 26 + .../orgfront/src/components/ui/table.tsx | 102 + .../orgfront/src/components/ui/textarea.tsx | 23 + .../orgfront/src/components/ui/toaster.tsx | 35 + .../orgfront/src/components/ui/use-toast.ts | 42 + .../src/features/audit/AuditLogsPage.tsx | 435 + .../src/features/auth/AuthCallbackPage.tsx | 35 + .../src/features/auth/AuthGuard.test.tsx | 77 + .../orgfront/src/features/auth/AuthGuard.tsx | 72 + .../orgfront/src/features/auth/AuthPage.tsx | 111 + .../src/features/auth/LoginPage.test.tsx | 73 + .../orgfront/src/features/auth/LoginPage.tsx | 137 + .../orgfront/src/features/auth/authApi.ts | 24 + .../features/clients/ClientConsentsPage.tsx | 603 ++ .../features/clients/ClientDetailsPage.tsx | 540 + .../features/clients/ClientGeneralPage.tsx | 1643 +++ .../src/features/clients/ClientsPage.tsx | 531 + .../clients/routes/ClientFederationPage.tsx | 308 + .../src/features/coverage/pageSmoke.test.tsx | 307 + .../src/features/dashboard/DashboardPage.tsx | 294 + .../orgchart/hanmacFamilyOrder.test.ts | 62 + .../features/orgchart/hanmacFamilyOrder.ts | 67 + .../src/features/orgchart/pickerTree.test.ts | 189 + .../src/features/orgchart/pickerTree.ts | 188 + .../src/features/orgchart/pickerTypes.test.ts | 56 + .../src/features/orgchart/pickerTypes.ts | 107 + .../features/orgchart/rankPriority.test.ts | 42 + .../src/features/orgchart/rankPriority.ts | 57 + .../orgchart/routes/OrgChartPage.test.tsx | 660 ++ .../features/orgchart/routes/OrgChartPage.tsx | 2144 ++++ .../orgchart/routes/OrgFrontLayout.tsx | 68 + .../routes/OrgPickerEmbedPreviewPage.tsx | 236 + .../orgchart/routes/OrgPickerPage.tsx | 737 ++ .../src/features/orgchart/tenantVisibility.ts | 45 + .../src/features/orgchart/userDisplay.test.ts | 167 + .../src/features/orgchart/userDisplay.ts | 77 + .../src/features/profile/ProfilePage.tsx | 218 + .../profile/ProfileTenantSwitcher.tsx | 92 + baron-sso/orgfront/src/index.css | 35 + baron-sso/orgfront/src/lib/adminApi.test.ts | 216 + baron-sso/orgfront/src/lib/adminApi.ts | 771 ++ baron-sso/orgfront/src/lib/apiClient.ts | 75 + baron-sso/orgfront/src/lib/auth.ts | 24 + baron-sso/orgfront/src/lib/authConfig.test.ts | 29 + baron-sso/orgfront/src/lib/authConfig.ts | 33 + baron-sso/orgfront/src/lib/devApi.test.ts | 139 + baron-sso/orgfront/src/lib/devApi.ts | 315 + baron-sso/orgfront/src/lib/i18n.ts | 16 + baron-sso/orgfront/src/lib/role.ts | 27 + baron-sso/orgfront/src/lib/sessionSliding.ts | 27 + baron-sso/orgfront/src/lib/tenantTree.test.ts | 46 + baron-sso/orgfront/src/lib/tenantTree.ts | 88 + baron-sso/orgfront/src/lib/utils.ts | 8 + baron-sso/orgfront/src/locales/en.toml | 1718 ++++ baron-sso/orgfront/src/locales/ko.toml | 1716 ++++ baron-sso/orgfront/src/locales/template.toml | 1703 ++++ baron-sso/orgfront/src/main.tsx | 27 + .../src/sdk/org-context-chart/index.ts | 987 ++ .../org-context-chart/orgContextChart.test.ts | 347 + baron-sso/orgfront/tailwind.config.ts | 14 + baron-sso/orgfront/tests/clients.spec.ts | 39 + .../orgfront/tests/devfront-audit.spec.ts | 120 + .../tests/devfront-clients-lifecycle.spec.ts | 374 + .../orgfront/tests/devfront-consents.spec.ts | 45 + .../tests/devfront-role-switch-report.spec.ts | 169 + .../orgfront/tests/devfront-security.spec.ts | 122 + .../tests/devfront-tenant-switch.spec.ts | 116 + baron-sso/orgfront/tests/example.spec.ts | 8 + .../tests/helpers/devfront-fixtures.ts | 513 + baron-sso/orgfront/tests/helpers/evidence.ts | 24 + baron-sso/orgfront/tests/light-theme.spec.ts | 38 + .../orgfront/tests/orgchart-pan-zoom.spec.ts | 765 ++ .../orgfront/tests/orgchart-picker.spec.ts | 737 ++ .../tests/orgchart-vector-render.spec.ts | 783 ++ .../tests/orgfront-auto-login.spec.ts | 74 + baron-sso/orgfront/tsconfig.app.json | 36 + baron-sso/orgfront/tsconfig.json | 7 + baron-sso/orgfront/tsconfig.node.json | 26 + baron-sso/orgfront/vite.config.ts | 51 + .../orgfront/vite.org-context-chart.config.ts | 33 + baron-sso/orgfront/vitest.config.ts | 58 + baron-sso/package.json | 12 + baron-sso/pnpm-lock.yaml | 4239 ++++++++ baron-sso/pnpm-workspace.yaml | 7 + baron-sso/saman_works_users.CSV | 1887 ++++ baron-sso/scripts/auth_config.sh | 485 + baron-sso/scripts/backup/dump-list.sh | 15 + baron-sso/scripts/backup/dump.sh | 69 + baron-sso/scripts/backup/lib/clickhouse.sh | 115 + baron-sso/scripts/backup/lib/common.sh | 137 + baron-sso/scripts/backup/lib/config.sh | 37 + baron-sso/scripts/backup/lib/manifest.sh | 42 + baron-sso/scripts/backup/lib/postgres.sh | 99 + baron-sso/scripts/backup/lib/report.sh | 148 + baron-sso/scripts/backup/restore-plan.sh | 5 + baron-sso/scripts/backup/restore.sh | 460 + baron-sso/scripts/backup/upload_cloud.sh | 642 ++ baron-sso/scripts/backup/verify-dump.sh | 16 + baron-sso/scripts/backup/verify-restore.sh | 13 + .../clear_orphan_tenant_memberships.sh | 99 + .../clear_orphan_user_tenant_memberships.sql | 67 + baron-sso/scripts/map_wasm_stack.py | 240 + baron-sso/scripts/playwrightHostDeps.cjs | 60 + .../scripts/playwrightPackageVersion.cjs | 24 + baron-sso/scripts/render_ory_config.sh | 228 + baron-sso/scripts/run_adminfront_ci_tests.sh | 330 + .../scripts/sanitize_baron_user_metadata.sql | 8 + baron-sso/scripts/serve_frontend_prod.mjs | 155 + .../scripts/summarize_flutter_coverage.mjs | 115 + .../scripts/summarize_vitest_coverage.mjs | 83 + baron-sso/scripts/sync_userfront_locales.sh | 47 + .../scripts/test_frontend_runtime_mode.sh | 36 + .../scripts/test_staging_workflow_env.sh | 39 + .../scripts/update_code_check_badges.mjs | 461 + .../scripts/verify_userfront_error_i18n.sh | 98 + .../adminfront_dev_performance_policy_test.sh | 50 + .../test/adminfront_port_env_policy_test.sh | 33 + .../adminfront_public_url_env_policy_test.sh | 41 + .../auth_config_orgfront_callback_test.sh | 27 + .../test/backend_go_version_policy_test.sh | 78 + baron-sso/test/backup_make_targets_test.sh | 76 + baron-sso/test/backup_scripts_policy_test.sh | 149 + .../test/backup_upload_cloud_policy_test.sh | 108 + .../code_check_badge_branch_policy_test.sh | 91 + ...code_check_playwright_cache_policy_test.sh | 35 + ...g_front_callback_public_url_policy_test.sh | 80 + baron-sso/test/env_secret_file_policy_test.sh | 23 + .../frontend_dev_bind_mount_policy_test.sh | 105 + .../gateway_userfront_residue_policy_test.sh | 35 + .../kratos_identity_schema_policy_test.sh | 15 + .../kratos_identity_write_path_policy_test.sh | 83 + baron-sso/test/make_dev_targets_test.sh | 196 + .../test/oathkeeper_access_log_e2e_test.sh | 123 + ...eper_kratos_public_exposure_policy_test.sh | 53 + .../test/orgfront_integration_policy_test.sh | 121 + ...orgfront_org_context_chart_package_test.sh | 32 + baron-sso/test/ory-network-check.sh | 28 + .../test/ory_log_pipeline_policy_test.sh | 41 + baron-sso/test/ory_v26_compose_policy_test.sh | 411 + .../test/playwright_package_version_test.sh | 25 + .../production_image_release_policy_test.sh | 102 + baron-sso/test/shell_layout_policy_test.sh | 30 + .../staging_frontend_deploy_policy_test.sh | 164 + .../test/staging_pull_restart_policy_test.sh | 93 + .../test/summarize_userfront_coverage_test.sh | 65 + baron-sso/test/test_sms.py | 111 + ...code_check_badges_package_coverage_test.sh | 153 + ...ode_check_badges_preserve_existing_test.sh | 119 + ...erfront_loading_performance_policy_test.sh | 122 + .../tools/i18n-scanner/gen-flutter-i18n.js | 96 + baron-sso/tools/i18n-scanner/index.js | 285 + baron-sso/tools/i18n-scanner/manual-keys.ts | 46 + baron-sso/tools/i18n-scanner/report.js | 302 + .../tools/i18n-scanner/translate-locales.js | 444 + baron-sso/tools/i18n-scanner/value-check.js | 365 + baron-sso/userfront-e2e/.gitignore | 3 + baron-sso/userfront-e2e/README.md | 29 + baron-sso/userfront-e2e/biome.json | 3 + .../fonts/NotoSansKR-TestSubset.woff2 | Bin 0 -> 27252 bytes baron-sso/userfront-e2e/package-lock.json | 290 + baron-sso/userfront-e2e/package.json | 25 + baron-sso/userfront-e2e/playwright.config.ts | 70 + baron-sso/userfront-e2e/pnpm-lock.yaml | 77 + .../scripts/serve-userfront-build.mjs | 148 + .../userfront-e2e/tests/auth-routing.spec.ts | 604 ++ .../tests/login-performance-budget.spec.ts | 283 + .../tests/oidc-login-challenge.spec.ts | 70 + .../tests/password-and-reset.spec.ts | 456 + .../tests/profile-department.spec.ts | 504 + .../tests/route-inventory.spec.ts | 335 + .../tests/runtime-env-mobile.spec.ts | 436 + .../tests/session-cross-browser-debug.spec.ts | 202 + .../tests/signup-theme-visibility.spec.ts | 470 + baron-sso/userfront-e2e/tsconfig.json | 14 + baron-sso/userfront/.gitignore | 45 + baron-sso/userfront/.metadata | 36 + baron-sso/userfront/Dockerfile | 38 + baron-sso/userfront/analysis_options.yaml | 28 + baron-sso/userfront/android/.gitignore | 14 + .../userfront/android/app/build.gradle.kts | 44 + .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 46 + .../kr/co/baroncs/frontend/MainActivity.kt | 5 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 6407 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 3519 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 9565 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 17423 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 25717 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + baron-sso/userfront/android/build.gradle.kts | 24 + baron-sso/userfront/android/gradle.properties | 2 + .../gradle/wrapper/gradle-wrapper.properties | 5 + .../userfront/android/settings.gradle.kts | 26 + baron-sso/userfront/assets/baron.ico | Bin 0 -> 16958 bytes .../assets/fonts/NotoSansKR-Bold.ttf | Bin 0 -> 6183932 bytes .../assets/fonts/NotoSansKR-Regular.ttf | Bin 0 -> 6185868 bytes .../userfront/assets/translations/en.toml | 709 ++ .../userfront/assets/translations/ko.toml | 930 ++ .../assets/translations/template.toml | 902 ++ baron-sso/userfront/ios/.gitignore | 34 + .../ios/Flutter/AppFrameworkInfo.plist | 26 + .../userfront/ios/Flutter/Debug.xcconfig | 1 + .../userfront/ios/Flutter/Release.xcconfig | 1 + .../ios/Runner.xcodeproj/project.pbxproj | 616 ++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 101 + .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../userfront/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 + .../Icon-App-1024x1024@1x.png | Bin 0 -> 241791 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 954 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 2680 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 4983 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 1646 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 4704 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 8497 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 2680 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 7530 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 13709 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 13709 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 23842 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 7042 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 19101 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 21347 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + .../ios/Runner/Base.lproj/Main.storyboard | 26 + baron-sso/userfront/ios/Runner/Info.plist | 51 + .../ios/Runner/Runner-Bridging-Header.h | 1 + .../ios/RunnerTests/RunnerTests.swift | 12 + .../lib/core/constants/error_whitelist.dart | 30 + .../userfront/lib/core/i18n/locale_gate.dart | 75 + .../lib/core/i18n/locale_registry.dart | 115 + .../lib/core/i18n/locale_storage.dart | 59 + .../lib/core/i18n/locale_storage_backend.dart | 38 + .../lib/core/i18n/locale_storage_engine.dart | 245 + .../lib/core/i18n/locale_storage_policy.dart | 13 + .../lib/core/i18n/locale_storage_stub.dart | 7 + .../lib/core/i18n/locale_storage_web.dart | 22 + .../userfront/lib/core/i18n/locale_utils.dart | 117 + .../lib/core/i18n/toml_asset_loader.dart | 54 + .../lib/core/notifiers/auth_notifier.dart | 16 + .../lib/core/services/audit_service.dart | 40 + .../lib/core/services/auth_proxy_service.dart | 1140 +++ .../lib/core/services/auth_token_store.dart | 45 + .../services/auth_token_store_backend.dart | 124 + .../core/services/auth_token_store_stub.dart | 53 + .../core/services/auth_token_store_web.dart | 48 + .../lib/core/services/http_client.dart | 6 + .../lib/core/services/http_client_stub.dart | 9 + .../lib/core/services/http_client_web.dart | 12 + .../lib/core/services/log_policy.dart | 139 + .../lib/core/services/logger_service.dart | 111 + .../services/login_challenge_loop_guard.dart | 4 + .../login_challenge_loop_guard_base.dart | 5 + .../login_challenge_loop_guard_stub.dart | 37 + .../login_challenge_loop_guard_web.dart | 69 + .../lib/core/services/logout_service.dart | 39 + .../core/services/null_check_recovery.dart | 26 + .../core/services/oidc_redirect_guard.dart | 220 + .../lib/core/services/runtime_env.dart | 37 + .../core/services/web_auth_integration.dart | 13 + .../services/web_auth_integration_stub.dart | 10 + .../services/web_auth_integration_web.dart | 98 + .../lib/core/services/web_window.dart | 1 + .../lib/core/services/web_window_stub.dart | 27 + .../lib/core/services/web_window_web.dart | 82 + .../userfront/lib/core/theme/app_theme.dart | 144 + .../lib/core/theme/theme_controller.dart | 37 + .../userfront/lib/core/theme/theme_scope.dart | 43 + .../lib/core/ui/layout_breakpoints.dart | 1 + .../userfront/lib/core/ui/toast_service.dart | 234 + .../lib/core/widgets/language_selector.dart | 74 + .../lib/core/widgets/theme_toggle_button.dart | 44 + .../presentation/create_user_screen.dart | 250 + .../presentation/user_management_screen.dart | 539 + .../auth/domain/consent_error_routing.dart | 29 + .../auth/domain/cookie_session_policy.dart | 15 + .../auth/domain/login_challenge_resolver.dart | 195 + .../auth/domain/login_link_route_policy.dart | 35 + .../domain/password_login_flow_policy.dart | 23 + .../domain/verification_completion_route.dart | 67 + .../auth/presentation/approve_qr_screen.dart | 233 + .../auth/presentation/consent_screen.dart | 501 + .../auth/presentation/error_screen.dart | 690 ++ .../presentation/forgot_password_screen.dart | 172 + .../auth/presentation/login_screen.dart | 2427 +++++ .../presentation/login_success_screen.dart | 74 + .../qr_camera_bootstrap_policy.dart | 54 + .../auth/presentation/qr_scan_route.dart | 28 + .../auth/presentation/qr_scan_screen.dart | 2 + .../presentation/qr_scan_screen_stub.dart | 89 + .../auth/presentation/qr_scan_screen_web.dart | 247 + .../presentation/reset_password_screen.dart | 351 + .../auth/presentation/signup_screen.dart | 2394 +++++ .../dashboard/domain/dashboard_providers.dart | 178 + .../dashboard/domain/linked_rp_launch.dart | 27 + .../lib/features/dashboard/domain/models.dart | 237 + .../domain/providers/linked_rps_provider.dart | 112 + .../providers/user_sessions_provider.dart | 61 + .../domain/session_time_resolver.dart | 50 + .../presentation/audit_device_utils.dart | 31 + .../presentation/dashboard_screen.dart | 2455 +++++ .../data/models/user_profile_model.dart | 98 + .../data/repositories/profile_repository.dart | 177 + .../domain/notifiers/profile_notifier.dart | 51 + .../presentation/pages/profile_page.dart | 1305 +++ .../widgets/profile_info_row.dart | 36 + baron-sso/userfront/lib/i18n.dart | 18 + baron-sso/userfront/lib/i18n_data.dart | 4787 +++++++++ baron-sso/userfront/lib/main.dart | 648 ++ baron-sso/userfront/nginx.conf | 96 + baron-sso/userfront/pubspec.lock | 878 ++ baron-sso/userfront/pubspec.yaml | 106 + baron-sso/userfront/scripts/dev-server.sh | 197 + .../userfront/scripts/optimize-web-build.mjs | 364 + .../test/app_theme_default_font_test.dart | 12 + .../test/audit_device_utils_test.dart | 59 + .../test/auth_proxy_service_test.dart | 620 ++ .../test/auth_token_store_backend_test.dart | 150 + .../userfront/test/auth_token_store_test.dart | 35 + .../test/consent_error_routing_test.dart | 52 + .../test/cookie_session_policy_test.dart | 40 + .../test/dashboard_providers_test.dart | 126 + .../test/dashboard_screen_smoke_test.dart | 50 + .../dashboard_session_time_resolver_test.dart | 35 + .../test/dashboard_timeline_dedup_test.dart | 35 + .../test/english_locale_placeholder_test.dart | 106 + .../userfront/test/error_screen_test.dart | 260 + .../userfront/test/linked_rp_launch_test.dart | 105 + .../userfront/test/locale_registry_test.dart | 36 + .../test/locale_storage_platform_test.dart | 138 + .../userfront/test/locale_utils_test.dart | 158 + baron-sso/userfront/test/log_policy_test.dart | 178 + .../test/login_challenge_loop_guard_test.dart | 27 + .../test/login_challenge_resolver_test.dart | 100 + .../test/login_link_route_policy_test.dart | 75 + .../test/login_navigation_race_test.dart | 94 + .../userfront/test/logout_service_test.dart | 120 + .../test/null_check_recovery_test.dart | 63 + .../test/oidc_redirect_guard_test.dart | 62 + .../test/password_login_flow_policy_test.dart | 47 + .../profile_notifier_persistence_test.dart | 112 + .../test/profile_page_edit_flow_test.dart | 131 + .../test/qr_camera_bootstrap_policy_test.dart | 67 + .../userfront/test/qr_scan_route_test.dart | 27 + .../userfront/test/qr_scan_screen_test.dart | 16 + .../test/router_redirect_widget_test.dart | 225 + .../test/runtime_env_compile_time_test.dart | 56 + .../userfront/test/theme_controller_test.dart | 32 + .../test/toml_asset_loader_test.dart | 58 + .../test/verification_route_policy_test.dart | 85 + baron-sso/userfront/test/widget_test.dart | 10 + baron-sso/userfront/web/favicon.ico | Bin 0 -> 16958 bytes baron-sso/userfront/web/favicon.png | Bin 0 -> 1191 bytes baron-sso/userfront/web/icons/Icon-192.png | Bin 0 -> 25717 bytes baron-sso/userfront/web/icons/Icon-512.png | Bin 0 -> 90905 bytes .../userfront/web/icons/Icon-maskable-192.png | Bin 0 -> 25717 bytes .../userfront/web/icons/Icon-maskable-512.png | Bin 0 -> 90905 bytes baron-sso/userfront/web/index.html | 198 + baron-sso/userfront/web/manifest.json | 35 + 1211 files changed, 312864 insertions(+) create mode 100644 baron-sso/.dockerignore create mode 100644 baron-sso/.env.sample create mode 100644 baron-sso/.gitea/coverage.json create mode 100644 baron-sso/.gitea/workflows/backend_coverage_check.yml create mode 100644 baron-sso/.gitea/workflows/build_RC.yml create mode 100644 baron-sso/.gitea/workflows/code_check.yml create mode 100644 baron-sso/.gitea/workflows/production_release.yml create mode 100644 baron-sso/.gitea/workflows/staging_build_check.yml create mode 100644 baron-sso/.gitea/workflows/staging_code_pull.yml create mode 100644 baron-sso/.gitea/workflows/staging_release.yml create mode 100644 baron-sso/.gitea/workflows/userfront_e2e_full_nightly.yml create mode 100644 baron-sso/.gitignore create mode 100644 baron-sso/Makefile create mode 100644 baron-sso/README.md create mode 100644 baron-sso/README_en.md create mode 100644 baron-sso/adminfront/.gitignore create mode 100644 baron-sso/adminfront/Dockerfile create mode 100644 baron-sso/adminfront/README.md create mode 100644 baron-sso/adminfront/biome.json create mode 100644 baron-sso/adminfront/index.html create mode 100644 baron-sso/adminfront/package-lock.json create mode 100644 baron-sso/adminfront/package.json create mode 100644 baron-sso/adminfront/playwright.config.ts create mode 100644 baron-sso/adminfront/pnpm-lock.yaml create mode 100644 baron-sso/adminfront/postcss.config.js create mode 100644 baron-sso/adminfront/public/LINE_WORKS_Appicon.svg create mode 100644 baron-sso/adminfront/public/vite.svg create mode 100644 baron-sso/adminfront/scripts/runtime-mode.sh create mode 100644 baron-sso/adminfront/scripts/serve-prod.mjs create mode 100644 baron-sso/adminfront/seed-tenant.csv create mode 100644 baron-sso/adminfront/src/app/queryClient.ts create mode 100644 baron-sso/adminfront/src/app/routes.test.tsx create mode 100644 baron-sso/adminfront/src/app/routes.tsx create mode 100644 baron-sso/adminfront/src/assets/react.svg create mode 100644 baron-sso/adminfront/src/components/auth/RoleGuard.tsx create mode 100644 baron-sso/adminfront/src/components/common/LanguageSelector.test.tsx create mode 100644 baron-sso/adminfront/src/components/common/LanguageSelector.tsx create mode 100644 baron-sso/adminfront/src/components/common/LocaleRefreshBoundary.test.tsx create mode 100644 baron-sso/adminfront/src/components/common/LocaleRefreshBoundary.tsx create mode 100644 baron-sso/adminfront/src/components/layout/AppLayout.test.tsx create mode 100644 baron-sso/adminfront/src/components/layout/AppLayout.tsx create mode 100644 baron-sso/adminfront/src/components/ui/avatar.test.tsx create mode 100644 baron-sso/adminfront/src/components/ui/avatar.tsx create mode 100644 baron-sso/adminfront/src/components/ui/badge.test.tsx create mode 100644 baron-sso/adminfront/src/components/ui/badge.tsx create mode 100644 baron-sso/adminfront/src/components/ui/button.test.tsx create mode 100644 baron-sso/adminfront/src/components/ui/button.tsx create mode 100644 baron-sso/adminfront/src/components/ui/card.test.tsx create mode 100644 baron-sso/adminfront/src/components/ui/card.tsx create mode 100644 baron-sso/adminfront/src/components/ui/checkbox.test.tsx create mode 100644 baron-sso/adminfront/src/components/ui/checkbox.tsx create mode 100644 baron-sso/adminfront/src/components/ui/dialog.focus-scope.test.tsx create mode 100644 baron-sso/adminfront/src/components/ui/dialog.tsx create mode 100644 baron-sso/adminfront/src/components/ui/dropdown-menu.tsx create mode 100644 baron-sso/adminfront/src/components/ui/input.test.tsx create mode 100644 baron-sso/adminfront/src/components/ui/input.tsx create mode 100644 baron-sso/adminfront/src/components/ui/label.test.tsx create mode 100644 baron-sso/adminfront/src/components/ui/label.tsx create mode 100644 baron-sso/adminfront/src/components/ui/scroll-area.tsx create mode 100644 baron-sso/adminfront/src/components/ui/select.tsx create mode 100644 baron-sso/adminfront/src/components/ui/separator.test.tsx create mode 100644 baron-sso/adminfront/src/components/ui/separator.tsx create mode 100644 baron-sso/adminfront/src/components/ui/switch.tsx create mode 100644 baron-sso/adminfront/src/components/ui/table.tsx create mode 100644 baron-sso/adminfront/src/components/ui/tabs.tsx create mode 100644 baron-sso/adminfront/src/components/ui/textarea.test.tsx create mode 100644 baron-sso/adminfront/src/components/ui/textarea.tsx create mode 100644 baron-sso/adminfront/src/components/ui/toaster.tsx create mode 100644 baron-sso/adminfront/src/components/ui/use-toast.ts create mode 100644 baron-sso/adminfront/src/features/api-keys/ApiKeyCreatePage.test.tsx create mode 100644 baron-sso/adminfront/src/features/api-keys/ApiKeyCreatePage.tsx create mode 100644 baron-sso/adminfront/src/features/api-keys/ApiKeyListPage.test.tsx create mode 100644 baron-sso/adminfront/src/features/api-keys/ApiKeyListPage.tsx create mode 100644 baron-sso/adminfront/src/features/api-keys/apiKeyScopes.ts create mode 100644 baron-sso/adminfront/src/features/audit/AuditLogsPage.tsx create mode 100644 baron-sso/adminfront/src/features/audit/VirtualizedAuditLogTable.tsx create mode 100644 baron-sso/adminfront/src/features/auth/AuthCallbackPage.tsx create mode 100644 baron-sso/adminfront/src/features/auth/AuthGuard.test.tsx create mode 100644 baron-sso/adminfront/src/features/auth/AuthGuard.tsx create mode 100644 baron-sso/adminfront/src/features/auth/AuthPage.test.tsx create mode 100644 baron-sso/adminfront/src/features/auth/AuthPage.tsx create mode 100644 baron-sso/adminfront/src/features/auth/LoginPage.test.tsx create mode 100644 baron-sso/adminfront/src/features/auth/LoginPage.tsx create mode 100644 baron-sso/adminfront/src/features/auth/components/PermissionChecker.tsx create mode 100644 baron-sso/adminfront/src/features/coverage/adminAuditAuth.test.tsx create mode 100644 baron-sso/adminfront/src/features/coverage/adminLargePages.test.tsx create mode 100644 baron-sso/adminfront/src/features/coverage/adminTenantDetailPages.test.tsx create mode 100644 baron-sso/adminfront/src/features/coverage/adminTenantGroupsPage.test.tsx create mode 100644 baron-sso/adminfront/src/features/coverage/adminTenantTabs.test.tsx create mode 100644 baron-sso/adminfront/src/features/integrity/DataIntegrityPage.test.tsx create mode 100644 baron-sso/adminfront/src/features/integrity/DataIntegrityPage.tsx create mode 100644 baron-sso/adminfront/src/features/overview/GlobalOverviewPage.test.tsx create mode 100644 baron-sso/adminfront/src/features/overview/GlobalOverviewPage.tsx create mode 100644 baron-sso/adminfront/src/features/projections/UserProjectionPage.test.tsx create mode 100644 baron-sso/adminfront/src/features/projections/UserProjectionPage.tsx create mode 100644 baron-sso/adminfront/src/features/tenants/components/DomainTagInput.test.tsx create mode 100644 baron-sso/adminfront/src/features/tenants/components/DomainTagInput.tsx create mode 100644 baron-sso/adminfront/src/features/tenants/components/ParentTenantSelector.helpers.ts create mode 100644 baron-sso/adminfront/src/features/tenants/components/ParentTenantSelector.picker.test.tsx create mode 100644 baron-sso/adminfront/src/features/tenants/components/ParentTenantSelector.test.ts create mode 100644 baron-sso/adminfront/src/features/tenants/components/ParentTenantSelector.tsx create mode 100644 baron-sso/adminfront/src/features/tenants/routes/TenantAdminsAndOwnersTab.tsx create mode 100644 baron-sso/adminfront/src/features/tenants/routes/TenantCreatePage.tsx create mode 100644 baron-sso/adminfront/src/features/tenants/routes/TenantDetailPage.tsx create mode 100644 baron-sso/adminfront/src/features/tenants/routes/TenantDetailPage.worksmobile.test.tsx create mode 100644 baron-sso/adminfront/src/features/tenants/routes/TenantGroupsPage.tsx create mode 100644 baron-sso/adminfront/src/features/tenants/routes/TenantListPage.test.ts create mode 100644 baron-sso/adminfront/src/features/tenants/routes/TenantListPage.tsx create mode 100644 baron-sso/adminfront/src/features/tenants/routes/TenantProfilePage.tsx create mode 100644 baron-sso/adminfront/src/features/tenants/routes/TenantSchemaPage.test.ts create mode 100644 baron-sso/adminfront/src/features/tenants/routes/TenantSchemaPage.tsx create mode 100644 baron-sso/adminfront/src/features/tenants/routes/TenantSubTenantsPage.tsx create mode 100644 baron-sso/adminfront/src/features/tenants/routes/TenantUsersPage.export.test.tsx create mode 100644 baron-sso/adminfront/src/features/tenants/routes/TenantUsersPage.tsx create mode 100644 baron-sso/adminfront/src/features/tenants/routes/TenantWorksmobilePage.test.ts create mode 100644 baron-sso/adminfront/src/features/tenants/routes/TenantWorksmobilePage.tsx create mode 100644 baron-sso/adminfront/src/features/tenants/routes/tenantListView.ts create mode 100644 baron-sso/adminfront/src/features/tenants/routes/tenantSchemaFields.ts create mode 100644 baron-sso/adminfront/src/features/tenants/routes/worksmobileAccess.test.ts create mode 100644 baron-sso/adminfront/src/features/tenants/routes/worksmobileAccess.ts create mode 100644 baron-sso/adminfront/src/features/tenants/routes/worksmobileComparison.ts create mode 100644 baron-sso/adminfront/src/features/tenants/utils/domainTags.test.ts create mode 100644 baron-sso/adminfront/src/features/tenants/utils/domainTags.ts create mode 100644 baron-sso/adminfront/src/features/tenants/utils/orgConfig.test.ts create mode 100644 baron-sso/adminfront/src/features/tenants/utils/orgConfig.ts create mode 100644 baron-sso/adminfront/src/features/tenants/utils/protectedTenants.test.ts create mode 100644 baron-sso/adminfront/src/features/tenants/utils/protectedTenants.ts create mode 100644 baron-sso/adminfront/src/features/tenants/utils/tenantCsvImport.test.ts create mode 100644 baron-sso/adminfront/src/features/tenants/utils/tenantCsvImport.ts create mode 100644 baron-sso/adminfront/src/features/user-groups/routes/GlobalUserGroupListPage.tsx create mode 100644 baron-sso/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx create mode 100644 baron-sso/adminfront/src/features/user-groups/routes/UserGroupDetailPage.tsx create mode 100644 baron-sso/adminfront/src/features/users/GlobalCustomClaimsPage.tsx create mode 100644 baron-sso/adminfront/src/features/users/UserCreatePage.tsx create mode 100644 baron-sso/adminfront/src/features/users/UserDetailPage.employeeNumber.test.tsx create mode 100644 baron-sso/adminfront/src/features/users/UserDetailPage.tsx create mode 100644 baron-sso/adminfront/src/features/users/UserListPage.render.test.tsx create mode 100644 baron-sso/adminfront/src/features/users/UserListPage.tsx create mode 100644 baron-sso/adminfront/src/features/users/components/UserBulkMoveGroupModal.tsx create mode 100644 baron-sso/adminfront/src/features/users/components/UserBulkUploadModal.tsx create mode 100644 baron-sso/adminfront/src/features/users/orgChartPicker.test.ts create mode 100644 baron-sso/adminfront/src/features/users/orgChartPicker.ts create mode 100644 baron-sso/adminfront/src/features/users/userSchemaFields.ts create mode 100644 baron-sso/adminfront/src/features/users/userStatus.test.ts create mode 100644 baron-sso/adminfront/src/features/users/userStatus.ts create mode 100644 baron-sso/adminfront/src/features/users/utils/csvParser.test.ts create mode 100644 baron-sso/adminfront/src/features/users/utils/csvParser.ts create mode 100644 baron-sso/adminfront/src/features/users/utils/generalPlanningOfficePriority.test.ts create mode 100644 baron-sso/adminfront/src/features/users/utils/generalPlanningOfficePriority.ts create mode 100644 baron-sso/adminfront/src/features/users/utils/hanmacImportEmail.test.ts create mode 100644 baron-sso/adminfront/src/features/users/utils/hanmacImportEmail.ts create mode 100644 baron-sso/adminfront/src/features/users/utils/personalTenant.test.ts create mode 100644 baron-sso/adminfront/src/features/users/utils/personalTenant.ts create mode 100644 baron-sso/adminfront/src/index.css create mode 100644 baron-sso/adminfront/src/lib/adminApi.contract.test.ts create mode 100644 baron-sso/adminfront/src/lib/adminApi.test.ts create mode 100644 baron-sso/adminfront/src/lib/adminApi.ts create mode 100644 baron-sso/adminfront/src/lib/apiClient.ts create mode 100644 baron-sso/adminfront/src/lib/auth.ts create mode 100644 baron-sso/adminfront/src/lib/authConfig.test.ts create mode 100644 baron-sso/adminfront/src/lib/authConfig.ts create mode 100644 baron-sso/adminfront/src/lib/cursorFetch.test.ts create mode 100644 baron-sso/adminfront/src/lib/debugLog.ts create mode 100644 baron-sso/adminfront/src/lib/i18n.test.ts create mode 100644 baron-sso/adminfront/src/lib/i18n.ts create mode 100644 baron-sso/adminfront/src/lib/locale.ts create mode 100644 baron-sso/adminfront/src/lib/loginRedirectGuard.test.ts create mode 100644 baron-sso/adminfront/src/lib/roles.test.ts create mode 100644 baron-sso/adminfront/src/lib/roles.ts create mode 100644 baron-sso/adminfront/src/lib/sessionSliding.test.ts create mode 100644 baron-sso/adminfront/src/lib/sessionSliding.ts create mode 100644 baron-sso/adminfront/src/lib/sort.test.ts create mode 100644 baron-sso/adminfront/src/lib/tenantTree.test.ts create mode 100644 baron-sso/adminfront/src/lib/tenantTree.ts create mode 100644 baron-sso/adminfront/src/lib/utils.test.ts create mode 100644 baron-sso/adminfront/src/lib/utils.ts create mode 100644 baron-sso/adminfront/src/locales/en.toml create mode 100644 baron-sso/adminfront/src/locales/ko.toml create mode 100644 baron-sso/adminfront/src/locales/template.toml create mode 100644 baron-sso/adminfront/src/main.tsx create mode 100644 baron-sso/adminfront/src/test/formFieldDiagnostics.test.ts create mode 100644 baron-sso/adminfront/src/test/formFieldDiagnostics.ts create mode 100644 baron-sso/adminfront/src/test/i18nMock.ts create mode 100644 baron-sso/adminfront/src/test/setup.ts create mode 100644 baron-sso/adminfront/tailwind.config.ts create mode 100644 baron-sso/adminfront/tests/audit.spec.ts create mode 100644 baron-sso/adminfront/tests/auth.spec.ts create mode 100644 baron-sso/adminfront/tests/bulk_actions.spec.ts create mode 100644 baron-sso/adminfront/tests/data_integrity.spec.ts create mode 100644 baron-sso/adminfront/tests/owners.spec.ts create mode 100644 baron-sso/adminfront/tests/security_roles.spec.ts create mode 100644 baron-sso/adminfront/tests/shell_layout.spec.ts create mode 100644 baron-sso/adminfront/tests/tenant_domains.spec.ts create mode 100644 baron-sso/adminfront/tests/tenant_schema.spec.ts create mode 100644 baron-sso/adminfront/tests/tenant_seed_protection.spec.ts create mode 100644 baron-sso/adminfront/tests/tenants.spec.ts create mode 100644 baron-sso/adminfront/tests/tenants_live.spec.ts create mode 100644 baron-sso/adminfront/tests/users.spec.ts create mode 100644 baron-sso/adminfront/tests/users_bulk.spec.ts create mode 100644 baron-sso/adminfront/tests/users_bulk_secondary.spec.ts create mode 100644 baron-sso/adminfront/tests/users_bulk_uuid.spec.ts create mode 100644 baron-sso/adminfront/tests/users_live.spec.ts create mode 100644 baron-sso/adminfront/tests/users_schema.spec.ts create mode 100644 baron-sso/adminfront/tests/worksmobile.spec.ts create mode 100644 baron-sso/adminfront/tsconfig.app.json create mode 100644 baron-sso/adminfront/tsconfig.json create mode 100644 baron-sso/adminfront/tsconfig.node.json create mode 100644 baron-sso/adminfront/vite.config.ts create mode 100644 baron-sso/adminfront/vitest.config.ts create mode 100644 baron-sso/backend/Dockerfile create mode 100644 baron-sso/backend/check_aaa2.go create mode 100644 baron-sso/backend/cmd/adminctl/main.go create mode 100644 baron-sso/backend/cmd/adminctl/main_test.go create mode 100644 baron-sso/backend/cmd/adminctl/worksmobile_sync.go create mode 100644 baron-sso/backend/cmd/adminctl/worksmobile_sync_test.go create mode 100644 baron-sso/backend/cmd/fix_kratos_roles.go create mode 100644 baron-sso/backend/cmd/keto_test/main.go create mode 100644 baron-sso/backend/cmd/keygen/main.go create mode 100644 baron-sso/backend/cmd/server/error_handler.go create mode 100644 baron-sso/backend/cmd/server/error_handler_test.go create mode 100644 baron-sso/backend/cmd/server/headless_login_e2e_test.go create mode 100644 baron-sso/backend/cmd/server/health_monitor.go create mode 100644 baron-sso/backend/cmd/server/main.go create mode 100644 baron-sso/backend/cmd/server/openapi_static_test.go create mode 100644 baron-sso/backend/cmd/server/worksmobile_config_test.go create mode 100644 baron-sso/backend/docs/openapi.yaml create mode 100644 baron-sso/backend/docs/redoc/index.html create mode 100644 baron-sso/backend/docs/redoc/redoc.standalone.js create mode 100644 baron-sso/backend/docs/swagger-ui/index.html create mode 100644 baron-sso/backend/docs/swagger-ui/swagger-ui-bundle.js create mode 100644 baron-sso/backend/docs/swagger-ui/swagger-ui-standalone-preset.js create mode 100644 baron-sso/backend/docs/swagger-ui/swagger-ui.css create mode 100644 baron-sso/backend/go.mod create mode 100644 baron-sso/backend/go.sum create mode 100644 baron-sso/backend/internal/bootstrap/admin_account.go create mode 100644 baron-sso/backend/internal/bootstrap/admin_account_test.go create mode 100644 baron-sso/backend/internal/bootstrap/bootstrap.go create mode 100644 baron-sso/backend/internal/bootstrap/keto_sync.go create mode 100644 baron-sso/backend/internal/bootstrap/kratos_seed.go create mode 100644 baron-sso/backend/internal/bootstrap/sync_admin.go create mode 100644 baron-sso/backend/internal/bootstrap/tenant_seed.go create mode 100644 baron-sso/backend/internal/bootstrap/tenant_seed_test.go create mode 100644 baron-sso/backend/internal/bootstrap/user_metadata_sanitize.go create mode 100644 baron-sso/backend/internal/bootstrap/user_metadata_sanitize_test.go create mode 100644 baron-sso/backend/internal/domain/api_key.go create mode 100644 baron-sso/backend/internal/domain/auth_models.go create mode 100644 baron-sso/backend/internal/domain/client_consent.go create mode 100644 baron-sso/backend/internal/domain/client_secret.go create mode 100644 baron-sso/backend/internal/domain/data_integrity.go create mode 100644 baron-sso/backend/internal/domain/developer_request.go create mode 100644 baron-sso/backend/internal/domain/email_models.go create mode 100644 baron-sso/backend/internal/domain/federation_models.go create mode 100644 baron-sso/backend/internal/domain/hanmac_email.go create mode 100644 baron-sso/backend/internal/domain/hanmac_email_test.go create mode 100644 baron-sso/backend/internal/domain/headless_jwks_cache.go create mode 100644 baron-sso/backend/internal/domain/hydra_models.go create mode 100644 baron-sso/backend/internal/domain/hydra_models_test.go create mode 100644 baron-sso/backend/internal/domain/identity_cache.go create mode 100644 baron-sso/backend/internal/domain/idp_models.go create mode 100644 baron-sso/backend/internal/domain/json_map.go create mode 100644 baron-sso/backend/internal/domain/json_map_test.go create mode 100644 baron-sso/backend/internal/domain/keto_outbox.go create mode 100644 baron-sso/backend/internal/domain/model_hooks_test.go create mode 100644 baron-sso/backend/internal/domain/models.go create mode 100644 baron-sso/backend/internal/domain/oathkeeper_models.go create mode 100644 baron-sso/backend/internal/domain/relying_party.go create mode 100644 baron-sso/backend/internal/domain/rp_usage_event.go create mode 100644 baron-sso/backend/internal/domain/rp_user_metadata.go create mode 100644 baron-sso/backend/internal/domain/shared_link.go create mode 100644 baron-sso/backend/internal/domain/shared_link_test.go create mode 100644 baron-sso/backend/internal/domain/sms_models.go create mode 100644 baron-sso/backend/internal/domain/system_setting.go create mode 100644 baron-sso/backend/internal/domain/tenant.go create mode 100644 baron-sso/backend/internal/domain/tenant_domain.go create mode 100644 baron-sso/backend/internal/domain/user.go create mode 100644 baron-sso/backend/internal/domain/user_group.go create mode 100644 baron-sso/backend/internal/domain/user_projection.go create mode 100644 baron-sso/backend/internal/domain/user_test.go create mode 100644 baron-sso/backend/internal/domain/user_validate_test.go create mode 100644 baron-sso/backend/internal/domain/worksmobile.go create mode 100644 baron-sso/backend/internal/handler/admin_handler.go create mode 100644 baron-sso/backend/internal/handler/admin_handler_test.go create mode 100644 baron-sso/backend/internal/handler/admin_integrity_test.go create mode 100644 baron-sso/backend/internal/handler/api_key_handler.go create mode 100644 baron-sso/backend/internal/handler/api_key_handler_test.go create mode 100644 baron-sso/backend/internal/handler/audit_handler.go create mode 100644 baron-sso/backend/internal/handler/auth_handler.go create mode 100644 baron-sso/backend/internal/handler/auth_handler_async_test.go create mode 100644 baron-sso/backend/internal/handler/auth_handler_client_test.go create mode 100644 baron-sso/backend/internal/handler/auth_handler_consent_test.go create mode 100644 baron-sso/backend/internal/handler/auth_handler_dynamic_claims_test.go create mode 100644 baron-sso/backend/internal/handler/auth_handler_link_test.go create mode 100644 baron-sso/backend/internal/handler/auth_handler_linked_test.go create mode 100644 baron-sso/backend/internal/handler/auth_handler_login_code_test.go create mode 100644 baron-sso/backend/internal/handler/auth_handler_login_test.go create mode 100644 baron-sso/backend/internal/handler/auth_handler_oidc_test.go create mode 100644 baron-sso/backend/internal/handler/auth_handler_otp_test.go create mode 100644 baron-sso/backend/internal/handler/auth_handler_profile_cache_test.go create mode 100644 baron-sso/backend/internal/handler/auth_handler_qr_test.go create mode 100644 baron-sso/backend/internal/handler/auth_handler_session_profile_test.go create mode 100644 baron-sso/backend/internal/handler/auth_handler_sessions_test.go create mode 100644 baron-sso/backend/internal/handler/auth_handler_signup_test.go create mode 100644 baron-sso/backend/internal/handler/auth_handler_test.go create mode 100644 baron-sso/backend/internal/handler/client_tenant_access.go create mode 100644 baron-sso/backend/internal/handler/client_tenant_access_test.go create mode 100644 baron-sso/backend/internal/handler/common_test.go create mode 100644 baron-sso/backend/internal/handler/dev_handler.go create mode 100644 baron-sso/backend/internal/handler/dev_handler_isolation_test.go create mode 100644 baron-sso/backend/internal/handler/dev_handler_rp_metadata_test.go create mode 100644 baron-sso/backend/internal/handler/dev_handler_test.go create mode 100644 baron-sso/backend/internal/handler/error_helper.go create mode 100644 baron-sso/backend/internal/handler/federation_handler.go create mode 100644 baron-sso/backend/internal/handler/hanmac_email_policy.go create mode 100644 baron-sso/backend/internal/handler/password_policy_test.go create mode 100644 baron-sso/backend/internal/handler/relying_party_handler.go create mode 100644 baron-sso/backend/internal/handler/rp_manifest_handler.go create mode 100644 baron-sso/backend/internal/handler/rp_manifest_handler_test.go create mode 100644 baron-sso/backend/internal/handler/tenant_access_cleanup.go create mode 100644 baron-sso/backend/internal/handler/tenant_access_cleanup_test.go create mode 100644 baron-sso/backend/internal/handler/tenant_assignment_policy.go create mode 100644 baron-sso/backend/internal/handler/tenant_handler.go create mode 100644 baron-sso/backend/internal/handler/tenant_handler_seed_delete_test.go create mode 100644 baron-sso/backend/internal/handler/tenant_handler_test.go create mode 100644 baron-sso/backend/internal/handler/user_group_handler.go create mode 100644 baron-sso/backend/internal/handler/user_group_handler_test.go create mode 100644 baron-sso/backend/internal/handler/user_handler.go create mode 100644 baron-sso/backend/internal/handler/user_handler_test.go create mode 100644 baron-sso/backend/internal/handler/user_handler_uuid_test.go create mode 100644 baron-sso/backend/internal/handler/user_projection_failure.go create mode 100644 baron-sso/backend/internal/handler/worksmobile_handler.go create mode 100644 baron-sso/backend/internal/handler/worksmobile_handler_test.go create mode 100644 baron-sso/backend/internal/handlerregression/dev_handler_headless_secret_test.go create mode 100644 baron-sso/backend/internal/idp/factory.go create mode 100644 baron-sso/backend/internal/idp/factory_test.go create mode 100644 baron-sso/backend/internal/logger/audit_logger.go create mode 100644 baron-sso/backend/internal/logger/audit_logger_test.go create mode 100644 baron-sso/backend/internal/logger/client_log_policy.go create mode 100644 baron-sso/backend/internal/logger/client_log_policy_test.go create mode 100644 baron-sso/backend/internal/logger/logger.go create mode 100644 baron-sso/backend/internal/logger/logger_test.go create mode 100644 baron-sso/backend/internal/middleware/api_key_auth.go create mode 100644 baron-sso/backend/internal/middleware/api_key_auth_test.go create mode 100644 baron-sso/backend/internal/middleware/audit_middleware.go create mode 100644 baron-sso/backend/internal/middleware/audit_middleware_test.go create mode 100644 baron-sso/backend/internal/middleware/error_code_enricher.go create mode 100644 baron-sso/backend/internal/middleware/error_code_enricher_test.go create mode 100644 baron-sso/backend/internal/middleware/error_helper.go create mode 100644 baron-sso/backend/internal/middleware/rbac.go create mode 100644 baron-sso/backend/internal/middleware/rbac_test.go create mode 100644 baron-sso/backend/internal/middleware/tenant_middleware.go create mode 100644 baron-sso/backend/internal/middleware/tenant_middleware_test.go create mode 100644 baron-sso/backend/internal/pagination/cursor.go create mode 100644 baron-sso/backend/internal/pagination/cursor_test.go create mode 100644 baron-sso/backend/internal/repository/clickhouse_repo.go create mode 100644 baron-sso/backend/internal/repository/client_consent_repository.go create mode 100644 baron-sso/backend/internal/repository/client_consent_repository_test.go create mode 100644 baron-sso/backend/internal/repository/client_secret_repository.go create mode 100644 baron-sso/backend/internal/repository/data_integrity_repository.go create mode 100644 baron-sso/backend/internal/repository/data_integrity_repository_test.go create mode 100644 baron-sso/backend/internal/repository/federation_repository.go create mode 100644 baron-sso/backend/internal/repository/gorm_federation_repository.go create mode 100644 baron-sso/backend/internal/repository/keto_outbox_repository.go create mode 100644 baron-sso/backend/internal/repository/keto_outbox_repository_test.go create mode 100644 baron-sso/backend/internal/repository/main_test.go create mode 100644 baron-sso/backend/internal/repository/oathkeeper_clickhouse_repo.go create mode 100644 baron-sso/backend/internal/repository/relying_party_repository.go create mode 100644 baron-sso/backend/internal/repository/rp_usage_outbox_repository.go create mode 100644 baron-sso/backend/internal/repository/rp_user_metadata_repository.go create mode 100644 baron-sso/backend/internal/repository/shared_link_repository.go create mode 100644 baron-sso/backend/internal/repository/tenant_repository.go create mode 100644 baron-sso/backend/internal/repository/tenant_repository_test.go create mode 100644 baron-sso/backend/internal/repository/user_group_repository.go create mode 100644 baron-sso/backend/internal/repository/user_membership_maintenance.go create mode 100644 baron-sso/backend/internal/repository/user_membership_maintenance_test.go create mode 100644 baron-sso/backend/internal/repository/user_projection_repository.go create mode 100644 baron-sso/backend/internal/repository/user_projection_repository_test.go create mode 100644 baron-sso/backend/internal/repository/user_repository.go create mode 100644 baron-sso/backend/internal/repository/user_repository_test.go create mode 100644 baron-sso/backend/internal/repository/worksmobile_outbox_repository.go create mode 100644 baron-sso/backend/internal/repository/worksmobile_outbox_repository_test.go create mode 100644 baron-sso/backend/internal/response/error_response.go create mode 100644 baron-sso/backend/internal/response/error_response_test.go create mode 100644 baron-sso/backend/internal/service/backchannel_logout_service.go create mode 100644 baron-sso/backend/internal/service/backchannel_logout_service_test.go create mode 100644 baron-sso/backend/internal/service/developer_service.go create mode 100644 baron-sso/backend/internal/service/dry_run_service.go create mode 100644 baron-sso/backend/internal/service/dry_run_service_test.go create mode 100644 baron-sso/backend/internal/service/federation_service.go create mode 100644 baron-sso/backend/internal/service/headless_jwks_cache.go create mode 100644 baron-sso/backend/internal/service/headless_jwks_cache_test.go create mode 100644 baron-sso/backend/internal/service/hydra_admin_service.go create mode 100644 baron-sso/backend/internal/service/hydra_admin_service_test.go create mode 100644 baron-sso/backend/internal/service/keto_relay_worker.go create mode 100644 baron-sso/backend/internal/service/keto_service.go create mode 100644 baron-sso/backend/internal/service/keto_service_test.go create mode 100644 baron-sso/backend/internal/service/kratos_admin_service.go create mode 100644 baron-sso/backend/internal/service/kratos_admin_service_test.go create mode 100644 baron-sso/backend/internal/service/mock_common_test.go create mode 100644 baron-sso/backend/internal/service/ory_service.go create mode 100644 baron-sso/backend/internal/service/ory_service_test.go create mode 100644 baron-sso/backend/internal/service/redis_service.go create mode 100644 baron-sso/backend/internal/service/redis_service_test.go create mode 100644 baron-sso/backend/internal/service/relying_party_service.go create mode 100644 baron-sso/backend/internal/service/relying_party_service_test.go create mode 100644 baron-sso/backend/internal/service/rp_usage_event_emitter.go create mode 100644 baron-sso/backend/internal/service/rp_usage_event_emitter_test.go create mode 100644 baron-sso/backend/internal/service/rp_usage_projector_worker.go create mode 100644 baron-sso/backend/internal/service/ses_service.go create mode 100644 baron-sso/backend/internal/service/shared_link_service.go create mode 100644 baron-sso/backend/internal/service/sms_service.go create mode 100644 baron-sso/backend/internal/service/sms_service_test.go create mode 100644 baron-sso/backend/internal/service/tenant_service.go create mode 100644 baron-sso/backend/internal/service/tenant_service_edge_test.go create mode 100644 baron-sso/backend/internal/service/tenant_service_test.go create mode 100644 baron-sso/backend/internal/service/user_group_service.go create mode 100644 baron-sso/backend/internal/service/user_group_service_edge_test.go create mode 100644 baron-sso/backend/internal/service/user_group_service_test.go create mode 100644 baron-sso/backend/internal/service/user_projection_sync_service.go create mode 100644 baron-sso/backend/internal/service/user_projection_sync_service_test.go create mode 100644 baron-sso/backend/internal/service/worksmobile_client.go create mode 100644 baron-sso/backend/internal/service/worksmobile_client_test.go create mode 100644 baron-sso/backend/internal/service/worksmobile_live_flow_test.go create mode 100644 baron-sso/backend/internal/service/worksmobile_mapper.go create mode 100644 baron-sso/backend/internal/service/worksmobile_mapper_test.go create mode 100644 baron-sso/backend/internal/service/worksmobile_relay_leader_lock.go create mode 100644 baron-sso/backend/internal/service/worksmobile_relay_worker.go create mode 100644 baron-sso/backend/internal/service/worksmobile_sync_service.go create mode 100644 baron-sso/backend/internal/service/worksmobile_sync_service_test.go create mode 100644 baron-sso/backend/internal/testsupport/env.go create mode 100644 baron-sso/backend/internal/utils/audit.go create mode 100644 baron-sso/backend/internal/utils/audit_test.go create mode 100644 baron-sso/backend/internal/utils/client_ip.go create mode 100644 baron-sso/backend/internal/utils/client_ip_test.go create mode 100644 baron-sso/backend/internal/utils/masking.go create mode 100644 baron-sso/backend/internal/utils/masking_test.go create mode 100644 baron-sso/backend/internal/utils/password_policy.go create mode 100644 baron-sso/backend/internal/utils/password_policy_test.go create mode 100644 baron-sso/backend/internal/utils/slug.go create mode 100644 baron-sso/backend/internal/utils/slug_test.go create mode 100644 baron-sso/backend/internal/validator/schema_validator.go create mode 100644 baron-sso/backend/internal/validator/schema_validator_test.go create mode 100644 baron-sso/backend/seed-tenant.csv create mode 100644 baron-sso/backups/pre-saman-works-users-20260608-063605Z/checksums.sha256 create mode 100644 baron-sso/backups/pre-saman-works-users-20260608-063605Z/manifest.json create mode 100644 baron-sso/backups/pre-saman-works-users-20260608-063605Z/postgres/baron.dump create mode 100644 baron-sso/backups/pre-saman-works-users-20260608-063605Z/postgres/globals.sql create mode 100644 baron-sso/backups/pre-saman-works-users-20260608-063605Z/postgres/ory_hydra.dump create mode 100644 baron-sso/backups/pre-saman-works-users-20260608-063605Z/postgres/ory_keto.dump create mode 100644 baron-sso/backups/pre-saman-works-users-20260608-063605Z/postgres/ory_kratos.dump create mode 100644 baron-sso/backups/pre-works-only-user-recovery-20260609-083105KST/checksums.sha256 create mode 100644 baron-sso/backups/pre-works-only-user-recovery-20260609-083105KST/manifest.json create mode 100644 baron-sso/backups/pre-works-only-user-recovery-20260609-083105KST/postgres/baron.dump create mode 100644 baron-sso/backups/pre-works-only-user-recovery-20260609-083105KST/postgres/globals.sql create mode 100644 baron-sso/backups/pre-works-only-user-recovery-20260609-083105KST/postgres/ory_hydra.dump create mode 100644 baron-sso/backups/pre-works-only-user-recovery-20260609-083105KST/postgres/ory_keto.dump create mode 100644 baron-sso/backups/pre-works-only-user-recovery-20260609-083105KST/postgres/ory_kratos.dump create mode 100644 baron-sso/common/.npmrc create mode 100644 baron-sso/common/biome.json create mode 100644 baron-sso/common/config/biome.base.json create mode 100644 baron-sso/common/config/vite.base.ts create mode 100644 baron-sso/common/core/audit/index.ts create mode 100644 baron-sso/common/core/auth/index.ts create mode 100644 baron-sso/common/core/components/audit/AuditLogTable.test.tsx create mode 100644 baron-sso/common/core/components/audit/AuditLogTable.tsx create mode 100644 baron-sso/common/core/components/audit/index.ts create mode 100644 baron-sso/common/core/components/overview/OverviewAxisNotes.tsx create mode 100644 baron-sso/common/core/components/overview/OverviewMetric.tsx create mode 100644 baron-sso/common/core/components/overview/OverviewSelectionChips.tsx create mode 100644 baron-sso/common/core/components/overview/index.ts create mode 100644 baron-sso/common/core/components/page/PageHeader.tsx create mode 100644 baron-sso/common/core/components/page/index.ts create mode 100644 baron-sso/common/core/components/sort/SortableTableHead.tsx create mode 100644 baron-sso/common/core/components/sort/index.ts create mode 100644 baron-sso/common/core/i18n/index.ts create mode 100644 baron-sso/common/core/i18n/loader.ts create mode 100644 baron-sso/common/core/i18n/types.ts create mode 100644 baron-sso/common/core/pagination/cursorFetch.ts create mode 100644 baron-sso/common/core/pagination/cursorFetch.worker.ts create mode 100644 baron-sso/common/core/pagination/cursorFetchCore.ts create mode 100644 baron-sso/common/core/pagination/index.ts create mode 100644 baron-sso/common/core/query/queryClient.ts create mode 100644 baron-sso/common/core/session/index.ts create mode 100644 baron-sso/common/core/utils/index.ts create mode 100644 baron-sso/common/core/utils/sort.ts create mode 100644 baron-sso/common/locales/en.toml create mode 100644 baron-sso/common/locales/ko.toml create mode 100644 baron-sso/common/locales/template.toml create mode 100644 baron-sso/common/package-lock.json create mode 100644 baron-sso/common/package.json create mode 100644 baron-sso/common/pnpm-lock.yaml create mode 100644 baron-sso/common/shell/AppSidebar.tsx create mode 100644 baron-sso/common/shell/index.ts create mode 100644 baron-sso/common/shell/layout.ts create mode 100644 baron-sso/common/theme/base.css create mode 100644 baron-sso/common/theme/tailwind.preset.ts create mode 100644 baron-sso/common/tsconfig.base.json create mode 100644 baron-sso/common/ui/badge.ts create mode 100644 baron-sso/common/ui/button.ts create mode 100644 baron-sso/common/ui/card.ts create mode 100644 baron-sso/common/ui/input.ts create mode 100644 baron-sso/common/ui/search-filter-bar.tsx create mode 100644 baron-sso/common/ui/table.ts create mode 100644 baron-sso/compose.infra.yaml create mode 100644 baron-sso/compose.ory.yaml create mode 100644 baron-sso/config-restored/compose/compose.infra.yaml create mode 100644 baron-sso/config-restored/compose/compose.ory.yaml create mode 100644 baron-sso/config-restored/compose/docker-compose.yaml create mode 100644 baron-sso/config-restored/env.redacted create mode 100644 baron-sso/config-restored/gateway.tar.zst create mode 100644 baron-sso/config-restored/generated-ory.tar.zst create mode 100644 baron-sso/config/.gitkeep create mode 100644 baron-sso/deploy/create-instance.sh create mode 100644 baron-sso/deploy/deploy_guide.md create mode 100644 baron-sso/deploy/templates/.env.template create mode 100644 baron-sso/deploy/templates/adminfront/vite.config.ts create mode 100644 baron-sso/deploy/templates/auth.template.ts create mode 100644 baron-sso/deploy/templates/devfront/vite.config.ts create mode 100644 baron-sso/deploy/templates/docker-compose.yaml create mode 100644 baron-sso/deploy/templates/gateway/nginx.conf create mode 100644 baron-sso/deploy/templates/orgfront/auth.ts create mode 100644 baron-sso/deploy/templates/orgfront/vite.config.ts create mode 100644 baron-sso/deploy/templates/ory/kratos/kratos.yml.template create mode 100644 baron-sso/deploy/templates/ory/oathkeeper/rules.json create mode 100644 baron-sso/deploy/templates/userfront/nginx.conf create mode 100644 baron-sso/devfront/.gitignore create mode 100644 baron-sso/devfront/Dockerfile create mode 100644 baron-sso/devfront/README.md create mode 100644 baron-sso/devfront/biome.json create mode 100644 baron-sso/devfront/e2e-evidence/tenant-access-allowed-tenant-added.png create mode 100644 baron-sso/devfront/e2e-evidence/tenant-access-allowed-tenant-deleted.png create mode 100644 baron-sso/devfront/hydra-rp-dummy.py create mode 100644 baron-sso/devfront/index.html create mode 100644 baron-sso/devfront/package-lock.json create mode 100644 baron-sso/devfront/package.json create mode 100644 baron-sso/devfront/playwright.config.ts create mode 100644 baron-sso/devfront/pnpm-lock.yaml create mode 100644 baron-sso/devfront/postcss.config.js create mode 100644 baron-sso/devfront/public/vite.svg create mode 100644 baron-sso/devfront/scripts/runtime-mode.sh create mode 100644 baron-sso/devfront/src/app/queryClient.ts create mode 100644 baron-sso/devfront/src/app/routes.test.tsx create mode 100644 baron-sso/devfront/src/app/routes.tsx create mode 100644 baron-sso/devfront/src/assets/react.svg create mode 100644 baron-sso/devfront/src/components/common/DeveloperAccessRequestCard.test.tsx create mode 100644 baron-sso/devfront/src/components/common/DeveloperAccessRequestCard.tsx create mode 100644 baron-sso/devfront/src/components/common/ForbiddenMessage.test.tsx create mode 100644 baron-sso/devfront/src/components/common/ForbiddenMessage.tsx create mode 100644 baron-sso/devfront/src/components/common/LanguageSelector.test.tsx create mode 100644 baron-sso/devfront/src/components/common/LanguageSelector.tsx create mode 100644 baron-sso/devfront/src/components/layout/AppLayout.test.tsx create mode 100644 baron-sso/devfront/src/components/layout/AppLayout.tsx create mode 100644 baron-sso/devfront/src/components/ui/avatar.tsx create mode 100644 baron-sso/devfront/src/components/ui/badge.tsx create mode 100644 baron-sso/devfront/src/components/ui/button.tsx create mode 100644 baron-sso/devfront/src/components/ui/card.tsx create mode 100644 baron-sso/devfront/src/components/ui/copy-button.test.tsx create mode 100644 baron-sso/devfront/src/components/ui/copy-button.tsx create mode 100644 baron-sso/devfront/src/components/ui/input.tsx create mode 100644 baron-sso/devfront/src/components/ui/label.tsx create mode 100644 baron-sso/devfront/src/components/ui/scroll-area.tsx create mode 100644 baron-sso/devfront/src/components/ui/separator.tsx create mode 100644 baron-sso/devfront/src/components/ui/switch.tsx create mode 100644 baron-sso/devfront/src/components/ui/table.tsx create mode 100644 baron-sso/devfront/src/components/ui/textarea.tsx create mode 100644 baron-sso/devfront/src/components/ui/toaster.tsx create mode 100644 baron-sso/devfront/src/components/ui/use-toast.ts create mode 100644 baron-sso/devfront/src/features/audit/AuditLogsPage.test.tsx create mode 100644 baron-sso/devfront/src/features/audit/AuditLogsPage.tsx create mode 100644 baron-sso/devfront/src/features/auth/AuthCallbackPage.tsx create mode 100644 baron-sso/devfront/src/features/auth/AuthGuard.tsx create mode 100644 baron-sso/devfront/src/features/auth/AuthPage.tsx create mode 100644 baron-sso/devfront/src/features/auth/LoginPage.tsx create mode 100644 baron-sso/devfront/src/features/auth/authApi.test.ts create mode 100644 baron-sso/devfront/src/features/auth/authApi.ts create mode 100644 baron-sso/devfront/src/features/auth/authPages.test.tsx create mode 100644 baron-sso/devfront/src/features/clients/ClientConsentsPage.tsx create mode 100644 baron-sso/devfront/src/features/clients/ClientDetailTabs.test.tsx create mode 100644 baron-sso/devfront/src/features/clients/ClientDetailTabs.tsx create mode 100644 baron-sso/devfront/src/features/clients/ClientDetailsPage.tsx create mode 100644 baron-sso/devfront/src/features/clients/ClientGeneralPage.tsx create mode 100644 baron-sso/devfront/src/features/clients/ClientRelationsPage.tsx create mode 100644 baron-sso/devfront/src/features/clients/ClientsPage.test.tsx create mode 100644 baron-sso/devfront/src/features/clients/ClientsPage.tsx create mode 100644 baron-sso/devfront/src/features/clients/clientCreateAccess.test.ts create mode 100644 baron-sso/devfront/src/features/clients/clientCreateAccess.ts create mode 100644 baron-sso/devfront/src/features/clients/clientSecretPolicy.test.ts create mode 100644 baron-sso/devfront/src/features/clients/clientSecretPolicy.ts create mode 100644 baron-sso/devfront/src/features/clients/components/AllowedTenantBadge.test.tsx create mode 100644 baron-sso/devfront/src/features/clients/components/AllowedTenantBadge.tsx create mode 100644 baron-sso/devfront/src/features/clients/components/ClientLogo.test.tsx create mode 100644 baron-sso/devfront/src/features/clients/components/ClientLogo.tsx create mode 100644 baron-sso/devfront/src/features/clients/routes/ClientFederationPage.test.tsx create mode 100644 baron-sso/devfront/src/features/clients/routes/ClientFederationPage.tsx create mode 100644 baron-sso/devfront/src/features/coverage/AuditLogTable.test.tsx create mode 100644 baron-sso/devfront/src/features/coverage/commonAudit.test.ts create mode 100644 baron-sso/devfront/src/features/coverage/commonAuth.test.ts create mode 100644 baron-sso/devfront/src/features/coverage/commonSort.test.ts create mode 100644 baron-sso/devfront/src/features/coverage/pageSmoke.test.tsx create mode 100644 baron-sso/devfront/src/features/developer-access/developerAccessGate.test.ts create mode 100644 baron-sso/devfront/src/features/developer-access/developerAccessGate.ts create mode 100644 baron-sso/devfront/src/features/developer-request/DeveloperRequestPage.test.tsx create mode 100644 baron-sso/devfront/src/features/developer-request/DeveloperRequestPage.tsx create mode 100644 baron-sso/devfront/src/features/overview/GlobalOverviewPage.tsx create mode 100644 baron-sso/devfront/src/features/overview/recentClientChanges.test.ts create mode 100644 baron-sso/devfront/src/features/overview/recentClientChanges.ts create mode 100644 baron-sso/devfront/src/features/profile/ProfilePage.tsx create mode 100644 baron-sso/devfront/src/features/profile/ProfileTenantSwitcher.tsx create mode 100644 baron-sso/devfront/src/index.css create mode 100644 baron-sso/devfront/src/lib/apiClient.test.ts create mode 100644 baron-sso/devfront/src/lib/apiClient.ts create mode 100644 baron-sso/devfront/src/lib/auth.ts create mode 100644 baron-sso/devfront/src/lib/authConfig.test.ts create mode 100644 baron-sso/devfront/src/lib/authConfig.ts create mode 100644 baron-sso/devfront/src/lib/devApi.test.ts create mode 100644 baron-sso/devfront/src/lib/devApi.ts create mode 100644 baron-sso/devfront/src/lib/i18n.ts create mode 100644 baron-sso/devfront/src/lib/oidcStorage.test.ts create mode 100644 baron-sso/devfront/src/lib/oidcStorage.ts create mode 100644 baron-sso/devfront/src/lib/role.test.ts create mode 100644 baron-sso/devfront/src/lib/role.ts create mode 100644 baron-sso/devfront/src/lib/sessionSliding.ts create mode 100644 baron-sso/devfront/src/lib/utils.ts create mode 100644 baron-sso/devfront/src/locales/en.toml create mode 100644 baron-sso/devfront/src/locales/ko.toml create mode 100644 baron-sso/devfront/src/locales/template.toml create mode 100644 baron-sso/devfront/src/main.tsx create mode 100644 baron-sso/devfront/tailwind.config.ts create mode 100644 baron-sso/devfront/tests/clients.spec.ts create mode 100644 baron-sso/devfront/tests/devfront-audit.spec.ts create mode 100644 baron-sso/devfront/tests/devfront-client-tabs.spec.ts create mode 100644 baron-sso/devfront/tests/devfront-client-tenant-access.spec.ts create mode 100644 baron-sso/devfront/tests/devfront-clients-lifecycle.spec.ts create mode 100644 baron-sso/devfront/tests/devfront-consents.spec.ts create mode 100644 baron-sso/devfront/tests/devfront-developer-request.spec.ts create mode 100644 baron-sso/devfront/tests/devfront-login.spec.ts create mode 100644 baron-sso/devfront/tests/devfront-relationships.spec.ts create mode 100644 baron-sso/devfront/tests/devfront-role-switch-report.spec.ts create mode 100644 baron-sso/devfront/tests/devfront-security.spec.ts create mode 100644 baron-sso/devfront/tests/devfront-tenant-switch.spec.ts create mode 100644 baron-sso/devfront/tests/example.spec.ts create mode 100644 baron-sso/devfront/tests/helpers/devfront-fixtures.ts create mode 100644 baron-sso/devfront/tests/helpers/evidence.ts create mode 100644 baron-sso/devfront/tsconfig.app.json create mode 100644 baron-sso/devfront/tsconfig.json create mode 100644 baron-sso/devfront/tsconfig.node.json create mode 100644 baron-sso/devfront/vite.config.ts create mode 100644 baron-sso/devfront/vitest.config.ts create mode 100644 baron-sso/docker-compose.yaml create mode 100644 baron-sso/docker/README.md create mode 100644 baron-sso/docker/backup-tools/Dockerfile create mode 100644 baron-sso/docker/compose.infra.prd.yaml create mode 100644 baron-sso/docker/compose.infra.yaml create mode 100644 baron-sso/docker/compose.ory.yaml create mode 100644 baron-sso/docker/docker-compose.staging.template.yaml create mode 100644 baron-sso/docker/docker-compose.template.yaml create mode 100644 baron-sso/docker/init-metadata/01_init_metadata.sql create mode 100644 baron-sso/docker/monitor/blackbox.yml create mode 100644 baron-sso/docker/monitor/grafana/dashboards/baron_sso_dashboard.json create mode 100644 baron-sso/docker/ory/clickhouse/init.sql create mode 100644 baron-sso/docker/ory/hydra/hydra.yml.template create mode 100644 baron-sso/docker/ory/init-db/01_create_dbs.sh create mode 100644 baron-sso/docker/ory/keto/keto.yml.template create mode 100644 baron-sso/docker/ory/keto/namespaces.ts create mode 100644 baron-sso/docker/ory/keto/namespaces.yml create mode 100644 baron-sso/docker/ory/kratos/courier-http.jsonnet create mode 100644 baron-sso/docker/ory/kratos/courier-templates/login_code/valid/email.body.gotmpl create mode 100644 baron-sso/docker/ory/kratos/courier-templates/login_code/valid/email.body.plaintext.gotmpl create mode 100644 baron-sso/docker/ory/kratos/courier-templates/login_code/valid/email.subject.gotmpl create mode 100644 baron-sso/docker/ory/kratos/courier-templates/login_code/valid/sms.body.gotmpl create mode 100644 baron-sso/docker/ory/kratos/identity.schema.json create mode 100644 baron-sso/docker/ory/kratos/kratos.yml.template create mode 100644 baron-sso/docker/ory/oathkeeper/entrypoint.sh create mode 100644 baron-sso/docker/ory/oathkeeper/oathkeeper.yml.template create mode 100644 baron-sso/docker/ory/oathkeeper/rules.active.json create mode 100644 baron-sso/docker/ory/oathkeeper/rules.draft.json create mode 100644 baron-sso/docker/ory/oathkeeper/rules.json create mode 100644 baron-sso/docker/ory/oathkeeper/rules.prod.json create mode 100644 baron-sso/docker/ory/oathkeeper/rules.stage.json create mode 100644 baron-sso/docker/ory/vector/vector.toml create mode 100644 baron-sso/docker/promtail-config.template.yaml create mode 100644 baron-sso/docker/staging_pull_compose.template.yaml create mode 100644 baron-sso/docs/AGENTS.md create mode 100644 baron-sso/docs/API_DESIGN_POLICY.md create mode 100644 baron-sso/docs/Gemini.md create mode 100644 baron-sso/docs/SoT_Architecture_Policy.md create mode 100644 baron-sso/docs/TEST_GUIDE.md create mode 100644 baron-sso/docs/UI_DESIGN_POLICY.md create mode 100644 baron-sso/docs/auto-login-policy-p0-mobile-installed-webapp.md create mode 100644 baron-sso/docs/b2b2b_dynamic_provisioning_flow.md create mode 100644 baron-sso/docs/backend-log-policy.md create mode 100644 baron-sso/docs/backup-restore-design.md create mode 100644 baron-sso/docs/badges/adminfront.svg create mode 100644 baron-sso/docs/badges/backend-tests.svg create mode 100644 baron-sso/docs/badges/badges.json create mode 100644 baron-sso/docs/badges/biome.svg create mode 100644 baron-sso/docs/badges/code-check.svg create mode 100644 baron-sso/docs/badges/dev-sha.svg create mode 100644 baron-sso/docs/badges/devfront.svg create mode 100644 baron-sso/docs/badges/orgfront.svg create mode 100644 baron-sso/docs/badges/userfront-chrome.svg create mode 100644 baron-sso/docs/badges/userfront-firefox.svg create mode 100644 baron-sso/docs/badges/userfront-safari.svg create mode 100644 baron-sso/docs/badges/userfront.svg create mode 100644 baron-sso/docs/baron-orgchart-data-flow.md create mode 100644 baron-sso/docs/client-log-policy.md create mode 100644 baron-sso/docs/client_deactivation_flow.md create mode 100644 baron-sso/docs/complete-password-reset-refactor.md create mode 100644 baron-sso/docs/compose-ory.md create mode 100644 baron-sso/docs/consent_flow_explanation.md create mode 100644 baron-sso/docs/consent_listing_flow.md create mode 100644 baron-sso/docs/consent_loop_fix_report.md create mode 100644 baron-sso/docs/consent_reject_flow.md create mode 100644 baron-sso/docs/consent_revoke_implementation.md create mode 100644 baron-sso/docs/consent_scope_selection_flow.md create mode 100644 baron-sso/docs/custom-field-jsonb-index-policy.md create mode 100644 baron-sso/docs/data-integrity-management.md create mode 100644 baron-sso/docs/devfront-rp-relationships-guide.md create mode 100644 baron-sso/docs/devfront_auth_flow_explanation.md create mode 100644 baron-sso/docs/employee_id_login_db_design.md create mode 100644 baron-sso/docs/employee_id_login_design.md create mode 100644 baron-sso/docs/external_healthcheck_monitoring_design.md create mode 100644 baron-sso/docs/frontend-monorepo-masterplan.md create mode 100644 baron-sso/docs/frontend_hydra_testing_guide.md create mode 100644 baron-sso/docs/hydra-rp-dummy.md create mode 100644 baron-sso/docs/hydra_backend_test_guide.md create mode 100644 baron-sso/docs/hydra_be_test_guide.md create mode 100644 baron-sso/docs/i18n.md create mode 100644 baron-sso/docs/identity-redis-mirror-policy-2026-06-09.md create mode 100644 baron-sso/docs/initial_PRD.md create mode 100644 baron-sso/docs/integrations-org-context-json-api.md create mode 100644 baron-sso/docs/keto-rebac-namespaces-diagram.md create mode 100644 baron-sso/docs/keto-rebac-policy-guide.md create mode 100644 baron-sso/docs/keto-tuple-samples.md create mode 100644 baron-sso/docs/kratos-user-traits-field-inventory.md create mode 100644 baron-sso/docs/oidc_redirect_mapping_validation_policy.md create mode 100644 baron-sso/docs/organization-chart-policy.md create mode 100644 baron-sso/docs/ory-stack-guide.md create mode 100644 baron-sso/docs/ory-usage.md create mode 100644 baron-sso/docs/pkce-backchannel-logout-guide.md create mode 100644 baron-sso/docs/rbac-rebac-policy.md create mode 100644 baron-sso/docs/rp-auto-login-guide.md create mode 100644 baron-sso/docs/rp-iam-integration-guide.md create mode 100644 baron-sso/docs/rp_activity_ux_expansion_flow.md create mode 100644 baron-sso/docs/rp_redirection_implementation.md create mode 100644 baron-sso/docs/server-side-app-backchannel-logout-guide.md create mode 100644 baron-sso/docs/snapshots/worksmobile-change-log-2026-06-02.png create mode 100644 baron-sso/docs/staging-502-restart-policy-incident-2026-06-08.md create mode 100644 baron-sso/docs/staging-deployment-flow.md create mode 100644 baron-sso/docs/superpowers/plans/2026-03-30-headless-password-login-backend-api.md create mode 100644 baron-sso/docs/superpowers/plans/2026-04-01-headless-login-debug-improvement.md create mode 100644 baron-sso/docs/superpowers/specs/2026-04-01-headless-login-debug-design.md create mode 100644 baron-sso/docs/tenant-maintenance-procedures.md create mode 100644 baron-sso/docs/tenant-policy.md create mode 100644 baron-sso/docs/tenant-usergroup-policy.md create mode 100644 baron-sso/docs/tenant-visibility-exposure-policy.md create mode 100644 baron-sso/docs/test-plan/backend-test-inventory.md create mode 100644 baron-sso/docs/test-plan/userfront-wasm-e2e-expansion-plan.md create mode 100644 baron-sso/docs/test-plan/web-e2e-test-inventory.md create mode 100644 baron-sso/docs/trouble-shooting/dev-branch-conflict-policy.md create mode 100644 baron-sso/docs/trouble-shooting/hydra-rp-consent-try.md create mode 100644 baron-sso/docs/trouble-shooting/issue-146-remote-login.md create mode 100644 baron-sso/docs/trouble-shooting/issue-269-locale-query-loss.md create mode 100644 baron-sso/docs/trouble-shooting/issue-269-test-scenarios.md create mode 100644 baron-sso/docs/trouble-shooting/issue-277-null-check-dashboard-routing.md create mode 100644 baron-sso/docs/trouble-shooting/issue-281-locale-storage-refactor-plan.md create mode 100644 baron-sso/docs/trouble-shooting/issue-303-login-link-shortcode-locale-route.md create mode 100644 baron-sso/docs/trouble-shooting/issue-434-dashboard-session-start-time.md create mode 100644 baron-sso/docs/trouble-shooting/issue-614-skip-consent.md create mode 100644 baron-sso/docs/trouble-shooting/issue-663-hanmac-family-orgfront-sync.md create mode 100644 baron-sso/docs/trouble-shooting/issue_489_completion_summary.md create mode 100644 baron-sso/docs/trouble-shooting/userfront-locale.md create mode 100644 baron-sso/docs/user-group-rebac-architecture.md create mode 100644 baron-sso/docs/user-projection-visibility-audit-2026-06-08.md create mode 100644 baron-sso/docs/userfront_error_handling_policy.md create mode 100644 baron-sso/docs/wiki-error-handling-policy-backend-log-update.md create mode 100644 baron-sso/docs/works-only-user-recovery-2026-06-09.md create mode 100644 baron-sso/docs/worksmobile-api-rate-limit-policy-2026-06-09.md create mode 100644 baron-sso/docs/worksmobile-directory-sync-technical-review.md create mode 100644 baron-sso/docs/worksmobile-halla-domain-migration-plan.md create mode 100644 baron-sso/docs/개발완료보고서.md create mode 100644 baron-sso/fix_toml.py create mode 100644 baron-sso/gateway/Dockerfile create mode 100644 baron-sso/gateway/entrypoint.sh create mode 100644 baron-sso/gateway/nginx.conf create mode 100644 baron-sso/image.png create mode 100644 baron-sso/locales/en.toml create mode 100644 baron-sso/locales/ko.toml create mode 100644 baron-sso/locales/template.toml create mode 100644 baron-sso/mcp/compose.mcp.ory.yaml create mode 100644 baron-sso/mcp/hydra-mcp/Dockerfile create mode 100644 baron-sso/mcp/hydra-mcp/biome.json create mode 100644 baron-sso/mcp/hydra-mcp/package-lock.json create mode 100644 baron-sso/mcp/hydra-mcp/package.json create mode 100644 baron-sso/mcp/hydra-mcp/src/index.js create mode 100644 baron-sso/mcp/hydra-mcp/src/runner.js create mode 100644 baron-sso/mcp/keto-mcp/Dockerfile create mode 100644 baron-sso/mcp/keto-mcp/biome.json create mode 100644 baron-sso/mcp/keto-mcp/package-lock.json create mode 100644 baron-sso/mcp/keto-mcp/package.json create mode 100644 baron-sso/mcp/keto-mcp/src/index.js create mode 100644 baron-sso/mcp/keto-mcp/src/runner.js create mode 100644 baron-sso/mcp/kratos-mcp/Dockerfile create mode 100644 baron-sso/orgfront/.dockerignore create mode 100644 baron-sso/orgfront/.gitignore create mode 100644 baron-sso/orgfront/Dockerfile create mode 100644 baron-sso/orgfront/Dockerfile2 create mode 100644 baron-sso/orgfront/README.md create mode 100644 baron-sso/orgfront/biome.json create mode 100644 baron-sso/orgfront/docker-compose.yml create mode 100644 baron-sso/orgfront/docs/orgchart-embedding-token-design.md create mode 100644 baron-sso/orgfront/hydra-rp-dummy.py create mode 100644 baron-sso/orgfront/index.html create mode 100644 baron-sso/orgfront/package-lock.json create mode 100644 baron-sso/orgfront/package.json create mode 100644 baron-sso/orgfront/playwright.config.ts create mode 100644 baron-sso/orgfront/pnpm-lock.yaml create mode 100644 baron-sso/orgfront/postcss.config.js create mode 100644 baron-sso/orgfront/public/vite.svg create mode 100644 baron-sso/orgfront/scripts/build-org-context-chart.mjs create mode 100644 baron-sso/orgfront/scripts/runtime-mode.sh create mode 100644 baron-sso/orgfront/src/app/queryClient.ts create mode 100644 baron-sso/orgfront/src/app/routes.test.tsx create mode 100644 baron-sso/orgfront/src/app/routes.tsx create mode 100644 baron-sso/orgfront/src/assets/react.svg create mode 100644 baron-sso/orgfront/src/components/common/ForbiddenMessage.tsx create mode 100644 baron-sso/orgfront/src/components/common/LanguageSelector.tsx create mode 100644 baron-sso/orgfront/src/components/layout/AppLayout.test.tsx create mode 100644 baron-sso/orgfront/src/components/layout/AppLayout.tsx create mode 100644 baron-sso/orgfront/src/components/ui/avatar.tsx create mode 100644 baron-sso/orgfront/src/components/ui/badge.tsx create mode 100644 baron-sso/orgfront/src/components/ui/basic.test.tsx create mode 100644 baron-sso/orgfront/src/components/ui/button.tsx create mode 100644 baron-sso/orgfront/src/components/ui/card.tsx create mode 100644 baron-sso/orgfront/src/components/ui/copy-button.tsx create mode 100644 baron-sso/orgfront/src/components/ui/input.tsx create mode 100644 baron-sso/orgfront/src/components/ui/label.tsx create mode 100644 baron-sso/orgfront/src/components/ui/scroll-area.tsx create mode 100644 baron-sso/orgfront/src/components/ui/separator.tsx create mode 100644 baron-sso/orgfront/src/components/ui/switch.tsx create mode 100644 baron-sso/orgfront/src/components/ui/table.tsx create mode 100644 baron-sso/orgfront/src/components/ui/textarea.tsx create mode 100644 baron-sso/orgfront/src/components/ui/toaster.tsx create mode 100644 baron-sso/orgfront/src/components/ui/use-toast.ts create mode 100644 baron-sso/orgfront/src/features/audit/AuditLogsPage.tsx create mode 100644 baron-sso/orgfront/src/features/auth/AuthCallbackPage.tsx create mode 100644 baron-sso/orgfront/src/features/auth/AuthGuard.test.tsx create mode 100644 baron-sso/orgfront/src/features/auth/AuthGuard.tsx create mode 100644 baron-sso/orgfront/src/features/auth/AuthPage.tsx create mode 100644 baron-sso/orgfront/src/features/auth/LoginPage.test.tsx create mode 100644 baron-sso/orgfront/src/features/auth/LoginPage.tsx create mode 100644 baron-sso/orgfront/src/features/auth/authApi.ts create mode 100644 baron-sso/orgfront/src/features/clients/ClientConsentsPage.tsx create mode 100644 baron-sso/orgfront/src/features/clients/ClientDetailsPage.tsx create mode 100644 baron-sso/orgfront/src/features/clients/ClientGeneralPage.tsx create mode 100644 baron-sso/orgfront/src/features/clients/ClientsPage.tsx create mode 100644 baron-sso/orgfront/src/features/clients/routes/ClientFederationPage.tsx create mode 100644 baron-sso/orgfront/src/features/coverage/pageSmoke.test.tsx create mode 100644 baron-sso/orgfront/src/features/dashboard/DashboardPage.tsx create mode 100644 baron-sso/orgfront/src/features/orgchart/hanmacFamilyOrder.test.ts create mode 100644 baron-sso/orgfront/src/features/orgchart/hanmacFamilyOrder.ts create mode 100644 baron-sso/orgfront/src/features/orgchart/pickerTree.test.ts create mode 100644 baron-sso/orgfront/src/features/orgchart/pickerTree.ts create mode 100644 baron-sso/orgfront/src/features/orgchart/pickerTypes.test.ts create mode 100644 baron-sso/orgfront/src/features/orgchart/pickerTypes.ts create mode 100644 baron-sso/orgfront/src/features/orgchart/rankPriority.test.ts create mode 100644 baron-sso/orgfront/src/features/orgchart/rankPriority.ts create mode 100644 baron-sso/orgfront/src/features/orgchart/routes/OrgChartPage.test.tsx create mode 100644 baron-sso/orgfront/src/features/orgchart/routes/OrgChartPage.tsx create mode 100644 baron-sso/orgfront/src/features/orgchart/routes/OrgFrontLayout.tsx create mode 100644 baron-sso/orgfront/src/features/orgchart/routes/OrgPickerEmbedPreviewPage.tsx create mode 100644 baron-sso/orgfront/src/features/orgchart/routes/OrgPickerPage.tsx create mode 100644 baron-sso/orgfront/src/features/orgchart/tenantVisibility.ts create mode 100644 baron-sso/orgfront/src/features/orgchart/userDisplay.test.ts create mode 100644 baron-sso/orgfront/src/features/orgchart/userDisplay.ts create mode 100644 baron-sso/orgfront/src/features/profile/ProfilePage.tsx create mode 100644 baron-sso/orgfront/src/features/profile/ProfileTenantSwitcher.tsx create mode 100644 baron-sso/orgfront/src/index.css create mode 100644 baron-sso/orgfront/src/lib/adminApi.test.ts create mode 100644 baron-sso/orgfront/src/lib/adminApi.ts create mode 100644 baron-sso/orgfront/src/lib/apiClient.ts create mode 100644 baron-sso/orgfront/src/lib/auth.ts create mode 100644 baron-sso/orgfront/src/lib/authConfig.test.ts create mode 100644 baron-sso/orgfront/src/lib/authConfig.ts create mode 100644 baron-sso/orgfront/src/lib/devApi.test.ts create mode 100644 baron-sso/orgfront/src/lib/devApi.ts create mode 100644 baron-sso/orgfront/src/lib/i18n.ts create mode 100644 baron-sso/orgfront/src/lib/role.ts create mode 100644 baron-sso/orgfront/src/lib/sessionSliding.ts create mode 100644 baron-sso/orgfront/src/lib/tenantTree.test.ts create mode 100644 baron-sso/orgfront/src/lib/tenantTree.ts create mode 100644 baron-sso/orgfront/src/lib/utils.ts create mode 100644 baron-sso/orgfront/src/locales/en.toml create mode 100644 baron-sso/orgfront/src/locales/ko.toml create mode 100644 baron-sso/orgfront/src/locales/template.toml create mode 100644 baron-sso/orgfront/src/main.tsx create mode 100644 baron-sso/orgfront/src/sdk/org-context-chart/index.ts create mode 100644 baron-sso/orgfront/src/sdk/org-context-chart/orgContextChart.test.ts create mode 100644 baron-sso/orgfront/tailwind.config.ts create mode 100644 baron-sso/orgfront/tests/clients.spec.ts create mode 100644 baron-sso/orgfront/tests/devfront-audit.spec.ts create mode 100644 baron-sso/orgfront/tests/devfront-clients-lifecycle.spec.ts create mode 100644 baron-sso/orgfront/tests/devfront-consents.spec.ts create mode 100644 baron-sso/orgfront/tests/devfront-role-switch-report.spec.ts create mode 100644 baron-sso/orgfront/tests/devfront-security.spec.ts create mode 100644 baron-sso/orgfront/tests/devfront-tenant-switch.spec.ts create mode 100644 baron-sso/orgfront/tests/example.spec.ts create mode 100644 baron-sso/orgfront/tests/helpers/devfront-fixtures.ts create mode 100644 baron-sso/orgfront/tests/helpers/evidence.ts create mode 100644 baron-sso/orgfront/tests/light-theme.spec.ts create mode 100644 baron-sso/orgfront/tests/orgchart-pan-zoom.spec.ts create mode 100644 baron-sso/orgfront/tests/orgchart-picker.spec.ts create mode 100644 baron-sso/orgfront/tests/orgchart-vector-render.spec.ts create mode 100644 baron-sso/orgfront/tests/orgfront-auto-login.spec.ts create mode 100644 baron-sso/orgfront/tsconfig.app.json create mode 100644 baron-sso/orgfront/tsconfig.json create mode 100644 baron-sso/orgfront/tsconfig.node.json create mode 100644 baron-sso/orgfront/vite.config.ts create mode 100644 baron-sso/orgfront/vite.org-context-chart.config.ts create mode 100644 baron-sso/orgfront/vitest.config.ts create mode 100644 baron-sso/package.json create mode 100644 baron-sso/pnpm-lock.yaml create mode 100644 baron-sso/pnpm-workspace.yaml create mode 100644 baron-sso/saman_works_users.CSV create mode 100644 baron-sso/scripts/auth_config.sh create mode 100644 baron-sso/scripts/backup/dump-list.sh create mode 100644 baron-sso/scripts/backup/dump.sh create mode 100644 baron-sso/scripts/backup/lib/clickhouse.sh create mode 100644 baron-sso/scripts/backup/lib/common.sh create mode 100644 baron-sso/scripts/backup/lib/config.sh create mode 100644 baron-sso/scripts/backup/lib/manifest.sh create mode 100644 baron-sso/scripts/backup/lib/postgres.sh create mode 100644 baron-sso/scripts/backup/lib/report.sh create mode 100644 baron-sso/scripts/backup/restore-plan.sh create mode 100644 baron-sso/scripts/backup/restore.sh create mode 100644 baron-sso/scripts/backup/upload_cloud.sh create mode 100644 baron-sso/scripts/backup/verify-dump.sh create mode 100644 baron-sso/scripts/backup/verify-restore.sh create mode 100644 baron-sso/scripts/clear_orphan_tenant_memberships.sh create mode 100644 baron-sso/scripts/clear_orphan_user_tenant_memberships.sql create mode 100644 baron-sso/scripts/map_wasm_stack.py create mode 100644 baron-sso/scripts/playwrightHostDeps.cjs create mode 100644 baron-sso/scripts/playwrightPackageVersion.cjs create mode 100644 baron-sso/scripts/render_ory_config.sh create mode 100644 baron-sso/scripts/run_adminfront_ci_tests.sh create mode 100644 baron-sso/scripts/sanitize_baron_user_metadata.sql create mode 100644 baron-sso/scripts/serve_frontend_prod.mjs create mode 100644 baron-sso/scripts/summarize_flutter_coverage.mjs create mode 100644 baron-sso/scripts/summarize_vitest_coverage.mjs create mode 100644 baron-sso/scripts/sync_userfront_locales.sh create mode 100644 baron-sso/scripts/test_frontend_runtime_mode.sh create mode 100644 baron-sso/scripts/test_staging_workflow_env.sh create mode 100644 baron-sso/scripts/update_code_check_badges.mjs create mode 100644 baron-sso/scripts/verify_userfront_error_i18n.sh create mode 100644 baron-sso/test/adminfront_dev_performance_policy_test.sh create mode 100644 baron-sso/test/adminfront_port_env_policy_test.sh create mode 100644 baron-sso/test/adminfront_public_url_env_policy_test.sh create mode 100644 baron-sso/test/auth_config_orgfront_callback_test.sh create mode 100644 baron-sso/test/backend_go_version_policy_test.sh create mode 100644 baron-sso/test/backup_make_targets_test.sh create mode 100644 baron-sso/test/backup_scripts_policy_test.sh create mode 100644 baron-sso/test/backup_upload_cloud_policy_test.sh create mode 100644 baron-sso/test/code_check_badge_branch_policy_test.sh create mode 100644 baron-sso/test/code_check_playwright_cache_policy_test.sh create mode 100644 baron-sso/test/dev_org_front_callback_public_url_policy_test.sh create mode 100644 baron-sso/test/env_secret_file_policy_test.sh create mode 100644 baron-sso/test/frontend_dev_bind_mount_policy_test.sh create mode 100644 baron-sso/test/gateway_userfront_residue_policy_test.sh create mode 100644 baron-sso/test/kratos_identity_schema_policy_test.sh create mode 100644 baron-sso/test/kratos_identity_write_path_policy_test.sh create mode 100644 baron-sso/test/make_dev_targets_test.sh create mode 100644 baron-sso/test/oathkeeper_access_log_e2e_test.sh create mode 100644 baron-sso/test/oathkeeper_kratos_public_exposure_policy_test.sh create mode 100644 baron-sso/test/orgfront_integration_policy_test.sh create mode 100644 baron-sso/test/orgfront_org_context_chart_package_test.sh create mode 100644 baron-sso/test/ory-network-check.sh create mode 100644 baron-sso/test/ory_log_pipeline_policy_test.sh create mode 100644 baron-sso/test/ory_v26_compose_policy_test.sh create mode 100644 baron-sso/test/playwright_package_version_test.sh create mode 100644 baron-sso/test/production_image_release_policy_test.sh create mode 100644 baron-sso/test/shell_layout_policy_test.sh create mode 100644 baron-sso/test/staging_frontend_deploy_policy_test.sh create mode 100644 baron-sso/test/staging_pull_restart_policy_test.sh create mode 100644 baron-sso/test/summarize_userfront_coverage_test.sh create mode 100644 baron-sso/test/test_sms.py create mode 100644 baron-sso/test/update_code_check_badges_package_coverage_test.sh create mode 100644 baron-sso/test/update_code_check_badges_preserve_existing_test.sh create mode 100644 baron-sso/test/userfront_loading_performance_policy_test.sh create mode 100644 baron-sso/tools/i18n-scanner/gen-flutter-i18n.js create mode 100644 baron-sso/tools/i18n-scanner/index.js create mode 100644 baron-sso/tools/i18n-scanner/manual-keys.ts create mode 100644 baron-sso/tools/i18n-scanner/report.js create mode 100644 baron-sso/tools/i18n-scanner/translate-locales.js create mode 100644 baron-sso/tools/i18n-scanner/value-check.js create mode 100644 baron-sso/userfront-e2e/.gitignore create mode 100644 baron-sso/userfront-e2e/README.md create mode 100644 baron-sso/userfront-e2e/biome.json create mode 100644 baron-sso/userfront-e2e/fixtures/fonts/NotoSansKR-TestSubset.woff2 create mode 100644 baron-sso/userfront-e2e/package-lock.json create mode 100644 baron-sso/userfront-e2e/package.json create mode 100644 baron-sso/userfront-e2e/playwright.config.ts create mode 100644 baron-sso/userfront-e2e/pnpm-lock.yaml create mode 100644 baron-sso/userfront-e2e/scripts/serve-userfront-build.mjs create mode 100644 baron-sso/userfront-e2e/tests/auth-routing.spec.ts create mode 100644 baron-sso/userfront-e2e/tests/login-performance-budget.spec.ts create mode 100644 baron-sso/userfront-e2e/tests/oidc-login-challenge.spec.ts create mode 100644 baron-sso/userfront-e2e/tests/password-and-reset.spec.ts create mode 100644 baron-sso/userfront-e2e/tests/profile-department.spec.ts create mode 100644 baron-sso/userfront-e2e/tests/route-inventory.spec.ts create mode 100644 baron-sso/userfront-e2e/tests/runtime-env-mobile.spec.ts create mode 100644 baron-sso/userfront-e2e/tests/session-cross-browser-debug.spec.ts create mode 100644 baron-sso/userfront-e2e/tests/signup-theme-visibility.spec.ts create mode 100644 baron-sso/userfront-e2e/tsconfig.json create mode 100644 baron-sso/userfront/.gitignore create mode 100644 baron-sso/userfront/.metadata create mode 100644 baron-sso/userfront/Dockerfile create mode 100644 baron-sso/userfront/analysis_options.yaml create mode 100644 baron-sso/userfront/android/.gitignore create mode 100644 baron-sso/userfront/android/app/build.gradle.kts create mode 100644 baron-sso/userfront/android/app/src/debug/AndroidManifest.xml create mode 100644 baron-sso/userfront/android/app/src/main/AndroidManifest.xml create mode 100644 baron-sso/userfront/android/app/src/main/kotlin/kr/co/baroncs/frontend/MainActivity.kt create mode 100644 baron-sso/userfront/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 baron-sso/userfront/android/app/src/main/res/drawable/launch_background.xml create mode 100644 baron-sso/userfront/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 baron-sso/userfront/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 baron-sso/userfront/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 baron-sso/userfront/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 baron-sso/userfront/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 baron-sso/userfront/android/app/src/main/res/values-night/styles.xml create mode 100644 baron-sso/userfront/android/app/src/main/res/values/styles.xml create mode 100644 baron-sso/userfront/android/app/src/profile/AndroidManifest.xml create mode 100644 baron-sso/userfront/android/build.gradle.kts create mode 100644 baron-sso/userfront/android/gradle.properties create mode 100644 baron-sso/userfront/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 baron-sso/userfront/android/settings.gradle.kts create mode 100644 baron-sso/userfront/assets/baron.ico create mode 100644 baron-sso/userfront/assets/fonts/NotoSansKR-Bold.ttf create mode 100644 baron-sso/userfront/assets/fonts/NotoSansKR-Regular.ttf create mode 100644 baron-sso/userfront/assets/translations/en.toml create mode 100644 baron-sso/userfront/assets/translations/ko.toml create mode 100644 baron-sso/userfront/assets/translations/template.toml create mode 100644 baron-sso/userfront/ios/.gitignore create mode 100644 baron-sso/userfront/ios/Flutter/AppFrameworkInfo.plist create mode 100644 baron-sso/userfront/ios/Flutter/Debug.xcconfig create mode 100644 baron-sso/userfront/ios/Flutter/Release.xcconfig create mode 100644 baron-sso/userfront/ios/Runner.xcodeproj/project.pbxproj create mode 100644 baron-sso/userfront/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 baron-sso/userfront/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 baron-sso/userfront/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 baron-sso/userfront/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 baron-sso/userfront/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 baron-sso/userfront/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 baron-sso/userfront/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 baron-sso/userfront/ios/Runner/AppDelegate.swift create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 baron-sso/userfront/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 baron-sso/userfront/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 baron-sso/userfront/ios/Runner/Base.lproj/Main.storyboard create mode 100644 baron-sso/userfront/ios/Runner/Info.plist create mode 100644 baron-sso/userfront/ios/Runner/Runner-Bridging-Header.h create mode 100644 baron-sso/userfront/ios/RunnerTests/RunnerTests.swift create mode 100644 baron-sso/userfront/lib/core/constants/error_whitelist.dart create mode 100644 baron-sso/userfront/lib/core/i18n/locale_gate.dart create mode 100644 baron-sso/userfront/lib/core/i18n/locale_registry.dart create mode 100644 baron-sso/userfront/lib/core/i18n/locale_storage.dart create mode 100644 baron-sso/userfront/lib/core/i18n/locale_storage_backend.dart create mode 100644 baron-sso/userfront/lib/core/i18n/locale_storage_engine.dart create mode 100644 baron-sso/userfront/lib/core/i18n/locale_storage_policy.dart create mode 100644 baron-sso/userfront/lib/core/i18n/locale_storage_stub.dart create mode 100644 baron-sso/userfront/lib/core/i18n/locale_storage_web.dart create mode 100644 baron-sso/userfront/lib/core/i18n/locale_utils.dart create mode 100644 baron-sso/userfront/lib/core/i18n/toml_asset_loader.dart create mode 100644 baron-sso/userfront/lib/core/notifiers/auth_notifier.dart create mode 100644 baron-sso/userfront/lib/core/services/audit_service.dart create mode 100644 baron-sso/userfront/lib/core/services/auth_proxy_service.dart create mode 100644 baron-sso/userfront/lib/core/services/auth_token_store.dart create mode 100644 baron-sso/userfront/lib/core/services/auth_token_store_backend.dart create mode 100644 baron-sso/userfront/lib/core/services/auth_token_store_stub.dart create mode 100644 baron-sso/userfront/lib/core/services/auth_token_store_web.dart create mode 100644 baron-sso/userfront/lib/core/services/http_client.dart create mode 100644 baron-sso/userfront/lib/core/services/http_client_stub.dart create mode 100644 baron-sso/userfront/lib/core/services/http_client_web.dart create mode 100644 baron-sso/userfront/lib/core/services/log_policy.dart create mode 100644 baron-sso/userfront/lib/core/services/logger_service.dart create mode 100644 baron-sso/userfront/lib/core/services/login_challenge_loop_guard.dart create mode 100644 baron-sso/userfront/lib/core/services/login_challenge_loop_guard_base.dart create mode 100644 baron-sso/userfront/lib/core/services/login_challenge_loop_guard_stub.dart create mode 100644 baron-sso/userfront/lib/core/services/login_challenge_loop_guard_web.dart create mode 100644 baron-sso/userfront/lib/core/services/logout_service.dart create mode 100644 baron-sso/userfront/lib/core/services/null_check_recovery.dart create mode 100644 baron-sso/userfront/lib/core/services/oidc_redirect_guard.dart create mode 100644 baron-sso/userfront/lib/core/services/runtime_env.dart create mode 100644 baron-sso/userfront/lib/core/services/web_auth_integration.dart create mode 100644 baron-sso/userfront/lib/core/services/web_auth_integration_stub.dart create mode 100644 baron-sso/userfront/lib/core/services/web_auth_integration_web.dart create mode 100644 baron-sso/userfront/lib/core/services/web_window.dart create mode 100644 baron-sso/userfront/lib/core/services/web_window_stub.dart create mode 100644 baron-sso/userfront/lib/core/services/web_window_web.dart create mode 100644 baron-sso/userfront/lib/core/theme/app_theme.dart create mode 100644 baron-sso/userfront/lib/core/theme/theme_controller.dart create mode 100644 baron-sso/userfront/lib/core/theme/theme_scope.dart create mode 100644 baron-sso/userfront/lib/core/ui/layout_breakpoints.dart create mode 100644 baron-sso/userfront/lib/core/ui/toast_service.dart create mode 100644 baron-sso/userfront/lib/core/widgets/language_selector.dart create mode 100644 baron-sso/userfront/lib/core/widgets/theme_toggle_button.dart create mode 100644 baron-sso/userfront/lib/features/admin/presentation/create_user_screen.dart create mode 100644 baron-sso/userfront/lib/features/admin/presentation/user_management_screen.dart create mode 100644 baron-sso/userfront/lib/features/auth/domain/consent_error_routing.dart create mode 100644 baron-sso/userfront/lib/features/auth/domain/cookie_session_policy.dart create mode 100644 baron-sso/userfront/lib/features/auth/domain/login_challenge_resolver.dart create mode 100644 baron-sso/userfront/lib/features/auth/domain/login_link_route_policy.dart create mode 100644 baron-sso/userfront/lib/features/auth/domain/password_login_flow_policy.dart create mode 100644 baron-sso/userfront/lib/features/auth/domain/verification_completion_route.dart create mode 100644 baron-sso/userfront/lib/features/auth/presentation/approve_qr_screen.dart create mode 100644 baron-sso/userfront/lib/features/auth/presentation/consent_screen.dart create mode 100644 baron-sso/userfront/lib/features/auth/presentation/error_screen.dart create mode 100644 baron-sso/userfront/lib/features/auth/presentation/forgot_password_screen.dart create mode 100644 baron-sso/userfront/lib/features/auth/presentation/login_screen.dart create mode 100644 baron-sso/userfront/lib/features/auth/presentation/login_success_screen.dart create mode 100644 baron-sso/userfront/lib/features/auth/presentation/qr_camera_bootstrap_policy.dart create mode 100644 baron-sso/userfront/lib/features/auth/presentation/qr_scan_route.dart create mode 100644 baron-sso/userfront/lib/features/auth/presentation/qr_scan_screen.dart create mode 100644 baron-sso/userfront/lib/features/auth/presentation/qr_scan_screen_stub.dart create mode 100644 baron-sso/userfront/lib/features/auth/presentation/qr_scan_screen_web.dart create mode 100644 baron-sso/userfront/lib/features/auth/presentation/reset_password_screen.dart create mode 100644 baron-sso/userfront/lib/features/auth/presentation/signup_screen.dart create mode 100644 baron-sso/userfront/lib/features/dashboard/domain/dashboard_providers.dart create mode 100644 baron-sso/userfront/lib/features/dashboard/domain/linked_rp_launch.dart create mode 100644 baron-sso/userfront/lib/features/dashboard/domain/models.dart create mode 100644 baron-sso/userfront/lib/features/dashboard/domain/providers/linked_rps_provider.dart create mode 100644 baron-sso/userfront/lib/features/dashboard/domain/providers/user_sessions_provider.dart create mode 100644 baron-sso/userfront/lib/features/dashboard/domain/session_time_resolver.dart create mode 100644 baron-sso/userfront/lib/features/dashboard/presentation/audit_device_utils.dart create mode 100644 baron-sso/userfront/lib/features/dashboard/presentation/dashboard_screen.dart create mode 100644 baron-sso/userfront/lib/features/profile/data/models/user_profile_model.dart create mode 100644 baron-sso/userfront/lib/features/profile/data/repositories/profile_repository.dart create mode 100644 baron-sso/userfront/lib/features/profile/domain/notifiers/profile_notifier.dart create mode 100644 baron-sso/userfront/lib/features/profile/presentation/pages/profile_page.dart create mode 100644 baron-sso/userfront/lib/features/profile/presentation/widgets/profile_info_row.dart create mode 100644 baron-sso/userfront/lib/i18n.dart create mode 100644 baron-sso/userfront/lib/i18n_data.dart create mode 100644 baron-sso/userfront/lib/main.dart create mode 100644 baron-sso/userfront/nginx.conf create mode 100644 baron-sso/userfront/pubspec.lock create mode 100644 baron-sso/userfront/pubspec.yaml create mode 100644 baron-sso/userfront/scripts/dev-server.sh create mode 100644 baron-sso/userfront/scripts/optimize-web-build.mjs create mode 100644 baron-sso/userfront/test/app_theme_default_font_test.dart create mode 100644 baron-sso/userfront/test/audit_device_utils_test.dart create mode 100644 baron-sso/userfront/test/auth_proxy_service_test.dart create mode 100644 baron-sso/userfront/test/auth_token_store_backend_test.dart create mode 100644 baron-sso/userfront/test/auth_token_store_test.dart create mode 100644 baron-sso/userfront/test/consent_error_routing_test.dart create mode 100644 baron-sso/userfront/test/cookie_session_policy_test.dart create mode 100644 baron-sso/userfront/test/dashboard_providers_test.dart create mode 100644 baron-sso/userfront/test/dashboard_screen_smoke_test.dart create mode 100644 baron-sso/userfront/test/dashboard_session_time_resolver_test.dart create mode 100644 baron-sso/userfront/test/dashboard_timeline_dedup_test.dart create mode 100644 baron-sso/userfront/test/english_locale_placeholder_test.dart create mode 100644 baron-sso/userfront/test/error_screen_test.dart create mode 100644 baron-sso/userfront/test/linked_rp_launch_test.dart create mode 100644 baron-sso/userfront/test/locale_registry_test.dart create mode 100644 baron-sso/userfront/test/locale_storage_platform_test.dart create mode 100644 baron-sso/userfront/test/locale_utils_test.dart create mode 100644 baron-sso/userfront/test/log_policy_test.dart create mode 100644 baron-sso/userfront/test/login_challenge_loop_guard_test.dart create mode 100644 baron-sso/userfront/test/login_challenge_resolver_test.dart create mode 100644 baron-sso/userfront/test/login_link_route_policy_test.dart create mode 100644 baron-sso/userfront/test/login_navigation_race_test.dart create mode 100644 baron-sso/userfront/test/logout_service_test.dart create mode 100644 baron-sso/userfront/test/null_check_recovery_test.dart create mode 100644 baron-sso/userfront/test/oidc_redirect_guard_test.dart create mode 100644 baron-sso/userfront/test/password_login_flow_policy_test.dart create mode 100644 baron-sso/userfront/test/profile_notifier_persistence_test.dart create mode 100644 baron-sso/userfront/test/profile_page_edit_flow_test.dart create mode 100644 baron-sso/userfront/test/qr_camera_bootstrap_policy_test.dart create mode 100644 baron-sso/userfront/test/qr_scan_route_test.dart create mode 100644 baron-sso/userfront/test/qr_scan_screen_test.dart create mode 100644 baron-sso/userfront/test/router_redirect_widget_test.dart create mode 100644 baron-sso/userfront/test/runtime_env_compile_time_test.dart create mode 100644 baron-sso/userfront/test/theme_controller_test.dart create mode 100644 baron-sso/userfront/test/toml_asset_loader_test.dart create mode 100644 baron-sso/userfront/test/verification_route_policy_test.dart create mode 100644 baron-sso/userfront/test/widget_test.dart create mode 100644 baron-sso/userfront/web/favicon.ico create mode 100644 baron-sso/userfront/web/favicon.png create mode 100644 baron-sso/userfront/web/icons/Icon-192.png create mode 100644 baron-sso/userfront/web/icons/Icon-512.png create mode 100644 baron-sso/userfront/web/icons/Icon-maskable-192.png create mode 100644 baron-sso/userfront/web/icons/Icon-maskable-512.png create mode 100644 baron-sso/userfront/web/index.html create mode 100644 baron-sso/userfront/web/manifest.json diff --git a/baron-sso/.dockerignore b/baron-sso/.dockerignore new file mode 100644 index 0000000..bb1551d --- /dev/null +++ b/baron-sso/.dockerignore @@ -0,0 +1,19 @@ +.git +.gitea +.codex +.env +.env.* +**/.dart_tool +**/.packages +**/build +**/node_modules +**/dist +**/.next +**/.cache +**/coverage +**/tmp +**/logs +**/*.log +**/*.swp +**/.DS_Store +**/.pnpm-store diff --git a/baron-sso/.env.sample b/baron-sso/.env.sample new file mode 100644 index 0000000..af98d23 --- /dev/null +++ b/baron-sso/.env.sample @@ -0,0 +1,186 @@ +# ========================================== +# Baron SSO - Unified Environment Configuration +# ========================================== + +# --- General System --- +APP_ENV=stage # 애플리케이션 실행 환경 (dev, stage, production) +TZ=Asia/Seoul + + +IDP_PROVIDER=ory + +# --- Infrastructure Ports --- +DB_PORT=5432 +CLICKHOUSE_PORT_HTTP=8123 +CLICKHOUSE_PORT_NATIVE=9000 +BACKEND_PORT=3000 +ADMINFRONT_PORT=5173 +DEVFRONT_PORT=5174 +USERFRONT_PORT=5000 + +# --- Database Credentials (PostgreSQL) --- +DB_USER=baron +DB_PASSWORD=password +DB_NAME=baron_sso + +# --- Backend Configuration --- +# Must be 32 bytes. Generate with `openssl rand -hex 32` +COOKIE_SECRET=super-secret-key-must-be-32-bytes! +JWT_SECRET=super-secret-key-must-be-32-bytes! +# Optional backend slog override: debug, info, warn, error +BACKEND_LOG_LEVEL= +REDIS_ADDR=redis:6389 # compose.infra.yaml의 redis 포트(컨테이너 내부 기준) +CORS_ALLOWED_ORIGINS=http://localhost:5000 # 쿠키 인증 사용 시 정확한 Origin 지정 필요 + +# --- NAVER WORKS API --- +WORKS_ADMIN_API_BASE_URL=https://www.worksapis.com +WORKS_ADMIN_OAUTH_TOKEN_URL=https://auth.worksmobile.com/oauth2/v2.0/token + +# --- NAVER WORKS Drive backup upload --- +# Drive API 업로드에는 `file` scope가 필요합니다. +# 운영에서는 Drive 권한이 위임된 사용자/OAuth access token을 우선 사용하세요. +# 서비스 계정 JWT 방식은 WORKS 앱 정책에서 Drive API scope 위임이 허용된 경우에만 사용할 수 있습니다. +WORKS_DRIVE_TARGET=sharedrive +WORKS_DRIVE_SHARED_DRIVE_ID= +WORKS_DRIVE_PARENT_FILE_ID= +WORKS_DRIVE_USER_ID=me +WORKS_DRIVE_GROUP_ID= +WORKS_DRIVE_SHARED_FOLDER_ID= +WORKS_DRIVE_ACCESS_TOKEN= +WORKS_DRIVE_ACCESS_TOKEN_FILE= +WORKS_DRIVE_ACCESS_TOKEN_CMD= +WORKS_DRIVE_OAUTH_SCOPE=file +WORKS_DRIVE_OAUTH_CLIENT_ID= +WORKS_DRIVE_OAUTH_CLIENT_SECRET= +WORKS_DRIVE_OAUTH_CLIENT_SERVICE_ACCOUNT= +WORKS_DRIVE_OAUTH_CLIENT_PRIVATE_KEY_FILE=./config/worksmobile-driveapp-private-key.pem +WORKS_DRIVE_OAUTH_REFRESH_TOKEN= +WORKS_DRIVE_OAUTH_REDIRECT_URI= +WORKS_DRIVE_SPLIT_SIZE=9000M +WORKS_DRIVE_MAX_SINGLE_FILE_BYTES=0 +WORKS_DRIVE_FORCE_SPLIT=false +WORKS_DRIVE_OVERWRITE=false +WORKS_DRIVE_DRY_RUN=false +WORKS_DRIVE_UPLOAD_REPORTS=true +WORKS_DRIVE_REPORT_FOLDER_NAME=reports + + +# Audit System Configuration +AUDIT_WORKER_COUNT=5 # 비동기 감사 로그 처리를 위한 고루틴 워커 수 +AUDIT_QUEUE_SIZE=2000 # 감사 로그 대기열(채널) 버퍼 크기 + +# Redis Cache Configuration +PROFILE_CACHE_TTL=30m # User Profile Redis 캐시 만료 시간 + +# --- Naver Cloud Services --- +NAVER_CLOUD_ACCESS_KEY=ncp_iam_... +NAVER_CLOUD_SECRET_KEY=ncp_iam_... +NAVER_CLOUD_SERVICE_ID=ncp:sms:kr:...:... +NAVER_SENDER_PHONE_NUMBER=... + + # --- AWS SES (이메일 발송용) --- +AWS_REGION=ap-northeast-2 +AWS_ACCESS_KEY_ID=... +AWS_SECRET_ACCESS_KEY=... +AWS_SES_SENDER=no-reply@baron.co.kr + +# --- 관리자 page pw --- +ADMIN_EMAIL=admin@baron.co.kr +ADMIN_PASSWORD=adminPasswordIsNotSimple + +# --- URLs for Proxy/Handoff --- +# Project Public Base URL (Served by UserFront Nginx) +USERFRONT_URL=https://sso.hmac.kr + +# Services proxied via Nginx +BACKEND_PUBLIC_URL=${USERFRONT_URL} +BACKEND_URL=${USERFRONT_URL} +OATHKEEPER_PUBLIC_URL=${USERFRONT_URL} + +# ory-stack 변수들 +ORY_POSTGRES_TAG=17-trixie +ORY_POSTGRES_USER=ory +ORY_POSTGRES_PASSWORD=EuBV5ywvXFehkggHQrnYo5727MseEi6i9 +ORY_POSTGRES_DB=ory +# ORY_POSTGRES_PORT=5433 # Internal only + +KRATOS_DB=ory_kratos +HYDRA_DB=ory_hydra +KETO_DB=ory_keto + +# Ory Kratos Configuration +KRATOS_VERSION=v26.2.0-distroless +# KRATOS_PUBLIC_PORT=4433 # Internal only +# KRATOS_ADMINFRONT_PORT=4434 # Internal only + +KRATOS_UI_NODE_VERSION=v26.2.0 +# KRATOS_UI_PORT=4455 # Internal only + +# Ory Hydra Configuration +HYDRA_VERSION=v26.2.0-distroless +# HYDRA_PUBLIC_PORT=4441 # Internal only +# HYDRA_ADMINFRONT_PORT=4445 # Internal only + +# Ory Keto Configuration +KETO_VERSION=v26.2.0-distroless +# KETO_READ_PORT=4466 # Internal only +# KETO_WRITE_PORT=4467 # Internal only +KETO_READ_URL=http://keto:4466 +KETO_WRITE_URL=http://keto:4467 + +# Kratos Selfservice UI upstreams (override for deployments) +ORY_SDK_URL=http://kratos:4433 +KRATOS_PUBLIC_URL=http://kratos:4433 +KRATOS_ADMIN_URL=http://kratos:4434 + +# 브라우저가 접근할 Kratos Public/UI 외부 URL +# Oathkeeper가 /auth 경로를 Kratos Public API로 라우팅합니다. +KRATOS_BROWSER_URL=${OATHKEEPER_PUBLIC_URL}/auth +# Kratos UI는 UserFront가 렌더링합니다. +KRATOS_UI_URL=http://localhost:5000 + +HYDRA_ADMIN_URL=http://hydra:4445 +# Oathkeeper가 /oidc 경로를 Hydra Public API로 라우팅합니다. +HYDRA_PUBLIC_URL=${OATHKEEPER_PUBLIC_URL}/oidc +# 선택: Hydra 화면 핸드오프 URL을 USERFRONT_URL 기준 기본값과 다르게 둘 때만 설정합니다. +# HYDRA_LOGIN_URL=https://sso.hmac.kr/login +# HYDRA_CONSENT_URL=https://sso.hmac.kr/consent +# HYDRA_ERROR_URL=https://sso.hmac.kr/error + +# Kratos allowed_return_urls 확장 목록 (콤마 구분, 선택) +# 기본값은 KRATOS_UI_URL, USERFRONT_URL, 각 callback URL을 자동 포함합니다. +KRATOS_ALLOWED_RETURN_URLS_EXTRA=[] +KRATOS_ALLOWED_RETURN_URLS_JSON=["http://localhost:5000","http://localhost:5000/","https://sso.hmac.kr","https://sso.hmac.kr/","https://sso.hmac.kr/ko","https://sso.hmac.kr/ko/","https://sso.hmac.kr/en","https://sso.hmac.kr/en/","https://sso.hmac.kr/auth/callback","https://sso.hmac.kr/ko/auth/callback","https://sso.hmac.kr/en/auth/callback","http://localhost:5173/auth/callback","http://localhost:5174/auth/callback","http://localhost:5175/auth/callback","https://sso.hmac.kr/orgfront/auth/callback"] + +# Oathkeeper JWKS (내부 통신용) +JWKS_URL=http://oathkeeper:4456/.well-known/jwks.json + +# Oathkeeper 실행 사용자/프로브 설정 +OATHKEEPER_VERSION=v26.2.0 +OATHKEEPER_UID=1001 +OATHKEEPER_GID=1001 +OATHKEEPER_HEALTH_URL=http://oathkeeper:4456/health/ready +OATHKEEPER_HEALTH_INTERVAL_SECONDS=10 +OATHKEEPER_HEALTH_TIMEOUT_SECONDS=2 +OATHKEEPER_HEALTH_ENABLED=true + +# Kratos Selfservice UI required secrets (local only) +COOKIE_SECRET=localcookie123 +CSRF_COOKIE_NAME=__HOST-baronSSO_csrf +CSRF_COOKIE_SECRET=localcsrf123 + +# AdminFront OIDC 설정 +ADMINFRONT_URL=http://localhost:5173 +ADMINFRONT_CALLBACK_URLS=http://localhost:5173/auth/callback,https://sso.hmac.kr/auth/callback + +# DevFront OIDC 설정 +VITE_OIDC_CLIENT_ID=devfront +VITE_OIDC_AUTHORITY=https://sso.hmac.kr/oidc +DEVFRONT_URL=http://localhost:5174 +DEVFRONT_CALLBACK_URLS=http://localhost:5174/auth/callback,https://sso.hmac.kr/devfront/auth/callback +ORGFRONT_CALLBACK_URLS=http://localhost:5175/auth/callback,https://sso.hmac.kr/orgfront/auth/callback +VITE_ORGCHART_URL= + +# promtail에서 로그를 전송받을 Loki 서버 엔드포인트 URL +LOKI_URL=http://loki:3100/loki/api/v1/push + diff --git a/baron-sso/.gitea/coverage.json b/baron-sso/.gitea/coverage.json new file mode 100644 index 0000000..a80ae85 --- /dev/null +++ b/baron-sso/.gitea/coverage.json @@ -0,0 +1,7 @@ +{ + "Path": "./backend/coverage.out", + "Thresholds": { + "baron-sso-backend/internal/handler": 10, + "baron-sso-backend/internal/service": 10 + } +} diff --git a/baron-sso/.gitea/workflows/backend_coverage_check.yml b/baron-sso/.gitea/workflows/backend_coverage_check.yml new file mode 100644 index 0000000..0115792 --- /dev/null +++ b/baron-sso/.gitea/workflows/backend_coverage_check.yml @@ -0,0 +1,29 @@ +name: Backend Test Coverage Check + +on: + push: + branches: + - dev + +jobs: + coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.25" + cache-dependency-path: backend/go.sum + + - name: Run tests with coverage + working-directory: ./backend + run: | + go test -v -coverprofile=coverage.out -covermode=atomic ./internal/handler/... ./internal/service/... + + - name: Check coverage + uses: vladopajic/go-test-coverage@v2 + with: + config: ./.gitea/coverage.json diff --git a/baron-sso/.gitea/workflows/build_RC.yml b/baron-sso/.gitea/workflows/build_RC.yml new file mode 100644 index 0000000..678d59f --- /dev/null +++ b/baron-sso/.gitea/workflows/build_RC.yml @@ -0,0 +1,165 @@ +name: Build Baron SSO RC + +on: + workflow_dispatch: + inputs: + version_tag: + description: "The version tag to release to staging (e.g., v1.2601.1)" + required: true + type: string + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y jq curl + + - name: Validate RC build configuration + env: + HARBOR_ENDPOINT: ${{ vars.HARBOR_ENDPOINT }} + HARBOR_HOSTNAME: ${{ vars.HARBOR_HOSTNAME }} + HARBOR_ROBOT_ACCOUNT: ${{ vars.HARBOR_ROBOT_ACCOUNT }} + HARBOR_ROBOT_KEY: ${{ secrets.HARBOR_ROBOT_KEY }} + ADMINFRONT_URL: ${{ vars.ADMINFRONT_URL }} + DEVFRONT_URL: ${{ vars.DEVFRONT_URL }} + ORGFRONT_URL: ${{ vars.ORGFRONT_URL }} + VITE_OIDC_AUTHORITY: ${{ vars.VITE_OIDC_AUTHORITY }} + run: | + set -euo pipefail + + required_action_env=" + HARBOR_ENDPOINT HARBOR_HOSTNAME HARBOR_ROBOT_ACCOUNT HARBOR_ROBOT_KEY + ADMINFRONT_URL DEVFRONT_URL ORGFRONT_URL VITE_OIDC_AUTHORITY + " + for key in ${required_action_env}; do + if [ -z "${!key:-}" ]; then + echo "::error::Missing required RC build value: ${key}. Check Gitea repo variables/secrets." + exit 1 + fi + done + + - name: Login to Docker Registry + uses: docker/login-action@v3 + with: + registry: ${{ vars.HARBOR_ENDPOINT }} + username: ${{ vars.HARBOR_ROBOT_ACCOUNT }} + password: ${{ secrets.HARBOR_ROBOT_KEY }} + + - name: Calculate next RC tag + id: rc_calculator + env: + INPUT_TAG: ${{ github.event.inputs.version_tag }} + REGISTRY_URL: ${{ vars.HARBOR_ENDPOINT }} + HARBOR_USER: ${{ vars.HARBOR_ROBOT_ACCOUNT }} + HARBOR_PASSWORD: ${{ secrets.HARBOR_ROBOT_KEY }} + run: | + # Generate YYMM dynamically for the new tag + CURRENT_YYMM=$(date +'%y%m') + + # Reconstruct the base tag with the current YYMM + MAJOR_VERSION=$(echo "${INPUT_TAG}" | cut -d'.' -f1) + MINOR_VERSION=$(echo "${INPUT_TAG}" | cut -d'.' -f3) + BASE_TAG="${MAJOR_VERSION}.${CURRENT_YYMM}.${MINOR_VERSION}" + + echo "Input tag: ${INPUT_TAG}" + echo "Generated dynamic base tag: ${BASE_TAG}" + + # Using the backend repository as the source for RC version calculation + API_URL="${REGISTRY_URL}/api/v2.0/projects/baron_sso/repositories/backend/artifacts?sort=-creation_time&page_size=100" + + AUTH_HEADER=$(echo -n "${HARBOR_USER}:${HARBOR_PASSWORD}" | base64) + API_RESPONSE=$(curl -s -k -H "Authorization: Basic ${AUTH_HEADER}" "${API_URL}") + + # Define a search pattern to find RCs across different months for the same major/minor version + # e.g., matches v1.2508.1-RC, v1.2509.1-RC, etc. + SEARCH_PATTERN="^${MAJOR_VERSION}\.[0-9]{4}\.${MINOR_VERSION}-RC" + echo "Using search pattern: ${SEARCH_PATTERN}" + + # Disable pipefail for grep, as it will exit with 1 if no match is found + set +o pipefail + # Find the highest RC number regardless of the YYMM part + LATEST_RC_NUM=$(echo "${API_RESPONSE}" | jq -r '.[] | .tags[]? | .name' | grep -E "${SEARCH_PATTERN}" | sed 's/.*-RC//' | sort -rn | head -n 1) + set -o pipefail + + if [ -z "$LATEST_RC_NUM" ]; then + NEXT_RC_NUM=1 + else + NEXT_RC_NUM=$((LATEST_RC_NUM + 1)) + fi + + # Create the new tag using the dynamically generated BASE_TAG and the incremented RC number + NEW_RC_TAG="${BASE_TAG}-RC${NEXT_RC_NUM}" + echo "new_rc_tag=$NEW_RC_TAG" >> $GITHUB_OUTPUT + echo "Found latest RC number: ${LATEST_RC_NUM:-0}" + echo "Calculated new RC tag: $NEW_RC_TAG" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push backend RC image + uses: docker/build-push-action@v5 + with: + context: ./backend + file: ./backend/Dockerfile + push: true + tags: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/backend:${{ steps.rc_calculator.outputs.new_rc_tag }} + provenance: false + sbom: false + + - name: Build and push adminfront RC image + uses: docker/build-push-action@v5 + with: + context: . + file: ./adminfront/Dockerfile + push: true + tags: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/adminfront:${{ steps.rc_calculator.outputs.new_rc_tag }} + build-args: | + VITE_ADMIN_PUBLIC_URL=${{ vars.ADMINFRONT_URL }} + VITE_OIDC_AUTHORITY=${{ vars.VITE_OIDC_AUTHORITY }} + VITE_OIDC_CLIENT_ID=adminfront + ORGFRONT_URL=${{ vars.ORGFRONT_URL }} + provenance: false + sbom: false + + - name: Build and push devfront RC image + uses: docker/build-push-action@v5 + with: + context: . + file: ./devfront/Dockerfile + push: true + tags: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/devfront:${{ steps.rc_calculator.outputs.new_rc_tag }} + build-args: | + VITE_DEVFRONT_PUBLIC_URL=${{ vars.DEVFRONT_URL }} + VITE_OIDC_AUTHORITY=${{ vars.VITE_OIDC_AUTHORITY }} + VITE_OIDC_CLIENT_ID=devfront + provenance: false + sbom: false + + - name: Build and push orgfront RC image + uses: docker/build-push-action@v5 + with: + context: . + file: ./orgfront/Dockerfile + push: true + tags: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/orgfront:${{ steps.rc_calculator.outputs.new_rc_tag }} + build-args: | + VITE_ORGFRONT_PUBLIC_URL=${{ vars.ORGFRONT_URL }} + VITE_OIDC_AUTHORITY=${{ vars.VITE_OIDC_AUTHORITY }} + VITE_OIDC_CLIENT_ID=orgfront + provenance: false + sbom: false + + - name: Build and push userfront RC image + uses: docker/build-push-action@v5 + with: + context: . + file: ./userfront/Dockerfile + target: production + push: true + tags: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/userfront:${{ steps.rc_calculator.outputs.new_rc_tag }} + provenance: false + sbom: false diff --git a/baron-sso/.gitea/workflows/code_check.yml b/baron-sso/.gitea/workflows/code_check.yml new file mode 100644 index 0000000..362dbd2 --- /dev/null +++ b/baron-sso/.gitea/workflows/code_check.yml @@ -0,0 +1,1874 @@ +name: Code Check + +on: + push: + branches: + - dev + paths-ignore: + - "docs/badges/**" + pull_request: + branches: + - dev + workflow_dispatch: + inputs: + run_lint: + description: "Run lint/format checks for Go, Flutter, adminfront, devfront, orgfront" + required: true + type: boolean + default: true + run_backend_tests: + description: "Run backend Go tests" + required: true + type: boolean + default: true + run_userfront_tests: + description: "Run userfront Flutter tests" + required: true + type: boolean + default: true + run_userfront_coverage: + description: "Run userfront Flutter coverage and upload LCOV report" + required: true + type: boolean + default: true + run_userfront_e2e_tests: + description: "Run userfront WASM Playwright E2E tests" + required: true + type: boolean + default: true + run_userfront_e2e_full: + description: "Run full userfront E2E browser matrix instead of Chromium fast lane" + required: true + type: boolean + default: false + userfront_e2e_workers: + description: "Playwright worker count for userfront E2E tests" + required: true + type: string + default: "2" + run_adminfront_tests: + description: "Run adminfront Playwright tests" + required: true + type: boolean + default: true + run_devfront_tests: + description: "Run devfront Playwright tests" + required: true + type: boolean + default: true + run_orgfront_tests: + description: "Run orgfront Playwright tests" + required: true + type: boolean + default: true + run_front_coverage: + description: "Run adminfront/devfront/orgfront Vitest coverage and upload reports" + required: true + type: boolean + default: true + +permissions: + contents: write + +jobs: + changes: + runs-on: ubuntu-latest + outputs: + any: ${{ steps.filter.outputs.any }} + lint: ${{ steps.filter.outputs.lint }} + biome: ${{ steps.filter.outputs.biome }} + backend: ${{ steps.filter.outputs.backend }} + userfront: ${{ steps.filter.outputs.userfront }} + userfront_coverage: ${{ steps.filter.outputs.userfront_coverage }} + userfront_e2e: ${{ steps.filter.outputs.userfront_e2e }} + front_coverage: ${{ steps.filter.outputs.front_coverage }} + adminfront: ${{ steps.filter.outputs.adminfront }} + devfront: ${{ steps.filter.outputs.devfront }} + orgfront: ${{ steps.filter.outputs.orgfront }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Detect changed areas + id: filter + run: | + set -euo pipefail + + set_output() { + echo "$1=$2" >> "$GITHUB_OUTPUT" + } + + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + for key in any lint biome backend userfront userfront_coverage userfront_e2e front_coverage adminfront devfront orgfront; do + set_output "$key" true + done + exit 0 + fi + + base="${{ github.event.before }}" + if [ "${{ github.event_name }}" = "pull_request" ]; then + base="${{ github.event.pull_request.base.sha }}" + fi + + if [ -z "$base" ] || ! git cat-file -e "$base^{commit}" 2>/dev/null; then + base="$(git rev-parse HEAD^ 2>/dev/null || true)" + fi + + if [ -n "$base" ]; then + changed_files="$(git diff --name-only "$base" HEAD)" + else + changed_files="$(git ls-files)" + fi + + echo "Changed files:" + printf '%s\n' "$changed_files" + + matches() { + printf '%s\n' "$changed_files" | grep -Eq "$1" + } + + global='^(\.gitea/workflows/code_check\.yml|Makefile|scripts/|tools/|test/code_check_)' + front_shared='^(common/|scripts/playwrightPackageVersion\.cjs|scripts/summarize_vitest_coverage\.mjs|scripts/run_adminfront_ci_tests\.sh|\.gitea/workflows/code_check\.yml|Makefile)' + i18n_shared='^(common/locales/|userfront/assets/translations/|scripts/sync_userfront_locales\.sh|tools/i18n-scanner/)' + + backend=false + userfront=false + userfront_coverage=false + userfront_e2e=false + adminfront=false + devfront=false + orgfront=false + front_coverage=false + biome=false + + if matches "$global|^backend/"; then backend=true; fi + if matches "$global|$i18n_shared|^userfront/"; then userfront=true; fi + if matches "$global|$i18n_shared|^userfront/|^scripts/summarize_flutter_coverage\.mjs"; then userfront_coverage=true; fi + if matches "$global|$i18n_shared|^userfront/|^userfront-e2e/"; then userfront_e2e=true; fi + if matches "$front_shared|^adminfront/"; then adminfront=true; fi + if matches "$front_shared|^devfront/"; then devfront=true; fi + if matches "$front_shared|^orgfront/"; then orgfront=true; fi + if matches "$front_shared|^adminfront/|^devfront/|^orgfront/"; then front_coverage=true; fi + if matches "$front_shared|^adminfront/|^devfront/|^orgfront/"; then biome=true; fi + + lint=false + if [ "$backend" = true ] || [ "$userfront" = true ] || [ "$adminfront" = true ] || [ "$devfront" = true ] || [ "$orgfront" = true ] || matches "$i18n_shared"; then + lint=true + fi + + any=false + for value in "$lint" "$biome" "$backend" "$userfront" "$userfront_coverage" "$userfront_e2e" "$front_coverage" "$adminfront" "$devfront" "$orgfront"; do + if [ "$value" = true ]; then any=true; fi + done + + set_output any "$any" + set_output lint "$lint" + set_output biome "$biome" + set_output backend "$backend" + set_output userfront "$userfront" + set_output userfront_coverage "$userfront_coverage" + set_output userfront_e2e "$userfront_e2e" + set_output front_coverage "$front_coverage" + set_output adminfront "$adminfront" + set_output devfront "$devfront" + set_output orgfront "$orgfront" + + lint: + needs: changes + if: ${{ needs.changes.outputs.lint == 'true' && (github.event_name != 'workflow_dispatch' || inputs.run_lint == true) }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + + - name: i18n resource check + run: | + mkdir -p reports + node tools/i18n-scanner/index.js + node tools/i18n-scanner/report.js + cat reports/i18n-report.txt + + - name: i18n value quality check + run: | + mkdir -p reports + node tools/i18n-scanner/value-check.js + cat reports/i18n-value-report.txt + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.25" + cache-dependency-path: backend/go.sum + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + cache: true + + - name: Install adminfront dependencies + run: | + cd adminfront + npx pnpm install -C ../common --no-frozen-lockfile + npx pnpm install --no-frozen-lockfile + + - name: Biome check adminfront (lint + format) + run: | + cd adminfront + npx biome check . --formatter-enabled=false --assist-enabled=false + npx biome check . --linter-enabled=false --assist-enabled=false + + - name: Install devfront dependencies + run: | + cd devfront + npx pnpm install -C ../common --no-frozen-lockfile + npx pnpm install --no-frozen-lockfile + + - name: Biome check devfront (lint + format) + run: | + cd devfront + npx biome check . --formatter-enabled=false --assist-enabled=false + npx biome check . --linter-enabled=false --assist-enabled=false + + - name: Install orgfront dependencies + run: | + cd orgfront + npx pnpm install -C ../common --no-frozen-lockfile + npx pnpm install --no-frozen-lockfile + + - name: Biome check orgfront (lint + format) + run: | + cd orgfront + npx biome check . --formatter-enabled=false --assist-enabled=false + npx biome check . --linter-enabled=false --assist-enabled=false + + - name: Lint Go backend + run: | + docker run --rm \ + -v "${PWD}/backend:/app" \ + -w /app \ + golangci/golangci-lint:v2.10.1 \ + golangci-lint fmt -E gofmt -E gofumpt -d + + - name: Sync userfront locales + run: | + /bin/sh ./scripts/sync_userfront_locales.sh + + - name: Install Userfront dependencies + run: | + cd userfront + flutter pub get + + - name: Format Flutter userfront + run: | + cd userfront + dart format --output=none --set-exit-if-changed lib test + + - name: Analyze Flutter userfront + run: | + cd userfront + flutter analyze --no-fatal-warnings --no-fatal-infos + + biome-check: + needs: changes + if: ${{ needs.changes.outputs.biome == 'true' && (github.event_name != 'workflow_dispatch' || inputs.run_lint == true) }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + + - name: Install adminfront dependencies + run: | + cd adminfront + npx pnpm install -C ../common --no-frozen-lockfile + npx pnpm install --no-frozen-lockfile + + - name: Biome check adminfront + run: | + cd adminfront + npx biome check . --formatter-enabled=false --assist-enabled=false + npx biome check . --linter-enabled=false --assist-enabled=false + + - name: Install devfront dependencies + run: | + cd devfront + npx pnpm install -C ../common --no-frozen-lockfile + npx pnpm install --no-frozen-lockfile + + - name: Biome check devfront + run: | + cd devfront + npx biome check . --formatter-enabled=false --assist-enabled=false + npx biome check . --linter-enabled=false --assist-enabled=false + + - name: Install orgfront dependencies + run: | + cd orgfront + npx pnpm install -C ../common --no-frozen-lockfile + npx pnpm install --no-frozen-lockfile + + - name: Biome check orgfront + run: | + cd orgfront + npx biome check . --formatter-enabled=false --assist-enabled=false + npx biome check . --linter-enabled=false --assist-enabled=false + + backend-tests: + needs: + - changes + - lint + if: ${{ always() && needs.changes.outputs.backend == 'true' && (github.event_name != 'workflow_dispatch' || inputs.run_backend_tests == true) }} + runs-on: ubuntu-latest + services: + redis: + image: redis:7-alpine + options: > + --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 + clickhouse: + image: clickhouse/clickhouse-server:24.6 + options: > + --health-cmd "wget -qO- 'http://localhost:8123/ping'" --health-interval 10s --health-timeout 5s --health-retries 5 + + env: + REDIS_ADDR: redis:6379 + CLICKHOUSE_HOST: clickhouse + CLICKHOUSE_PORT_NATIVE: 9000 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.25" + cache-dependency-path: backend/go.sum + + - name: Run backend tests + run: | + mkdir -p reports + set +e + cd backend + go test -v -coverprofile=../reports/backend-coverage.out -covermode=atomic \ + ./internal/domain \ + ./internal/pagination \ + ./internal/response \ + ./internal/utils \ + ./internal/validator 2>&1 | tee ../reports/backend-coverage.log + coverage_exit_code=${PIPESTATUS[0]} + + if [ "$coverage_exit_code" -eq 0 ]; then + coverage_percent="$(go tool cover -func=../reports/backend-coverage.out | awk '/^total:/ { gsub(/%/, "", $3); print $3 }')" + node -e "const fs = require('node:fs'); const statements = Number(process.argv[1]); fs.writeFileSync('../reports/backend-coverage-summary.json', JSON.stringify({ package: 'backend', statements }, null, 2) + '\n');" "$coverage_percent" + { + echo "# Backend Coverage Summary" + echo + echo "| Package | Statements |" + echo "| --- | ---: |" + printf '| backend | %.2f%% |\n' "$coverage_percent" + echo + } > ../reports/backend-coverage-summary.md + cat ../reports/backend-coverage-summary.md >> "$GITHUB_STEP_SUMMARY" + fi + + go test -v ./... 2>&1 | tee ../reports/backend-test.log + test_exit_code=${PIPESTATUS[0]} + cd .. + + if [ "$coverage_exit_code" -ne 0 ] || [ "$test_exit_code" -ne 0 ]; then + { + echo "# Backend Test Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`backend-tests\`" + echo "- Coverage Exit Code: \`$coverage_exit_code\`" + echo "- Test Exit Code: \`$test_exit_code\`" + echo + echo "## Command" + echo "\`go test -v ./...\`" + echo + if [ -f reports/backend-coverage.log ]; then + echo "## Coverage Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/backend-coverage.log + echo '```' + echo + fi + echo "## Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/backend-test.log + echo '```' + } > reports/backend-test-failure-report.md + fi + + if [ "$coverage_exit_code" -ne 0 ]; then + exit "$coverage_exit_code" + fi + exit "$test_exit_code" + + - name: Publish backend failure summary + if: ${{ failure() }} + run: | + if [ -f reports/backend-test-failure-report.md ]; then + cat reports/backend-test-failure-report.md >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Upload backend failure report artifact + if: ${{ always() }} + uses: actions/upload-artifact@v3 + continue-on-error: true + with: + name: backend-coverage-report + path: | + reports/backend-test-failure-report.md + reports/backend-test.log + reports/backend-coverage.log + reports/backend-coverage.out + reports/backend-coverage-summary.json + reports/backend-coverage-summary.md + if-no-files-found: ignore + + userfront-tests: + needs: + - changes + - lint + if: ${{ always() && needs.changes.outputs.userfront == 'true' && (github.event_name != 'workflow_dispatch' || inputs.run_userfront_tests == true) }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + cache: true + + - name: Sync userfront locales + run: | + /bin/sh ./scripts/sync_userfront_locales.sh + + - name: Run userfront tests + run: | + cd userfront + if [ -d test ]; then + mkdir -p ../reports + set +e + flutter test 2>&1 | tee ../reports/userfront-test.log + test_exit_code=${PIPESTATUS[0]} + set -e + + if [ "$test_exit_code" -ne 0 ]; then + { + echo "# Userfront Test Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`userfront-tests\`" + echo "- Exit Code: \`$test_exit_code\`" + echo + echo "## Command" + echo "\`flutter test\`" + echo + if [ -f ../reports/userfront-test.log ]; then + echo "## Test Log Tail (last 200 lines)" + echo '```text' + tail -n 200 ../reports/userfront-test.log + echo '```' + fi + } > ../reports/userfront-test-failure-report.md + exit 1 + fi + else + echo "No userfront tests: skipping (test/ directory not found)." + fi + + - name: Ensure userfront failure report exists + if: ${{ failure() }} + run: | + mkdir -p reports + if [ -f reports/userfront-test-failure-report.md ]; then + exit 0 + fi + + { + echo "# Userfront Test Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`userfront-tests\`" + echo "- Reason: \`Job failed before detailed report generation\`" + echo + if [ -f reports/userfront-test.log ]; then + echo "## Test Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/userfront-test.log + echo '```' + fi + } > reports/userfront-test-failure-report.md + + - name: Publish userfront failure summary + if: ${{ failure() }} + run: | + if [ -f reports/userfront-test-failure-report.md ]; then + cat reports/userfront-test-failure-report.md >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Upload userfront failure report artifact + if: ${{ failure() }} + uses: actions/upload-artifact@v3 + continue-on-error: true + with: + name: userfront-test-failure-report + path: | + reports/userfront-test-failure-report.md + reports/userfront-test.log + if-no-files-found: ignore + + userfront-flutter-coverage: + needs: + - changes + - lint + if: ${{ always() && needs.changes.outputs.userfront_coverage == 'true' && (github.event_name != 'workflow_dispatch' || inputs.run_userfront_coverage == true) }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + cache: true + + - name: Sync userfront locales + run: | + /bin/sh ./scripts/sync_userfront_locales.sh + + - name: Run userfront Flutter coverage + run: | + cd userfront + if [ -d test ]; then + mkdir -p ../reports + set +e + flutter test --coverage 2>&1 | tee ../reports/userfront-flutter-coverage.log + test_exit_code=${PIPESTATUS[0]} + set -e + + if [ "$test_exit_code" -ne 0 ]; then + { + echo "# Userfront Flutter Coverage Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`userfront-flutter-coverage\`" + echo "- Exit Code: \`$test_exit_code\`" + echo + echo "## Command" + echo "\`flutter test --coverage\`" + echo + if [ -f ../reports/userfront-flutter-coverage.log ]; then + echo "## Coverage Log Tail (last 200 lines)" + echo '```text' + tail -n 200 ../reports/userfront-flutter-coverage.log + echo '```' + fi + } > ../reports/userfront-flutter-coverage-failure-report.md + exit 1 + fi + else + echo "No userfront tests: skipping coverage (test/ directory not found)." + fi + + - name: Generate userfront Flutter coverage summary + run: | + node scripts/summarize_flutter_coverage.mjs userfront + cat reports/userfront-coverage-summary.md >> "$GITHUB_STEP_SUMMARY" + + - name: Publish userfront Flutter coverage failure summary + if: ${{ failure() }} + run: | + if [ -f reports/userfront-flutter-coverage-failure-report.md ]; then + cat reports/userfront-flutter-coverage-failure-report.md >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Upload userfront Flutter coverage report artifact + if: ${{ always() }} + uses: actions/upload-artifact@v3 + continue-on-error: true + with: + name: userfront-flutter-coverage-report + path: | + reports/package-coverage-summary.json + reports/userfront-coverage-summary.md + reports/userfront-flutter-coverage-failure-report.md + reports/userfront-flutter-coverage.log + userfront/coverage/lcov.info + if-no-files-found: ignore + + userfront-e2e-tests: + needs: + - changes + - lint + if: ${{ always() && needs.changes.outputs.userfront_e2e == 'true' && (github.event_name != 'workflow_dispatch' || inputs.run_userfront_e2e_tests == true) }} + runs-on: ubuntu-latest + timeout-minutes: 40 + env: + USERFRONT_E2E_FULL: ${{ github.event_name == 'workflow_dispatch' && inputs.run_userfront_e2e_full == true }} + USERFRONT_E2E_WORKERS: ${{ github.event_name == 'workflow_dispatch' && inputs.userfront_e2e_workers || '2' }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + cache: "npm" + cache-dependency-path: userfront-e2e/package-lock.json + + - name: Get Playwright version + id: playwright-version + run: | + node scripts/playwrightPackageVersion.cjs userfront-e2e >> "$GITHUB_OUTPUT" + + - name: Cache Playwright Browsers + uses: actions/cache@v4 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }} + restore-keys: | + ${{ runner.os }}-playwright- + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + cache: true + + - name: Sync userfront locales + run: | + /bin/sh ./scripts/sync_userfront_locales.sh + + - name: Install userfront-e2e dependencies + run: | + mkdir -p reports + set +e + cd userfront-e2e + npm ci 2>&1 | tee ../reports/userfront-e2e-install.log + install_exit_code=${PIPESTATUS[0]} + cd .. + set -e + + if [ "$install_exit_code" -ne 0 ]; then + { + echo "# Userfront E2E Test Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`userfront-e2e-tests\`" + echo "- Reason: \`Dependency install failed\`" + echo "- Exit Code: \`$install_exit_code\`" + echo + echo "## Command" + echo "\`cd userfront-e2e && npm ci\`" + echo + echo "## Install Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/userfront-e2e-install.log + echo '```' + } > reports/userfront-e2e-test-failure-report.md + exit 1 + fi + + - name: Build userfront WASM + run: | + mkdir -p reports + set +e + cd userfront + rm -rf build/web + flutter build web --wasm --release 2>&1 | tee ../reports/userfront-e2e-build.log + build_exit_code=${PIPESTATUS[0]} + cd .. + if [ "$build_exit_code" -eq 0 ]; then + node userfront/scripts/optimize-web-build.mjs userfront/build/web 2>&1 | tee -a reports/userfront-e2e-build.log + build_exit_code=${PIPESTATUS[0]} + fi + set -e + + if [ "$build_exit_code" -ne 0 ]; then + { + echo "# Userfront E2E Test Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`userfront-e2e-tests\`" + echo "- Reason: \`WASM build failed\`" + echo "- Exit Code: \`$build_exit_code\`" + echo + echo "## Command" + echo "\`cd userfront && flutter build web --wasm --release\`" + echo + echo "## Build Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/userfront-e2e-build.log + echo '```' + } > reports/userfront-e2e-test-failure-report.md + exit 1 + fi + + - name: Provision browsers for userfront-e2e tests + run: | + set +e + cd userfront-e2e + if [ "$USERFRONT_E2E_FULL" = "true" ]; then + provision_command="npx playwright install --with-deps" + else + provision_command="npx playwright install --with-deps chromium" + fi + echo "[userfront-e2e] $provision_command" | tee ../reports/userfront-e2e-provision.log + $provision_command 2>&1 | tee -a ../reports/userfront-e2e-provision.log + provision_exit_code=${PIPESTATUS[0]} + cd .. + set -e + + if [ "$provision_exit_code" -ne 0 ]; then + { + echo "# Userfront E2E Test Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`userfront-e2e-tests\`" + echo "- Reason: \`Browser provisioning failed\`" + echo "- Exit Code: \`$provision_exit_code\`" + echo + echo "## Command" + echo "\`cd userfront-e2e && $provision_command\`" + echo + echo "## Provision Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/userfront-e2e-provision.log + echo '```' + } > reports/userfront-e2e-test-failure-report.md + exit 1 + fi + + - name: Run userfront-e2e tests + run: | + mkdir -p reports + set +e + cd userfront-e2e + if [ "$USERFRONT_E2E_FULL" = "true" ]; then + test_command="npm test" + else + test_command="npm test -- --project=chromium-desktop --project=chromium-mobile-webapp" + fi + workers="${USERFRONT_E2E_WORKERS:-2}" + case "$workers" in + ''|*[!0-9]*|0) workers=2 ;; + esac + echo "[userfront-e2e] PLAYWRIGHT_WORKERS=$workers $test_command" | tee ../reports/userfront-e2e-test.log + PLAYWRIGHT_WORKERS="$workers" $test_command 2>&1 | tee -a ../reports/userfront-e2e-test.log + test_exit_code=${PIPESTATUS[0]} + cd .. + set -e + + if [ "$test_exit_code" -ne 0 ]; then + { + echo "# Userfront E2E Test Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`userfront-e2e-tests\`" + echo "- Exit Code: \`$test_exit_code\`" + echo + echo "## Commands" + echo "1. \`cd userfront-e2e\`" + echo "2. \`npm ci\`" + echo "3. \`cd ../userfront && flutter build web --wasm --release\`" + if [ "$USERFRONT_E2E_FULL" = "true" ]; then + echo "4. \`cd ../userfront-e2e && npx playwright install --with-deps\`" + echo "5. \`npm test\`" + else + echo "4. \`cd ../userfront-e2e && npx playwright install --with-deps chromium\`" + echo "5. \`npm test -- --project=chromium-desktop --project=chromium-mobile-webapp\`" + fi + echo + echo "## Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/userfront-e2e-test.log + echo '```' + } > reports/userfront-e2e-test-failure-report.md + fi + + exit "$test_exit_code" + + - name: Ensure userfront-e2e failure report exists + if: ${{ failure() }} + run: | + mkdir -p reports + if [ -f reports/userfront-e2e-test-failure-report.md ]; then + exit 0 + fi + + { + echo "# Userfront E2E Test Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`userfront-e2e-tests\`" + echo "- Reason: \`Job failed before detailed report generation\`" + echo + if [ -f reports/userfront-e2e-install.log ]; then + echo "## Install Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/userfront-e2e-install.log + echo '```' + echo + fi + if [ -f reports/userfront-e2e-build.log ]; then + echo "## Build Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/userfront-e2e-build.log + echo '```' + echo + fi + if [ -f reports/userfront-e2e-provision.log ]; then + echo "## Provision Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/userfront-e2e-provision.log + echo '```' + echo + fi + if [ -f reports/userfront-e2e-test.log ]; then + echo "## Test Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/userfront-e2e-test.log + echo '```' + fi + } > reports/userfront-e2e-test-failure-report.md + + - name: Publish userfront-e2e failure summary + if: ${{ failure() }} + run: | + if [ -f reports/userfront-e2e-test-failure-report.md ]; then + cat reports/userfront-e2e-test-failure-report.md >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Upload userfront-e2e failure report artifact + if: ${{ failure() }} + uses: actions/upload-artifact@v3 + continue-on-error: true + with: + name: userfront-e2e-test-failure-report + path: | + reports/userfront-e2e-test-failure-report.md + reports/userfront-e2e-install.log + reports/userfront-e2e-build.log + reports/userfront-e2e-provision.log + reports/userfront-e2e-test.log + userfront-e2e/playwright-report + userfront-e2e/test-results + if-no-files-found: ignore + + adminfront-vitest-coverage: + needs: + - changes + - lint + if: ${{ always() && needs.changes.outputs.adminfront == 'true' && (github.event_name != 'workflow_dispatch' || inputs.run_front_coverage == true) }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.5.2 + + - name: Install front workspace dependencies + run: | + mkdir -p reports + set +e + cd common + pnpm install --no-frozen-lockfile --shamefully-hoist 2>&1 | tee ../reports/front-coverage-install.log + install_exit_code=${PIPESTATUS[0]} + cd .. + set -e + + if [ "$install_exit_code" -ne 0 ]; then + { + echo "# Adminfront Vitest Coverage Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`adminfront-vitest-coverage\`" + echo "- Reason: \`Dependency install failed\`" + echo "- Exit Code: \`$install_exit_code\`" + echo + echo "## Command" + echo "\`cd common && pnpm install --no-frozen-lockfile --shamefully-hoist\`" + echo + echo "## Install Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/front-coverage-install.log + echo '```' + } > reports/adminfront-vitest-coverage-failure-report.md + exit 1 + fi + + set +e + cd adminfront + pnpm install --no-frozen-lockfile --shamefully-hoist 2>&1 | tee -a ../reports/front-coverage-install.log + app_install_exit_code=${PIPESTATUS[0]} + cd .. + set -e + + if [ "$app_install_exit_code" -ne 0 ]; then + { + echo "# Adminfront Vitest Coverage Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`adminfront-vitest-coverage\`" + echo "- Package: \`adminfront\`" + echo "- Reason: \`Dependency install failed\`" + echo "- Exit Code: \`$app_install_exit_code\`" + echo + echo "## Command" + echo "\`cd adminfront && pnpm install --no-frozen-lockfile --shamefully-hoist\`" + echo + echo "## Install Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/front-coverage-install.log + echo '```' + } > reports/adminfront-vitest-coverage-failure-report.md + exit 1 + fi + + - name: Run adminfront Vitest coverage + run: | + set +e + cd adminfront + pnpm run test:coverage 2>&1 | tee ../reports/adminfront-vitest-coverage.log + test_exit_code=${PIPESTATUS[0]} + cd .. + set -e + + if [ "$test_exit_code" -ne 0 ]; then + { + echo "# Adminfront Vitest Coverage Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`adminfront-vitest-coverage\`" + echo "- Package: \`adminfront\`" + echo "- Exit Code: \`$test_exit_code\`" + echo + echo "## Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/adminfront-vitest-coverage.log + echo '```' + } > reports/adminfront-vitest-coverage-failure-report.md + exit 1 + fi + + - name: Generate adminfront Vitest coverage summary + run: | + node scripts/summarize_vitest_coverage.mjs adminfront + cat reports/vitest-coverage-summary.md >> "$GITHUB_STEP_SUMMARY" + + - name: Publish adminfront Vitest coverage failure summary + if: ${{ failure() }} + run: | + if [ -f reports/adminfront-vitest-coverage-failure-report.md ]; then + cat reports/adminfront-vitest-coverage-failure-report.md >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Upload adminfront Vitest coverage report artifact + if: ${{ always() }} + uses: actions/upload-artifact@v3 + continue-on-error: true + with: + name: adminfront-vitest-coverage-report + path: | + reports/vitest-coverage-summary.md + reports/vitest-coverage-summary.json + reports/adminfront-vitest-coverage-failure-report.md + reports/front-coverage-install.log + reports/adminfront-vitest-coverage.log + adminfront/coverage + if-no-files-found: ignore + + devfront-vitest-coverage: + needs: + - changes + - lint + if: ${{ always() && needs.changes.outputs.devfront == 'true' && (github.event_name != 'workflow_dispatch' || inputs.run_front_coverage == true) }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.5.2 + + - name: Install front workspace dependencies + run: | + mkdir -p reports + set +e + cd common + pnpm install --no-frozen-lockfile --shamefully-hoist 2>&1 | tee ../reports/front-coverage-install.log + install_exit_code=${PIPESTATUS[0]} + cd .. + set -e + + if [ "$install_exit_code" -ne 0 ]; then + { + echo "# Devfront Vitest Coverage Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`devfront-vitest-coverage\`" + echo "- Reason: \`Dependency install failed\`" + echo "- Exit Code: \`$install_exit_code\`" + echo + echo "## Command" + echo "\`cd common && pnpm install --no-frozen-lockfile --shamefully-hoist\`" + echo + echo "## Install Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/front-coverage-install.log + echo '```' + } > reports/devfront-vitest-coverage-failure-report.md + exit 1 + fi + + set +e + cd devfront + pnpm install --no-frozen-lockfile --shamefully-hoist 2>&1 | tee -a ../reports/front-coverage-install.log + app_install_exit_code=${PIPESTATUS[0]} + cd .. + set -e + + if [ "$app_install_exit_code" -ne 0 ]; then + { + echo "# Devfront Vitest Coverage Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`devfront-vitest-coverage\`" + echo "- Package: \`devfront\`" + echo "- Reason: \`Dependency install failed\`" + echo "- Exit Code: \`$app_install_exit_code\`" + echo + echo "## Command" + echo "\`cd devfront && pnpm install --no-frozen-lockfile --shamefully-hoist\`" + echo + echo "## Install Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/front-coverage-install.log + echo '```' + } > reports/devfront-vitest-coverage-failure-report.md + exit 1 + fi + + - name: Run devfront Vitest coverage + run: | + set +e + cd devfront + pnpm run test:coverage 2>&1 | tee ../reports/devfront-vitest-coverage.log + test_exit_code=${PIPESTATUS[0]} + cd .. + set -e + + if [ "$test_exit_code" -ne 0 ]; then + { + echo "# Devfront Vitest Coverage Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`devfront-vitest-coverage\`" + echo "- Package: \`devfront\`" + echo "- Exit Code: \`$test_exit_code\`" + echo + echo "## Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/devfront-vitest-coverage.log + echo '```' + } > reports/devfront-vitest-coverage-failure-report.md + exit 1 + fi + + - name: Generate devfront Vitest coverage summary + run: | + node scripts/summarize_vitest_coverage.mjs devfront + cat reports/vitest-coverage-summary.md >> "$GITHUB_STEP_SUMMARY" + + - name: Publish devfront Vitest coverage failure summary + if: ${{ failure() }} + run: | + if [ -f reports/devfront-vitest-coverage-failure-report.md ]; then + cat reports/devfront-vitest-coverage-failure-report.md >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Upload devfront Vitest coverage report artifact + if: ${{ always() }} + uses: actions/upload-artifact@v3 + continue-on-error: true + with: + name: devfront-vitest-coverage-report + path: | + reports/vitest-coverage-summary.md + reports/vitest-coverage-summary.json + reports/devfront-vitest-coverage-failure-report.md + reports/front-coverage-install.log + reports/devfront-vitest-coverage.log + devfront/coverage + if-no-files-found: ignore + + orgfront-vitest-coverage: + needs: + - changes + - lint + if: ${{ always() && needs.changes.outputs.orgfront == 'true' && (github.event_name != 'workflow_dispatch' || inputs.run_front_coverage == true) }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.5.2 + + - name: Install front workspace dependencies + run: | + mkdir -p reports + set +e + cd common + pnpm install --no-frozen-lockfile --shamefully-hoist 2>&1 | tee ../reports/front-coverage-install.log + install_exit_code=${PIPESTATUS[0]} + cd .. + set -e + + if [ "$install_exit_code" -ne 0 ]; then + { + echo "# Orgfront Vitest Coverage Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`orgfront-vitest-coverage\`" + echo "- Reason: \`Dependency install failed\`" + echo "- Exit Code: \`$install_exit_code\`" + echo + echo "## Command" + echo "\`cd common && pnpm install --no-frozen-lockfile --shamefully-hoist\`" + echo + echo "## Install Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/front-coverage-install.log + echo '```' + } > reports/orgfront-vitest-coverage-failure-report.md + exit 1 + fi + + set +e + cd orgfront + pnpm install --no-frozen-lockfile --shamefully-hoist 2>&1 | tee -a ../reports/front-coverage-install.log + app_install_exit_code=${PIPESTATUS[0]} + cd .. + set -e + + if [ "$app_install_exit_code" -ne 0 ]; then + { + echo "# Orgfront Vitest Coverage Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`orgfront-vitest-coverage\`" + echo "- Package: \`orgfront\`" + echo "- Reason: \`Dependency install failed\`" + echo "- Exit Code: \`$app_install_exit_code\`" + echo + echo "## Command" + echo "\`cd orgfront && pnpm install --no-frozen-lockfile --shamefully-hoist\`" + echo + echo "## Install Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/front-coverage-install.log + echo '```' + } > reports/orgfront-vitest-coverage-failure-report.md + exit 1 + fi + + - name: Run orgfront Vitest coverage + run: | + set +e + cd orgfront + pnpm run test:coverage 2>&1 | tee ../reports/orgfront-vitest-coverage.log + test_exit_code=${PIPESTATUS[0]} + cd .. + set -e + + if [ "$test_exit_code" -ne 0 ]; then + { + echo "# Orgfront Vitest Coverage Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`orgfront-vitest-coverage\`" + echo "- Package: \`orgfront\`" + echo "- Exit Code: \`$test_exit_code\`" + echo + echo "## Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/orgfront-vitest-coverage.log + echo '```' + } > reports/orgfront-vitest-coverage-failure-report.md + exit 1 + fi + + - name: Generate orgfront Vitest coverage summary + run: | + node scripts/summarize_vitest_coverage.mjs orgfront + cat reports/vitest-coverage-summary.md >> "$GITHUB_STEP_SUMMARY" + + - name: Publish orgfront Vitest coverage failure summary + if: ${{ failure() }} + run: | + if [ -f reports/orgfront-vitest-coverage-failure-report.md ]; then + cat reports/orgfront-vitest-coverage-failure-report.md >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Upload orgfront Vitest coverage report artifact + if: ${{ always() }} + uses: actions/upload-artifact@v3 + continue-on-error: true + with: + name: orgfront-vitest-coverage-report + path: | + reports/vitest-coverage-summary.md + reports/vitest-coverage-summary.json + reports/orgfront-vitest-coverage-failure-report.md + reports/front-coverage-install.log + reports/orgfront-vitest-coverage.log + orgfront/coverage + if-no-files-found: ignore + + adminfront-tests: + needs: + - changes + - lint + if: ${{ always() && needs.changes.outputs.adminfront == 'true' && (github.event_name != 'workflow_dispatch' || inputs.run_adminfront_tests == true) }} + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + + - name: Get Playwright version + id: playwright-version + run: | + node scripts/playwrightPackageVersion.cjs adminfront >> "$GITHUB_OUTPUT" + + - name: Cache Playwright Browsers + uses: actions/cache@v4 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }} + restore-keys: | + ${{ runner.os }}-playwright- + + - name: Run adminfront tests + env: + PLAYWRIGHT_WORKERS: 2 + run: | + scripts/run_adminfront_ci_tests.sh adminfront-tests + + - name: Ensure adminfront failure report exists + if: ${{ failure() }} + run: | + mkdir -p reports + if [ -f reports/adminfront-test-failure-report.md ]; then + exit 0 + fi + + { + echo "# Adminfront Test Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`adminfront-tests\`" + echo "- Reason: \`Job failed before detailed report generation\`" + echo + if [ -f reports/adminfront-install.log ]; then + echo "## Install Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/adminfront-install.log + echo '```' + echo + fi + if [ -f reports/adminfront-provision.log ]; then + echo "## Provision Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/adminfront-provision.log + echo '```' + echo + fi + if [ -f reports/adminfront-test.log ]; then + echo "## Test Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/adminfront-test.log + echo '```' + fi + } > reports/adminfront-test-failure-report.md + + - name: Publish adminfront failure summary + if: ${{ failure() }} + run: | + if [ -f reports/adminfront-test-failure-report.md ]; then + cat reports/adminfront-test-failure-report.md >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Upload adminfront failure report artifact + if: ${{ failure() }} + uses: actions/upload-artifact@v3 + continue-on-error: true + with: + name: adminfront-test-failure-report + path: | + reports/adminfront-test-failure-report.md + reports/adminfront-install.log + reports/adminfront-provision.log + reports/adminfront-test.log + adminfront/playwright-report + adminfront/test-results + if-no-files-found: ignore + + devfront-tests: + needs: + - changes + - lint + if: ${{ always() && needs.changes.outputs.devfront == 'true' && (github.event_name != 'workflow_dispatch' || inputs.run_devfront_tests == true) }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + + - name: Get Playwright version + id: playwright-version + working-directory: devfront + run: | + node ../scripts/playwrightPackageVersion.cjs . >> "$GITHUB_OUTPUT" + + - name: Cache Playwright Browsers + uses: actions/cache@v4 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }} + restore-keys: | + ${{ runner.os }}-playwright- + + - name: Install devfront dependencies + working-directory: devfront + run: | + mkdir -p ../reports + set +e + { + pnpm install -C ../common --no-frozen-lockfile + pnpm install --no-frozen-lockfile + } 2>&1 | tee ../reports/devfront-install.log + install_exit_code=${PIPESTATUS[0]} + set -e + + if [ "$install_exit_code" -ne 0 ]; then + { + echo "# Devfront Test Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`devfront-tests\`" + echo "- Reason: \`Dependency install failed\`" + echo "- Exit Code: \`$install_exit_code\`" + echo + echo "## Command" + echo "\`cd devfront && pnpm install -C ../common --no-frozen-lockfile\`" + echo + echo "## Install Log Tail (last 200 lines)" + echo '```text' + tail -n 200 ../reports/devfront-install.log + echo '```' + } > ../reports/devfront-test-failure-report.md + exit 1 + fi + + - name: Provision browsers for devfront tests + working-directory: devfront + run: | + set +e + pnpm exec playwright install --with-deps 2>&1 | tee ../reports/devfront-provision.log + provision_exit_code=${PIPESTATUS[0]} + set -e + + if [ "$provision_exit_code" -ne 0 ]; then + { + echo "# Devfront Test Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`devfront-tests\`" + echo "- Reason: \`Browser provisioning failed\`" + echo "- Exit Code: \`$provision_exit_code\`" + echo + echo "## Command" + echo "\`cd devfront && pnpm exec playwright install --with-deps\`" + echo + echo "## Provision Log Tail (last 200 lines)" + echo '```text' + tail -n 200 ../reports/devfront-provision.log + echo '```' + } > ../reports/devfront-test-failure-report.md + exit 1 + fi + + - name: Run devfront tests + working-directory: devfront + env: + PLAYWRIGHT_WORKERS: 2 + run: | + mkdir -p ../reports + set +e + pnpm test 2>&1 | tee ../reports/devfront-test.log + test_exit_code=${PIPESTATUS[0]} + set -e + + if [ "$test_exit_code" -ne 0 ]; then + { + echo "# Devfront Test Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`devfront-tests\`" + echo "- Exit Code: \`$test_exit_code\`" + echo + echo "## Commands" + echo "1. \`cd devfront\`" + echo "2. \`pnpm install -C ../common --no-frozen-lockfile\`" + echo "3. \`pnpm exec playwright install --with-deps\`" + echo "4. \`pnpm test\`" + echo + echo "## Log Tail (last 200 lines)" + echo '```text' + tail -n 200 ../reports/devfront-test.log + echo '```' + } > ../reports/devfront-test-failure-report.md + fi + + exit "$test_exit_code" + + - name: Ensure devfront failure report exists + if: ${{ failure() }} + run: | + mkdir -p reports + if [ -f reports/devfront-test-failure-report.md ]; then + exit 0 + fi + + { + echo "# Devfront Test Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`devfront-tests\`" + echo "- Reason: \`Job failed before detailed report generation\`" + echo + if [ -f reports/devfront-install.log ]; then + echo "## Install Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/devfront-install.log + echo '```' + echo + fi + if [ -f reports/devfront-provision.log ]; then + echo "## Provision Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/devfront-provision.log + echo '```' + echo + fi + if [ -f reports/devfront-test.log ]; then + echo "## Test Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/devfront-test.log + echo '```' + fi + } > reports/devfront-test-failure-report.md + + - name: Publish devfront failure summary + if: ${{ failure() }} + run: | + if [ -f reports/devfront-test-failure-report.md ]; then + cat reports/devfront-test-failure-report.md >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Upload devfront failure report artifact + if: ${{ failure() }} + uses: actions/upload-artifact@v3 + continue-on-error: true + with: + name: devfront-test-failure-report + path: | + reports/devfront-test-failure-report.md + reports/devfront-install.log + reports/devfront-provision.log + reports/devfront-test.log + devfront/playwright-report + devfront/test-results + if-no-files-found: ignore + + orgfront-tests: + needs: + - changes + - lint + if: ${{ always() && needs.changes.outputs.orgfront == 'true' && (github.event_name != 'workflow_dispatch' || inputs.run_orgfront_tests == true) }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + + - name: Get Playwright version + id: playwright-version + run: | + node scripts/playwrightPackageVersion.cjs orgfront >> "$GITHUB_OUTPUT" + + - name: Cache Playwright Browsers + uses: actions/cache@v4 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }} + restore-keys: | + ${{ runner.os }}-playwright- + + - name: Install orgfront dependencies + run: | + mkdir -p reports + set +e + cd orgfront + corepack enable + corepack prepare pnpm@10.5.2 --activate + { + pnpm install -C ../common --no-frozen-lockfile + pnpm install --no-frozen-lockfile + } 2>&1 | tee ../reports/orgfront-install.log + install_exit_code=${PIPESTATUS[0]} + cd .. + set -e + + if [ "$install_exit_code" -ne 0 ]; then + { + echo "# OrgFront Test Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`orgfront-tests\`" + echo "- Reason: \`Dependency install failed\`" + echo "- Exit Code: \`$install_exit_code\`" + echo + echo "## Command" + echo "\`cd orgfront && npm ci\`" + echo + echo "## Install Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/orgfront-install.log + echo '```' + } > reports/orgfront-test-failure-report.md + exit 1 + fi + + - name: Provision browsers for orgfront tests + run: | + set +e + cd orgfront + pnpm exec playwright install --with-deps 2>&1 | tee ../reports/orgfront-provision.log + provision_exit_code=${PIPESTATUS[0]} + cd .. + set -e + + if [ "$provision_exit_code" -ne 0 ]; then + { + echo "# OrgFront Test Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`orgfront-tests\`" + echo "- Reason: \`Browser provisioning failed\`" + echo "- Exit Code: \`$provision_exit_code\`" + echo + echo "## Command" + echo "\`cd orgfront && pnpm exec playwright install --with-deps\`" + echo + echo "## Provision Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/orgfront-provision.log + echo '```' + } > reports/orgfront-test-failure-report.md + exit 1 + fi + + - name: Run orgfront tests + env: + PLAYWRIGHT_WORKERS: 2 + run: | + mkdir -p reports + set +e + cd orgfront + pnpm run test 2>&1 | tee ../reports/orgfront-test.log + test_exit_code=${PIPESTATUS[0]} + cd .. + set -e + + if [ "$test_exit_code" -ne 0 ]; then + { + echo "# OrgFront Test Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`orgfront-tests\`" + echo "- Exit Code: \`$test_exit_code\`" + echo + echo "## Commands" + echo "1. \`cd orgfront\`" + echo "2. \`npm ci\`" + echo "3. \`pnpm exec playwright install --with-deps\`" + echo "4. \`pnpm run test\`" + echo + echo "## Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/orgfront-test.log + echo '```' + } > reports/orgfront-test-failure-report.md + fi + + exit "$test_exit_code" + + - name: Ensure orgfront failure report exists + if: ${{ failure() }} + run: | + mkdir -p reports + if [ -f reports/orgfront-test-failure-report.md ]; then + exit 0 + fi + + { + echo "# OrgFront Test Failure Report" + echo + echo "- Workflow: \`${GITHUB_WORKFLOW:-Code Check}\`" + echo "- Job: \`orgfront-tests\`" + echo "- Reason: \`Job failed before detailed report generation\`" + echo + if [ -f reports/orgfront-install.log ]; then + echo "## Install Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/orgfront-install.log + echo '```' + echo + fi + if [ -f reports/orgfront-provision.log ]; then + echo "## Provision Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/orgfront-provision.log + echo '```' + echo + fi + if [ -f reports/orgfront-test.log ]; then + echo "## Test Log Tail (last 200 lines)" + echo '```text' + tail -n 200 reports/orgfront-test.log + echo '```' + fi + } > reports/orgfront-test-failure-report.md + + - name: Publish orgfront failure summary + if: ${{ failure() }} + run: | + if [ -f reports/orgfront-test-failure-report.md ]; then + cat reports/orgfront-test-failure-report.md >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Upload orgfront failure report artifact + if: ${{ failure() }} + uses: actions/upload-artifact@v3 + continue-on-error: true + with: + name: orgfront-test-failure-report + path: | + reports/orgfront-test-failure-report.md + reports/orgfront-install.log + reports/orgfront-provision.log + reports/orgfront-test.log + orgfront/playwright-report + orgfront/test-results + if-no-files-found: ignore + + badge-updater: + needs: + - changes + - lint + - biome-check + - backend-tests + - userfront-tests + - userfront-flutter-coverage + - userfront-e2e-tests + - adminfront-vitest-coverage + - devfront-vitest-coverage + - orgfront-vitest-coverage + - adminfront-tests + - devfront-tests + - orgfront-tests + if: ${{ always() && needs.changes.outputs.any == 'true' && github.event_name != 'pull_request' && github.ref == 'refs/heads/dev' }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + + - name: Download backend coverage report artifact + uses: actions/download-artifact@v3 + continue-on-error: true + with: + name: backend-coverage-report + path: badge-artifacts/backend + + - name: Download userfront Flutter coverage report artifact + uses: actions/download-artifact@v3 + continue-on-error: true + with: + name: userfront-flutter-coverage-report + path: badge-artifacts/userfront + + - name: Download adminfront Vitest coverage report artifact + uses: actions/download-artifact@v3 + continue-on-error: true + with: + name: adminfront-vitest-coverage-report + path: badge-artifacts/adminfront + + - name: Download devfront Vitest coverage report artifact + uses: actions/download-artifact@v3 + continue-on-error: true + with: + name: devfront-vitest-coverage-report + path: badge-artifacts/devfront + + - name: Download orgfront Vitest coverage report artifact + uses: actions/download-artifact@v3 + continue-on-error: true + with: + name: orgfront-vitest-coverage-report + path: badge-artifacts/orgfront + + - name: Restore published badge state + run: | + git fetch origin "+refs/heads/badges:refs/remotes/origin/badges" || true + if git show-ref --verify --quiet refs/remotes/origin/badges && \ + git cat-file -e refs/remotes/origin/badges:latest/badges.json 2>/dev/null; then + mkdir -p docs/badges + git archive --format=tar refs/remotes/origin/badges latest | tar -x + cp latest/* docs/badges/ + rm -rf latest + else + echo "No published badge state found." + fi + + - name: Update badge files + env: + LINT_RESULT: ${{ needs.lint.result }} + BIOME_RESULT: ${{ needs['biome-check'].result }} + BACKEND_RESULT: ${{ needs['backend-tests'].result }} + BACKEND_COVERAGE_RESULT: ${{ needs['backend-tests'].result }} + USERFRONT_RESULT: ${{ needs['userfront-tests'].result }} + USERFRONT_E2E_RESULT: ${{ needs['userfront-e2e-tests'].result }} + USERFRONT_E2E_FULL: ${{ github.event_name == 'workflow_dispatch' && inputs.run_userfront_e2e_full == true }} + USERFRONT_COVERAGE_RESULT: ${{ needs['userfront-flutter-coverage'].result }} + ADMINFRONT_COVERAGE_RESULT: ${{ needs['adminfront-vitest-coverage'].result }} + DEVFRONT_COVERAGE_RESULT: ${{ needs['devfront-vitest-coverage'].result }} + ORGFRONT_COVERAGE_RESULT: ${{ needs['orgfront-vitest-coverage'].result }} + ADMINFRONT_RESULT: ${{ needs['adminfront-tests'].result }} + DEVFRONT_RESULT: ${{ needs['devfront-tests'].result }} + ORGFRONT_RESULT: ${{ needs['orgfront-tests'].result }} + BADGE_SOURCE_BRANCH: dev + BADGE_SOURCE_SHA: ${{ github.sha }} + run: | + node scripts/update_code_check_badges.mjs + cat docs/badges/badges.json + + - name: Publish badge assets + run: | + if [ -z "$(git status --porcelain docs/badges)" ]; then + echo "No badge changes." + exit 0 + fi + + BADGE_BRANCH=badges + BADGE_WORKTREE="$(mktemp -d)" + BADGE_LATEST_DIR="${BADGE_WORKTREE}/latest" + BADGE_SHA_DIR="${BADGE_WORKTREE}/dev/${GITHUB_SHA}" + trap 'rm -rf "${BADGE_WORKTREE}"' EXIT + + git config user.name "gitea-actions" + git config user.email "gitea-actions@hmac.kr" + + git fetch origin "+refs/heads/${BADGE_BRANCH}:refs/remotes/origin/${BADGE_BRANCH}" || true + if git show-ref --verify --quiet "refs/remotes/origin/${BADGE_BRANCH}"; then + git worktree add --detach "${BADGE_WORKTREE}" "origin/${BADGE_BRANCH}" + else + git worktree add --detach "${BADGE_WORKTREE}" + git -C "${BADGE_WORKTREE}" checkout --orphan "${BADGE_BRANCH}" + git -C "${BADGE_WORKTREE}" rm -rf . || true + fi + + find "${BADGE_WORKTREE}" -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} + + mkdir -p "${BADGE_LATEST_DIR}" "${BADGE_SHA_DIR}" + cp docs/badges/*.svg "${BADGE_LATEST_DIR}/" + cp docs/badges/badges.json "${BADGE_LATEST_DIR}/badges.json" + cp docs/badges/*.svg "${BADGE_SHA_DIR}/" + cp docs/badges/badges.json "${BADGE_SHA_DIR}/badges.json" + + git -C "${BADGE_WORKTREE}" add . + if [ -z "$(git -C "${BADGE_WORKTREE}" status --porcelain)" ]; then + echo "No published badge changes." + exit 0 + fi + + git -C "${BADGE_WORKTREE}" commit -m "chore: publish code check badges [skip ci]" + git -C "${BADGE_WORKTREE}" push origin HEAD:${BADGE_BRANCH} diff --git a/baron-sso/.gitea/workflows/production_release.yml b/baron-sso/.gitea/workflows/production_release.yml new file mode 100644 index 0000000..9c51ad8 --- /dev/null +++ b/baron-sso/.gitea/workflows/production_release.yml @@ -0,0 +1,172 @@ +name: Release Baron SSO to Production + +on: + workflow_dispatch: + inputs: + rc_version_tag: + description: "The version tag to release to production (e.g., v1.2601.1-RC1)" + required: true + type: string + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Login to Harbor Registry + uses: docker/login-action@v3 + with: + registry: ${{ vars.HARBOR_ENDPOINT }} + username: ${{ vars.HARBOR_ROBOT_ACCOUNT }} + password: ${{ secrets.HARBOR_ROBOT_KEY }} + + - name: Parse RC and re-tag to final + id: retag + env: + HARBOR_HOSTNAME: ${{ vars.HARBOR_HOSTNAME }} + HARBOR_USER: ${{ vars.HARBOR_ROBOT_ACCOUNT }} + HARBOR_PASSWORD: ${{ secrets.HARBOR_ROBOT_KEY }} + run: | + BASE_TAG=$(echo "${{ github.event.inputs.rc_version_tag }}" | xargs) + + if [[ ! "$BASE_TAG" =~ ^v[0-9]+\.[0-9]{4}\.[0-9]+-RC[0-9]+$ ]]; then + echo "::error::rc_version_tag must look like vX.YYMM.Z-RC## (got: $BASE_TAG)" + exit 1 + fi + RE_TAG="${BASE_TAG%-RC*}" + echo "Final tag will be: ${RE_TAG}" + + if ! command -v skopeo >/dev/null 2>&1; then + sudo apt-get update -y && sudo apt-get install -y skopeo + fi + + for image in backend userfront adminfront devfront orgfront; do + echo "Re-tagging ${image} image..." + skopeo copy --preserve-digests \ + --src-creds "${HARBOR_USER}:${HARBOR_PASSWORD}" --dest-creds "${HARBOR_USER}:${HARBOR_PASSWORD}" \ + --src-tls-verify=false --dest-tls-verify=false \ + "docker://${HARBOR_HOSTNAME}/baron_sso/${image}:${BASE_TAG}" "docker://${HARBOR_HOSTNAME}/baron_sso/${image}:${RE_TAG}" + done + + echo "final_image_tag=${RE_TAG}" >> "$GITHUB_OUTPUT" + + - name: Setup SSH + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.PROD_SSH_PRIVATE_KEY }} + + - name: Deploy to Production + env: + IMAGE_TAG: ${{ steps.retag.outputs.final_image_tag }} + BACKEND_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/backend + USERFRONT_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/userfront + ADMINFRONT_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/adminfront + DEVFRONT_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/devfront + ORGFRONT_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/orgfront + DEPLOY_PATH: ${{ vars.PROD_DEPLOY_PATH }} + PROD_HOST: ${{ vars.PROD_HOST }} + PROD_USER: ${{ vars.PROD_USER }} + HARBOR_ENDPOINT: ${{ vars.HARBOR_ENDPOINT }} + HARBOR_ROBOT_ACCOUNT: ${{ vars.HARBOR_ROBOT_ACCOUNT }} + HARBOR_ROBOT_KEY: ${{ secrets.HARBOR_ROBOT_KEY }} + run: | + set -euo pipefail + + echo "DEBUG: PROD_USER='${PROD_USER}'" + echo "DEBUG: PROD_HOST='${PROD_HOST}'" + echo "DEBUG: DEPLOY_PATH='${DEPLOY_PATH}'" + + # Sanity check (fail fast with a clear message) + if [ -z "${PROD_USER}" ] || [ -z "${PROD_HOST}" ] || [ -z "${DEPLOY_PATH}" ]; then + echo "::error::Missing required vars (PROD_USER/PROD_HOST/DEPLOY_PATH). Check Gitea repo variables." + exit 1 + fi + + ssh-keyscan -H "${PROD_HOST}" >> ~/.ssh/known_hosts + + ssh "${PROD_USER}@${PROD_HOST}" "mkdir -p '${DEPLOY_PATH}/adminfront'" + + # Create the main .env file for Baron SSO on the remote server + # Note: All values are pulled from Gitea secrets and variables + printf '%s\n' \ + "APP_ENV=production" \ + "TZ=Asia/Seoul" \ + "DB_PORT=${{ vars.PROD_DB_PORT }}" \ + "CLICKHOUSE_PORT_HTTP=${{ vars.PROD_CLICKHOUSE_PORT_HTTP }}" \ + "CLICKHOUSE_PORT_NATIVE=${{ vars.PROD_CLICKHOUSE_PORT_NATIVE }}" \ + "CLICKHOUSE_USER=${{ vars.PROD_CLICKHOUSE_USER }}" \ + "CLICKHOUSE_PASSWORD=${{ secrets.PROD_CLICKHOUSE_PASSWORD }}" \ + "PROD_BACKEND_PORT=${{ vars.PROD_BACKEND_PORT }}" \ + "BACKEND_PORT=3000" \ + "USERFRONT_PORT=${{ vars.PROD_FRONTEND_PORT }}" \ + "ADMINFRONT_PORT=${{ vars.ADMINFRONT_PORT }}" \ + "DEVFRONT_PORT=${{ vars.DEVFRONT_PORT }}" \ + "ORGFRONT_PORT=${{ vars.ORGFRONT_PORT }}" \ + "DB_USER=${{ vars.PROD_DB_USER }}" \ + "DB_PASSWORD=${{ secrets.PROD_DB_PASSWORD }}" \ + "DB_NAME=${{ vars.PROD_DB_NAME }}" \ + "COOKIE_SECRET=${{ secrets.PROD_COOKIE_SECRET }}" \ + "JWT_SECRET=${{ secrets.PROD_JWT_SECRET }}" \ + "REDIS_ADDR=${{ vars.PROD_REDIS_ADDR }}" \ + "NAVER_CLOUD_ACCESS_KEY=${{ vars.NAVER_CLOUD_ACCESS_KEY }}" \ + "NAVER_CLOUD_SECRET_KEY=${{ secrets.NAVER_CLOUD_SECRET_KEY }}" \ + "NAVER_CLOUD_SERVICE_ID=${{ vars.NAVER_CLOUD_SERVICE_ID }}" \ + "NAVER_SENDER_PHONE_NUMBER=${{ vars.NAVER_SENDER_PHONE_NUMBER }}" \ + "AWS_REGION=${{ vars.AWS_REGION }}" \ + "AWS_ACCESS_KEY_ID=${{ vars.AWS_ACCESS_KEY_ID }}" \ + "AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}" \ + "AWS_SES_SENDER=${{ vars.AWS_SES_SENDER }}" \ + "USERFRONT_URL=${{ vars.PROD_FRONTEND_URL }}" \ + "ADMINFRONT_URL=${{ vars.ADMINFRONT_URL }}" \ + "DEVFRONT_URL=${{ vars.DEVFRONT_URL }}" \ + "ORGFRONT_URL=${{ vars.ORGFRONT_URL }}" \ + "BACKEND_URL=${{ vars.PROD_BACKEND_URL }}" \ + "VITE_OIDC_AUTHORITY=${{ vars.VITE_OIDC_AUTHORITY }}" \ + "ADMINFRONT_CALLBACK_URLS=${{ vars.ADMINFRONT_CALLBACK_URLS }}" \ + "DEVFRONT_CALLBACK_URLS=${{ vars.DEVFRONT_CALLBACK_URLS }}" \ + "ORGFRONT_CALLBACK_URLS=${{ vars.ORGFRONT_CALLBACK_URLS }}" \ + > .env + + required_dotenv_keys=" + APP_ENV TZ DB_PORT CLICKHOUSE_PORT_HTTP CLICKHOUSE_PORT_NATIVE CLICKHOUSE_USER CLICKHOUSE_PASSWORD + PROD_BACKEND_PORT BACKEND_PORT USERFRONT_PORT ADMINFRONT_PORT DEVFRONT_PORT ORGFRONT_PORT + DB_USER DB_PASSWORD DB_NAME COOKIE_SECRET JWT_SECRET REDIS_ADDR + NAVER_CLOUD_ACCESS_KEY NAVER_CLOUD_SECRET_KEY NAVER_CLOUD_SERVICE_ID NAVER_SENDER_PHONE_NUMBER + AWS_REGION AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SES_SENDER + USERFRONT_URL ADMINFRONT_URL DEVFRONT_URL ORGFRONT_URL BACKEND_URL VITE_OIDC_AUTHORITY + ADMINFRONT_CALLBACK_URLS DEVFRONT_CALLBACK_URLS ORGFRONT_CALLBACK_URLS + " + for key in ${required_dotenv_keys}; do + if ! grep -Eq "^${key}=.+" .env; then + echo "::error::Missing required production .env value: ${key}. Check Gitea repo variables/secrets." + exit 1 + fi + done + + # Copy compose template and .env file to the remote server + scp adminfront/seed-tenant.csv "${PROD_USER}@${PROD_HOST}:${DEPLOY_PATH}/adminfront/" + scp docker/docker-compose.template.yaml .env "${PROD_USER}@${PROD_HOST}:${DEPLOY_PATH}/" + scp docker/compose.infra.prd.yaml "${PROD_USER}@${PROD_HOST}:${DEPLOY_PATH}/compose.infra.yml" + + # Deploy Baron SSO + echo "${HARBOR_ROBOT_KEY}" | ssh "${PROD_USER}@${PROD_HOST}" \ + "export DEPLOY_PATH='${DEPLOY_PATH}'; \ + export BACKEND_IMAGE_NAME='${BACKEND_IMAGE_NAME}'; \ + export USERFRONT_IMAGE_NAME='${USERFRONT_IMAGE_NAME}'; \ + export ADMINFRONT_IMAGE_NAME='${ADMINFRONT_IMAGE_NAME}'; \ + export DEVFRONT_IMAGE_NAME='${DEVFRONT_IMAGE_NAME}'; \ + export ORGFRONT_IMAGE_NAME='${ORGFRONT_IMAGE_NAME}'; \ + export IMAGE_TAG='${IMAGE_TAG}'; \ + export HARBOR_ENDPOINT='${HARBOR_ENDPOINT}'; \ + export HARBOR_ROBOT_ACCOUNT='${HARBOR_ROBOT_ACCOUNT}'; \ + set -e; \ + cd \"\${DEPLOY_PATH}\"; \ + docker login \"\${HARBOR_ENDPOINT}\" -u \"\${HARBOR_ROBOT_ACCOUNT}\" --password-stdin; \ + set -a; \ + . ./.env; \ + set +a; \ + envsubst < docker-compose.template.yaml > docker-compose.yml; \ + docker compose -f compose.infra.yml -f docker-compose.yml pull; \ + docker compose -f compose.infra.yml -f docker-compose.yml up -d --remove-orphans" diff --git a/baron-sso/.gitea/workflows/staging_build_check.yml b/baron-sso/.gitea/workflows/staging_build_check.yml new file mode 100644 index 0000000..c569137 --- /dev/null +++ b/baron-sso/.gitea/workflows/staging_build_check.yml @@ -0,0 +1,83 @@ +name: Staging Build Check + +on: + pull_request: + paths: + - ".gitea/workflows/staging_build_check.yml" + - "docker/staging_pull_compose.template.yaml" + - "adminfront/**" + - "devfront/**" + - "userfront/**" + - "backend/**" + - "common/**" + - "scripts/**" + - "locales/**" + - "package.json" + - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" + workflow_dispatch: + +jobs: + build-check: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - service: adminfront + - service: devfront + - service: userfront + - service: backend + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Prepare staging build inputs + run: | + set -euo pipefail + + cat <<'EOF' > .env + APP_ENV=stage + TZ=Asia/Seoul + IDP_PROVIDER=ory + ADMINFRONT_URL=https://adminfront.staging.example.com + DEVFRONT_URL=https://devfront.staging.example.com + USERFRONT_URL=https://userfront.staging.example.com + ORGFRONT_URL=https://orgfront.staging.example.com + BACKEND_URL=https://backend.staging.example.com + BACKEND_PUBLIC_URL=https://backend.staging.example.com + VITE_OIDC_AUTHORITY=https://sso.staging.example.com/oidc + WORKS_ADMIN_API_BASE_URL=https://works-admin.staging.example.com/api + WORKS_ADMIN_OAUTH_TOKEN_URL=https://works-admin.staging.example.com/oauth/token + ORY_POSTGRES_USER=ory + ORY_POSTGRES_PASSWORD=ory-password + COOKIE_SECRET=staging-build-cookie-secret + JWT_SECRET=staging-build-jwt-secret + NAVER_CLOUD_ACCESS_KEY=dummy + NAVER_CLOUD_SECRET_KEY=dummy + NAVER_CLOUD_SERVICE_ID=dummy + NAVER_SENDER_PHONE_NUMBER=00000000000 + AWS_REGION=ap-northeast-2 + AWS_ACCESS_KEY_ID=dummy + AWS_SECRET_ACCESS_KEY=dummy + AWS_SES_SENDER=dummy@example.com + REDIS_ADDR=redis:6389 + CLICKHOUSE_PORT_NATIVE=9000 + CLICKHOUSE_USER=baron + CLICKHOUSE_PASSWORD=password + HYDRA_PUBLIC_URL=https://hydra.staging.example.com + KRATOS_BROWSER_URL=https://sso.staging.example.com + KRATOS_ADMIN_URL=http://kratos:4434 + KRATOS_UI_URL=https://sso.staging.example.com + EOF + + cp docker/staging_pull_compose.template.yaml staging_pull_compose.yaml + + - name: Build ${{ matrix.service }} with staging compose + env: + DOCKER_BUILDKIT: "1" + COMPOSE_DOCKER_CLI_BUILD: "1" + run: | + set -euo pipefail + docker compose -f staging_pull_compose.yaml build --pull --progress=plain "${{ matrix.service }}" diff --git a/baron-sso/.gitea/workflows/staging_code_pull.yml b/baron-sso/.gitea/workflows/staging_code_pull.yml new file mode 100644 index 0000000..6b97138 --- /dev/null +++ b/baron-sso/.gitea/workflows/staging_code_pull.yml @@ -0,0 +1,256 @@ +name: Release Baron SSO to Staging + +on: + workflow_dispatch: + inputs: + target_branch: + description: "Branch to deploy" + required: true + default: "dev" + +jobs: + deploy-staging: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup SSH + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.STAGE_SSH_PRIVATE_KEY }} + + - name: Deploy to Staging by git pull + env: + DEPLOY_PATH: ${{ vars.STAGE_DEPLOY_PATH }} + STAGE_HOST: ${{ vars.STAGE_HOST }} + STAGE_USER: ${{ vars.STAGE_USER }} + TARGET_BRANCH: ${{ inputs.target_branch }} + run: | + set -euo pipefail + + echo "DEBUG: STAGE_USER='${STAGE_USER}'" + echo "DEBUG: STAGE_HOST='${STAGE_HOST}'" + echo "DEBUG: DEPLOY_PATH='${DEPLOY_PATH}'" + echo "DEBUG: TARGET_BRANCH='${TARGET_BRANCH}'" + + # Sanity check + if [ -z "${STAGE_USER}" ] || [ -z "${STAGE_HOST}" ] || [ -z "${DEPLOY_PATH}" ] || [ -z "${TARGET_BRANCH}" ]; then + echo "::error::Missing required vars (STAGE_USER/STAGE_HOST/DEPLOY_PATH/TARGET_BRANCH)." + exit 1 + fi + + ssh-keyscan -H "${STAGE_HOST}" >> ~/.ssh/known_hosts + ssh "${STAGE_USER}@${STAGE_HOST}" "mkdir -p '${DEPLOY_PATH}'" + + # .env 파일 생성 + cat <<'EOF' > .env + APP_ENV=stage + BACKEND_LOG_LEVEL=debug + CLIENT_LOG_DEBUG=true + WORKS_ADMIN_API_BASE_URL=${{ vars.WORKS_ADMIN_API_BASE_URL }} + WORKS_ADMIN_OAUTH_TOKEN_URL=${{ vars.WORKS_ADMIN_OAUTH_TOKEN_URL }} + TZ=Asia/Seoul + IDP_PROVIDER=ory + + # DB & Clickhouse + DB_PORT=${{ vars.DB_PORT }} + CLICKHOUSE_PORT_HTTP=${{ vars.CLICKHOUSE_PORT_HTTP }} + CLICKHOUSE_PORT_NATIVE=${{ vars.CLICKHOUSE_PORT_NATIVE }} + CLICKHOUSE_HOST=${{ vars.CLICKHOUSE_HOST }} + CLICKHOUSE_USER=${{ vars.CLICKHOUSE_USER }} + CLICKHOUSE_PASSWORD=${{ secrets.CLICKHOUSE_PASSWORD }} + + + BACKEND_PORT=${{ vars.BACKEND_PORT }} + ADMINFRONT_PORT=${{ vars.ADMINFRONT_PORT }} + DEVFRONT_PORT=${{ vars.DEVFRONT_PORT }} + ORGFRONT_PORT=${{ vars.ORGFRONT_PORT }} + USERFRONT_PORT=${{ vars.USERFRONT_PORT }} + + OATHKEEPER_API_URL=${{ vars.OATHKEEPER_API_URL }} + + DB_USER=${{ vars.DB_USER }} + DB_PASSWORD=${{ secrets.STG_DB_PASSWORD }} + DB_NAME=${{ vars.DB_NAME }} + COOKIE_SECRET=${{ secrets.STG_COOKIE_SECRET }} + JWT_SECRET=${{ secrets.STG_JWT_SECRET }} + REDIS_ADDR=${{ vars.REDIS_ADDR }} + CORS_ALLOWED_ORIGINS=${{ vars.CORS_ALLOWED_ORIGINS }} + AUDIT_WORKER_COUNT=5 + AUDIT_QUEUE_SIZE=2000 + PROFILE_CACHE_TTL=${{ vars.PROFILE_CACHE_TTL }} + ORGFRONT_ORGCHART_CACHE_TTL_SECONDS=${{ vars.ORGFRONT_ORGCHART_CACHE_TTL_SECONDS }} + NAVER_CLOUD_ACCESS_KEY=${{ vars.NAVER_CLOUD_ACCESS_KEY }} + NAVER_CLOUD_SECRET_KEY=${{ secrets.NAVER_CLOUD_SECRET_KEY }} + NAVER_CLOUD_SERVICE_ID=${{ vars.NAVER_CLOUD_SERVICE_ID }} + NAVER_SENDER_PHONE_NUMBER=${{ vars.NAVER_SENDER_PHONE_NUMBER }} + AWS_REGION=${{ vars.AWS_REGION }} + AWS_ACCESS_KEY_ID=${{ vars.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_SES_SENDER=${{ vars.AWS_SES_SENDER }} + ADMIN_EMAIL=${{ vars.ADMIN_EMAIL }} + ADMIN_PASSWORD=${{ secrets.STG_ADMIN_PASSWORD }} + USERFRONT_URL=${{ vars.USERFRONT_URL }} + ADMINFRONT_URL=${{ vars.ADMINFRONT_URL }} + DEVFRONT_URL=${{ vars.DEVFRONT_URL }} + ORGFRONT_URL=${{ vars.ORGFRONT_URL }} + BACKEND_PUBLIC_URL=${{ vars.BACKEND_URL }} + BACKEND_URL=${{ vars.BACKEND_URL }} + OATHKEEPER_PUBLIC_URL=${{ vars.OATHKEEPER_PUBLIC_URL }} + ORY_POSTGRES_TAG=${{ vars.ORY_POSTGRES_TAG }} + ORY_POSTGRES_USER=${{ vars.ORY_POSTGRES_USER }} + ORY_POSTGRES_PASSWORD=${{ secrets.STG_ORY_POSTGRES_PASSWORD }} + ORY_POSTGRES_DB=${{ vars.ORY_POSTGRES_DB }} + KRATOS_DB=${{ vars.KRATOS_DB }} + HYDRA_DB=${{ vars.HYDRA_DB }} + KETO_DB=${{ vars.KETO_DB }} + KRATOS_VERSION=${{ vars.KRATOS_VERSION }} + KRATOS_UI_NODE_VERSION=${{ vars.KRATOS_UI_NODE_VERSION }} + HYDRA_VERSION=${{ vars.HYDRA_VERSION }} + KETO_VERSION=${{ vars.KETO_VERSION }} + ORY_SDK_URL=${{ vars.ORY_SDK_URL }} + KRATOS_PUBLIC_URL=${{ vars.KRATOS_PUBLIC_URL }} + KRATOS_ADMIN_URL=${{ vars.KRATOS_ADMIN_URL }} + KRATOS_BROWSER_URL=${{ vars.KRATOS_BROWSER_URL }} + KRATOS_UI_URL=${{ vars.KRATOS_UI_URL }} + HYDRA_ADMIN_URL=${{ vars.HYDRA_ADMIN_URL }} + HYDRA_PUBLIC_URL=${{ vars.HYDRA_PUBLIC_URL }} + JWKS_URL=${{ vars.JWKS_URL }} + OATHKEEPER_VERSION=${{ vars.OATHKEEPER_VERSION }} + OATHKEEPER_UID=${{ vars.OATHKEEPER_UID }} + OATHKEEPER_GID=${{ vars.OATHKEEPER_GID }} + OATHKEEPER_HEALTH_URL=${{ vars.OATHKEEPER_HEALTH_URL }} + OATHKEEPER_HEALTH_INTERVAL_SECONDS=${{ vars.OATHKEEPER_HEALTH_INTERVAL_SECONDS }} + OATHKEEPER_HEALTH_TIMEOUT_SECONDS=${{ vars.OATHKEEPER_HEALTH_TIMEOUT_SECONDS }} + OATHKEEPER_HEALTH_ENABLED=${{ vars.OATHKEEPER_HEALTH_ENABLED }} + CSRF_COOKIE_NAME=${{ vars.CSRF_COOKIE_NAME }} + CSRF_COOKIE_SECRET=${{ secrets.STG_CSRF_COOKIE_SECRET }} + + # Frontend/Ory URL configs for Staging + VITE_OIDC_AUTHORITY=${{ vars.VITE_OIDC_AUTHORITY }} + ADMINFRONT_CALLBACK_URLS=${{ vars.ADMINFRONT_CALLBACK_URLS }} + DEVFRONT_CALLBACK_URLS=${{ vars.DEVFRONT_CALLBACK_URLS }} + ORGFRONT_CALLBACK_URLS=${{ vars.ORGFRONT_CALLBACK_URLS }} + KRATOS_ALLOWED_RETURN_URLS_JSON=${{ vars.KRATOS_ALLOWED_RETURN_URLS_JSON }} + KRATOS_ALLOWED_RETURN_URLS_EXTRA=${{ vars.KRATOS_ALLOWED_RETURN_URLS_EXTRA }} + # OATHKEEPER_INTROSPECT_CLIENT_ID=${{ vars.OATHKEEPER_INTROSPECT_CLIENT_ID }} + # OATHKEEPER_INTROSPECT_CLIENT_SECRET=${{ secrets.STG_OATHKEEPER_INTROSPECT_CLIENT_SECRET }} + + # Monitoring & Alerts + SMS_WEBHOOK_PORT=${{ vars.SMS_WEBHOOK_PORT || '8080' }} + MONITOR_RECIPIENT_PHONES=${{ vars.MONITOR_RECIPIENT_PHONES || '01012345678,01098765432' }} + LOKI_URL=${{ vars.LOKI_URL || 'http://loki:3100/loki/api/v1/push' }} + EOF + + if ! grep -Eq "^ORGFRONT_ORGCHART_CACHE_TTL_SECONDS=.+" .env; then + sed -i "s/^ORGFRONT_ORGCHART_CACHE_TTL_SECONDS=.*/ORGFRONT_ORGCHART_CACHE_TTL_SECONDS=3600/" .env + fi + + # 코드 업데이트 (Git) + ssh "${STAGE_USER}@${STAGE_HOST}" "mkdir -p '${DEPLOY_PATH}' && cd '${DEPLOY_PATH}' && \ + if [ ! -d .git ]; then + git init + git remote add origin ssh://git@172.16.10.175:222/baron/baron-sso.git + else + git remote set-url origin ssh://git@172.16.10.175:222/baron/baron-sso.git + fi + git fetch --depth 1 origin '${TARGET_BRANCH}' && \ + git reset --hard FETCH_HEAD && \ + git clean -fd && \ + git checkout -B '${TARGET_BRANCH}' FETCH_HEAD" + + # .env 파일 복사 + scp .env "${STAGE_USER}@${STAGE_HOST}:${DEPLOY_PATH}/" + + # 배포 실행 + ssh "${STAGE_USER}@${STAGE_HOST}" "DEPLOY_PATH='${DEPLOY_PATH}' bash -s" <<'EOSSH' + set -euo pipefail + cd "${DEPLOY_PATH}" + set -a; . ./.env; set +a; + + # 네트워크 생성 + for net in baron_net public_net ory-net hydranet kratosnet; do + docker network inspect "${net}" >/dev/null 2>&1 || docker network create "${net}" + done + + + # Ory 컨테이너가 직접 읽는 설정은 env 기반으로 완성한 뒤 mount합니다. + bash scripts/render_ory_config.sh + chmod -R 777 config/.generated/ory || true + + cp docker/staging_pull_compose.template.yaml staging_pull_compose.yaml + + docker compose -f staging_pull_compose.yaml pull + + # 코드 변경 반영을 위해 build 수행 (userfront nginx.conf 등) + docker compose -f staging_pull_compose.yaml build --pull + + docker compose -f staging_pull_compose.yaml up -d --remove-orphans --renew-anon-volumes + docker compose -f staging_pull_compose.yaml up -d --force-recreate kratos hydra keto oathkeeper + docker compose -f staging_pull_compose.yaml up -d --force-recreate ory_stack_check + docker compose -f staging_pull_compose.yaml up -d init-rp + + # 배포 후 상태 확인 (실패 시 로그 출력을 위함) + sleep 10 + + check_container_http() { + name="$1" + port="$2" + max="${FRONTEND_HEALTH_MAX_ATTEMPTS:-60}" + i=1 + while [ "${i}" -le "${max}" ]; do + if docker exec "${name}" sh -c "if command -v wget >/dev/null 2>&1; then wget -qO- 'http://127.0.0.1:${port}/' >/dev/null; elif command -v node >/dev/null 2>&1; then node -e \"fetch('http://127.0.0.1:${port}/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\"; else exit 127; fi" >/dev/null 2>&1; then + echo "Frontend ready: ${name}:${port}" + return 0 + fi + echo "Waiting for frontend: ${name}:${port} (${i}/${max})" + i=$((i + 1)) + sleep 2 + done + echo "ERROR: frontend not ready: ${name}:${port}" >&2 + docker logs "${name}" --tail 200 >&2 || true + return 1 + } + + check_container_url() { + name="$1" + url="$2" + max="${FRONTEND_HEALTH_MAX_ATTEMPTS:-60}" + i=1 + while [ "${i}" -le "${max}" ]; do + if docker exec "${name}" sh -c "if command -v wget >/dev/null 2>&1; then wget -qO- '${url}' >/dev/null; elif command -v node >/dev/null 2>&1; then node -e \"fetch('${url}').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\"; else exit 127; fi" >/dev/null 2>&1; then + echo "Container URL ready: ${name} ${url}" + return 0 + fi + echo "Waiting for container URL: ${name} ${url} (${i}/${max})" + i=$((i + 1)) + sleep 2 + done + echo "ERROR: container URL not ready: ${name} ${url}" >&2 + docker logs "${name}" --tail 200 >&2 || true + return 1 + } + + check_container_url baron_backend http://127.0.0.1:3000/health + check_container_http baron_userfront 5000 + check_container_http baron_gateway 5000 + check_container_http baron_adminfront 5173 + check_container_http baron_devfront 5173 + check_container_http baron_orgfront 5175 + + echo "===== INIT-RP LOGS =====" + docker compose -f staging_pull_compose.yaml logs init-rp || true + echo "========================" + + kratos_migrate_cid="$(docker compose -f staging_pull_compose.yaml ps -q kratos-migrate || true)" + if [ -n "${kratos_migrate_cid}" ]; then + if [ "$(docker inspect -f '{{.State.ExitCode}}' "${kratos_migrate_cid}")" -ne 0 ]; then + echo 'Kratos Migrate Failed. Logs:' + docker logs "${kratos_migrate_cid}" + exit 1 + fi + else + echo "WARN: kratos-migrate container not found; skipping exit-code check." + fi + EOSSH diff --git a/baron-sso/.gitea/workflows/staging_release.yml b/baron-sso/.gitea/workflows/staging_release.yml new file mode 100644 index 0000000..d15d133 --- /dev/null +++ b/baron-sso/.gitea/workflows/staging_release.yml @@ -0,0 +1,238 @@ +name: Release Baron SSO to Staging + +on: + workflow_dispatch: + inputs: + rc_version_tag: + description: "The version tag to deploy to staging (e.g., v1.2601.1-RC1)" + required: true + type: string + +jobs: + deploy-staging: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup SSH + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.STAGE_SSH_PRIVATE_KEY }} + + - name: Deploy to Staging + env: + IMAGE_TAG: ${{ github.event.inputs.rc_version_tag }} + BACKEND_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/backend + USERFRONT_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/userfront + ADMINFRONT_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/adminfront + DEVFRONT_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/devfront + ORGFRONT_IMAGE_NAME: ${{ vars.HARBOR_HOSTNAME }}/baron_sso/orgfront + + # Staging-specific variables + DEPLOY_PATH: ${{ vars.STAGE_DEPLOY_PATH }} + STAGE_HOST: ${{ vars.STAGE_HOST }} + STAGE_USER: ${{ vars.STAGE_USER }} + + HARBOR_ENDPOINT: ${{ vars.HARBOR_ENDPOINT }} + HARBOR_ROBOT_ACCOUNT: ${{ vars.HARBOR_ROBOT_ACCOUNT }} + HARBOR_ROBOT_KEY: ${{ secrets.HARBOR_ROBOT_KEY }} + run: | + set -euo pipefail + + echo "DEBUG: STAGE_USER='${STAGE_USER}'" + echo "DEBUG: STAGE_HOST='${STAGE_HOST}'" + echo "DEBUG: DEPLOY_PATH='${DEPLOY_PATH}'" + + # Sanity check + if [ -z "${STAGE_USER}" ] || [ -z "${STAGE_HOST}" ] || [ -z "${DEPLOY_PATH}" ]; then + echo "::error::Missing required vars (STAGE_USER/STAGE_HOST/DEPLOY_PATH)." + exit 1 + fi + + ssh-keyscan -H "${STAGE_HOST}" >> ~/.ssh/known_hosts + ssh "${STAGE_USER}@${STAGE_HOST}" "mkdir -p '${DEPLOY_PATH}'" + + # .env 파일 생성 + cat <<'EOF' > .env + APP_ENV=stage + BACKEND_LOG_LEVEL=debug + CLIENT_LOG_DEBUG=true + WORKS_ADMIN_API_BASE_URL=${{ vars.WORKS_ADMIN_API_BASE_URL }} + WORKS_ADMIN_OAUTH_TOKEN_URL=${{ vars.WORKS_ADMIN_OAUTH_TOKEN_URL }} + TZ=Asia/Seoul + IDP_PROVIDER=ory + + # DB & Clickhouse + DB_PORT=${{ vars.DB_PORT }} + CLICKHOUSE_PORT_HTTP=${{ vars.CLICKHOUSE_PORT_HTTP }} + CLICKHOUSE_PORT_NATIVE=${{ vars.CLICKHOUSE_PORT_NATIVE }} + CLICKHOUSE_HOST=${{ vars.CLICKHOUSE_HOST }} + CLICKHOUSE_USER=${{ vars.CLICKHOUSE_USER }} + CLICKHOUSE_PASSWORD=${{ secrets.CLICKHOUSE_PASSWORD }} + + + BACKEND_PORT=${{ vars.BACKEND_PORT }} + ADMINFRONT_PORT=${{ vars.ADMINFRONT_PORT }} + DEVFRONT_PORT=${{ vars.DEVFRONT_PORT }} + ORGFRONT_PORT=${{ vars.ORGFRONT_PORT }} + USERFRONT_PORT=${{ vars.USERFRONT_PORT }} + + OATHKEEPER_API_URL=${{ vars.OATHKEEPER_API_URL }} + + DB_USER=${{ vars.DB_USER }} + DB_PASSWORD=${{ secrets.STG_DB_PASSWORD }} + DB_NAME=${{ vars.DB_NAME }} + COOKIE_SECRET=${{ secrets.STG_COOKIE_SECRET }} + JWT_SECRET=${{ secrets.STG_JWT_SECRET }} + REDIS_ADDR=${{ vars.REDIS_ADDR }} + CORS_ALLOWED_ORIGINS=${{ vars.CORS_ALLOWED_ORIGINS }} + AUDIT_WORKER_COUNT=5 + AUDIT_QUEUE_SIZE=2000 + PROFILE_CACHE_TTL=${{ vars.PROFILE_CACHE_TTL }} + ORGFRONT_ORGCHART_CACHE_TTL_SECONDS=${{ vars.ORGFRONT_ORGCHART_CACHE_TTL_SECONDS }} + NAVER_CLOUD_ACCESS_KEY=${{ vars.NAVER_CLOUD_ACCESS_KEY }} + NAVER_CLOUD_SECRET_KEY=${{ secrets.NAVER_CLOUD_SECRET_KEY }} + NAVER_CLOUD_SERVICE_ID=${{ vars.NAVER_CLOUD_SERVICE_ID }} + NAVER_SENDER_PHONE_NUMBER=${{ vars.NAVER_SENDER_PHONE_NUMBER }} + AWS_REGION=${{ vars.AWS_REGION }} + AWS_ACCESS_KEY_ID=${{ vars.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_SES_SENDER=${{ vars.AWS_SES_SENDER }} + ADMIN_EMAIL=${{ vars.ADMIN_EMAIL }} + ADMIN_PASSWORD=${{ secrets.STG_ADMIN_PASSWORD }} + USERFRONT_URL=${{ vars.USERFRONT_URL }} + ORGFRONT_URL=${{ vars.ORGFRONT_URL }} + BACKEND_PUBLIC_URL=${{ vars.BACKEND_URL }} + BACKEND_URL=${{ vars.BACKEND_URL }} + OATHKEEPER_PUBLIC_URL=${{ vars.OATHKEEPER_PUBLIC_URL }} + ORY_POSTGRES_TAG=${{ vars.ORY_POSTGRES_TAG }} + ORY_POSTGRES_USER=${{ vars.ORY_POSTGRES_USER }} + ORY_POSTGRES_PASSWORD=${{ secrets.STG_ORY_POSTGRES_PASSWORD }} + ORY_POSTGRES_DB=${{ vars.ORY_POSTGRES_DB }} + KRATOS_DB=${{ vars.KRATOS_DB }} + HYDRA_DB=${{ vars.HYDRA_DB }} + KETO_DB=${{ vars.KETO_DB }} + KRATOS_VERSION=${{ vars.KRATOS_VERSION }} + KRATOS_UI_NODE_VERSION=${{ vars.KRATOS_UI_NODE_VERSION }} + HYDRA_VERSION=${{ vars.HYDRA_VERSION }} + KETO_VERSION=${{ vars.KETO_VERSION }} + ORY_SDK_URL=${{ vars.ORY_SDK_URL }} + KRATOS_PUBLIC_URL=${{ vars.KRATOS_PUBLIC_URL }} + KRATOS_ADMIN_URL=${{ vars.KRATOS_ADMIN_URL }} + KRATOS_BROWSER_URL=${{ vars.KRATOS_BROWSER_URL }} + KRATOS_UI_URL=${{ vars.KRATOS_UI_URL }} + HYDRA_ADMIN_URL=${{ vars.HYDRA_ADMIN_URL }} + HYDRA_PUBLIC_URL=${{ vars.HYDRA_PUBLIC_URL }} + JWKS_URL=${{ vars.JWKS_URL }} + OATHKEEPER_VERSION=${{ vars.OATHKEEPER_VERSION }} + OATHKEEPER_UID=${{ vars.OATHKEEPER_UID }} + OATHKEEPER_GID=${{ vars.OATHKEEPER_GID }} + OATHKEEPER_HEALTH_URL=${{ vars.OATHKEEPER_HEALTH_URL }} + OATHKEEPER_HEALTH_INTERVAL_SECONDS=${{ vars.OATHKEEPER_HEALTH_INTERVAL_SECONDS }} + OATHKEEPER_HEALTH_TIMEOUT_SECONDS=${{ vars.OATHKEEPER_HEALTH_TIMEOUT_SECONDS }} + OATHKEEPER_HEALTH_ENABLED=${{ vars.OATHKEEPER_HEALTH_ENABLED }} + CSRF_COOKIE_NAME=${{ vars.CSRF_COOKIE_NAME }} + CSRF_COOKIE_SECRET=${{ secrets.STG_CSRF_COOKIE_SECRET }} + + VITE_OIDC_AUTHORITY=${{ vars.VITE_OIDC_AUTHORITY }} + ADMINFRONT_CALLBACK_URLS=${{ vars.ADMINFRONT_CALLBACK_URLS }} + DEVFRONT_CALLBACK_URLS=${{ vars.DEVFRONT_CALLBACK_URLS }} + ORGFRONT_CALLBACK_URLS=${{ vars.ORGFRONT_CALLBACK_URLS }} + # OATHKEEPER_INTROSPECT_CLIENT_ID=${{ vars.OATHKEEPER_INTROSPECT_CLIENT_ID }} + # OATHKEEPER_INTROSPECT_CLIENT_SECRET=${{ secrets.STG_OATHKEEPER_INTROSPECT_CLIENT_SECRET }} + EOF + + if ! grep -Eq "^ORGFRONT_ORGCHART_CACHE_TTL_SECONDS=.+" .env; then + sed -i "s/^ORGFRONT_ORGCHART_CACHE_TTL_SECONDS=.*/ORGFRONT_ORGCHART_CACHE_TTL_SECONDS=3600/" .env + fi + + required_dotenv_keys=" + APP_ENV BACKEND_LOG_LEVEL CLIENT_LOG_DEBUG WORKS_ADMIN_API_BASE_URL WORKS_ADMIN_OAUTH_TOKEN_URL TZ IDP_PROVIDER + DB_PORT CLICKHOUSE_PORT_HTTP CLICKHOUSE_PORT_NATIVE CLICKHOUSE_HOST CLICKHOUSE_USER CLICKHOUSE_PASSWORD + BACKEND_PORT ADMINFRONT_PORT DEVFRONT_PORT ORGFRONT_PORT USERFRONT_PORT OATHKEEPER_API_URL + DB_USER DB_PASSWORD DB_NAME COOKIE_SECRET JWT_SECRET REDIS_ADDR CORS_ALLOWED_ORIGINS PROFILE_CACHE_TTL + ORGFRONT_ORGCHART_CACHE_TTL_SECONDS + NAVER_CLOUD_ACCESS_KEY NAVER_CLOUD_SECRET_KEY NAVER_CLOUD_SERVICE_ID NAVER_SENDER_PHONE_NUMBER + AWS_REGION AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SES_SENDER ADMIN_EMAIL ADMIN_PASSWORD + USERFRONT_URL ORGFRONT_URL BACKEND_PUBLIC_URL BACKEND_URL OATHKEEPER_PUBLIC_URL + ORY_POSTGRES_TAG ORY_POSTGRES_USER ORY_POSTGRES_PASSWORD ORY_POSTGRES_DB KRATOS_DB HYDRA_DB KETO_DB + KRATOS_VERSION KRATOS_UI_NODE_VERSION HYDRA_VERSION KETO_VERSION ORY_SDK_URL KRATOS_PUBLIC_URL + KRATOS_ADMIN_URL KRATOS_BROWSER_URL KRATOS_UI_URL HYDRA_ADMIN_URL HYDRA_PUBLIC_URL JWKS_URL + OATHKEEPER_VERSION OATHKEEPER_UID OATHKEEPER_GID OATHKEEPER_HEALTH_URL OATHKEEPER_HEALTH_INTERVAL_SECONDS + OATHKEEPER_HEALTH_TIMEOUT_SECONDS OATHKEEPER_HEALTH_ENABLED CSRF_COOKIE_NAME CSRF_COOKIE_SECRET + VITE_OIDC_AUTHORITY ADMINFRONT_CALLBACK_URLS DEVFRONT_CALLBACK_URLS ORGFRONT_CALLBACK_URLS + " + for key in ${required_dotenv_keys}; do + if ! grep -Eq "^${key}=.+" .env; then + echo "::error::Missing required staging .env value: ${key}. Check Gitea repo variables/secrets." + exit 1 + fi + done + + # 파일 복사 + ssh "${STAGE_USER}@${STAGE_HOST}" "mkdir -p ${DEPLOY_PATH}/docker" + ssh "${STAGE_USER}@${STAGE_HOST}" "mkdir -p ${DEPLOY_PATH}/adminfront" + ssh "${STAGE_USER}@${STAGE_HOST}" "mkdir -p ${DEPLOY_PATH}/scripts" + + # [중요] docker/ory 폴더 복사 (여기에 init-db/1-createdb.sql이 있어야 함) + scp -r docker/ory "${STAGE_USER}@${STAGE_HOST}:${DEPLOY_PATH}/docker/" + + if [ -d "docker/init-metadata" ]; then + scp -r docker/init-metadata "${STAGE_USER}@${STAGE_HOST}:${DEPLOY_PATH}/docker/" + fi + + if [ -d "gateway" ]; then + scp -r gateway "${STAGE_USER}@${STAGE_HOST}:${DEPLOY_PATH}/" + fi + + scp adminfront/seed-tenant.csv "${STAGE_USER}@${STAGE_HOST}:${DEPLOY_PATH}/adminfront/" + scp scripts/render_ory_config.sh "${STAGE_USER}@${STAGE_HOST}:${DEPLOY_PATH}/scripts/" + scp docker/docker-compose.staging.template.yaml .env "${STAGE_USER}@${STAGE_HOST}:${DEPLOY_PATH}/" + scp docker/compose.infra.yaml "${STAGE_USER}@${STAGE_HOST}:${DEPLOY_PATH}/compose.infra.yml" + scp compose.ory.yaml "${STAGE_USER}@${STAGE_HOST}:${DEPLOY_PATH}/compose.ory.yml" + + # 배포 실행 + echo "${HARBOR_ROBOT_KEY}" | ssh "${STAGE_USER}@${STAGE_HOST}" \ + "export DEPLOY_PATH='${DEPLOY_PATH}'; \ + export BACKEND_IMAGE_NAME='${BACKEND_IMAGE_NAME}'; \ + export USERFRONT_IMAGE_NAME='${USERFRONT_IMAGE_NAME}'; \ + export ADMINFRONT_IMAGE_NAME='${ADMINFRONT_IMAGE_NAME}'; \ + export DEVFRONT_IMAGE_NAME='${DEVFRONT_IMAGE_NAME}'; \ + export ORGFRONT_IMAGE_NAME='${ORGFRONT_IMAGE_NAME}'; \ + export IMAGE_TAG='${IMAGE_TAG}'; \ + export HARBOR_ENDPOINT='${HARBOR_ENDPOINT}'; \ + export HARBOR_ROBOT_ACCOUNT='${HARBOR_ROBOT_ACCOUNT}'; \ + cd \"\${DEPLOY_PATH}\"; \ + docker login \"\${HARBOR_ENDPOINT}\" -u \"\${HARBOR_ROBOT_ACCOUNT}\" --password-stdin; \ + set -a; . ./.env; set +a; \ + + # 네트워크 생성 + for net in baron_net public_net ory-net hydranet kratosnet; do + docker network inspect \"\$net\" >/dev/null 2>&1 || docker network create \"\$net\" + done + + bash scripts/render_ory_config.sh; \ + chmod -R 777 config/.generated/ory || true; \ + + envsubst < docker-compose.staging.template.yaml > docker-compose.yml; \ + + # [중요] 설정 파일 권한 문제 해결 (Ory 이미지는 root가 아닌 사용자로 실행됨) + chmod -R 777 docker/ory + + docker compose -f compose.infra.yml -f compose.ory.yml -f docker-compose.yml pull; \ + + # [주의] DB 초기화 스크립트는 '새로운 볼륨'에서만 실행됨. + # DB 초기화 문제를 확실히 해결하기 위해 기존 볼륨을 날리고 다시 띄움 (데이터 삭제됨 주의) + # 스테이징이므로 초기화 진행. 데이터 보존이 필요하면 이 줄 제거하고 수동으로 DB 만들어야 함. + docker compose -f compose.infra.yml -f compose.ory.yml -f docker-compose.yml down -v || true + + docker compose -f compose.infra.yml -f compose.ory.yml -f docker-compose.yml up -d --remove-orphans; \ + + # 배포 후 상태 확인 (실패 시 로그 출력을 위함) + sleep 10; \ + if [ \$(docker inspect -f '{{.State.ExitCode}}' baron-sso-staging-kratos-migrate-1) -ne 0 ]; then \ + echo 'Kratos Migrate Failed. Logs:'; \ + docker logs baron-sso-staging-kratos-migrate-1; \ + exit 1; \ + fi" diff --git a/baron-sso/.gitea/workflows/userfront_e2e_full_nightly.yml b/baron-sso/.gitea/workflows/userfront_e2e_full_nightly.yml new file mode 100644 index 0000000..01cda41 --- /dev/null +++ b/baron-sso/.gitea/workflows/userfront_e2e_full_nightly.yml @@ -0,0 +1,273 @@ +name: Userfront E2E Full Nightly + +on: + schedule: + - cron: "0 18 * * *" + workflow_dispatch: + +permissions: + contents: write + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.25" + cache-dependency-path: backend/go.sum + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + cache: true + + - name: Run common lint checks + run: | + make code-check-lint + + full-test-policy: + runs-on: ubuntu-latest + outputs: + should_run: ${{ steps.policy.outputs.should_run }} + reason: ${{ steps.policy.outputs.reason }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Decide whether full E2E is needed + id: policy + run: | + set -euo pipefail + + target_sha="${GITHUB_SHA}" + should_run="true" + reason="manual-dispatch" + + if [ "${GITHUB_EVENT_NAME}" = "schedule" ]; then + reason="missing-full-result" + git fetch origin "+refs/heads/badges:refs/remotes/origin/badges" || true + if git show-ref --verify --quiet refs/remotes/origin/badges && \ + git cat-file -e "refs/remotes/origin/badges:dev/${target_sha}/badges.json" 2>/dev/null; then + full_message="$( + git show "refs/remotes/origin/badges:dev/${target_sha}/badges.json" | + node -e "let input=''; process.stdin.on('data', c => input += c); process.stdin.on('end', () => { const data = JSON.parse(input); const keys = ['userfront-chrome', 'userfront-firefox', 'userfront-safari']; const messages = keys.map((key) => data.badges?.[key]?.message || 'unknown'); process.stdout.write(messages.join(',')); });" + )" + if [ -n "${full_message}" ] && ! printf '%s' "${full_message}" | grep -q "unknown"; then + should_run="false" + reason="full-result-exists:${full_message}" + fi + fi + fi + + echo "should_run=${should_run}" >> "$GITHUB_OUTPUT" + echo "reason=${reason}" >> "$GITHUB_OUTPUT" + echo "target_sha=${target_sha}" + echo "should_run=${should_run}" + echo "reason=${reason}" + + userfront-e2e-full: + needs: + - lint + - full-test-policy + if: ${{ needs.lint.result == 'success' && needs.full-test-policy.outputs.should_run == 'true' }} + runs-on: ubuntu-latest + timeout-minutes: 80 + outputs: + chromium_desktop: ${{ steps.full-results.outputs.chromium_desktop }} + chromium_mobile: ${{ steps.full-results.outputs.chromium_mobile }} + firefox_desktop: ${{ steps.full-results.outputs.firefox_desktop }} + firefox_mobile: ${{ steps.full-results.outputs.firefox_mobile }} + webkit_desktop: ${{ steps.full-results.outputs.webkit_desktop }} + webkit_mobile: ${{ steps.full-results.outputs.webkit_mobile }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + cache: "npm" + cache-dependency-path: userfront-e2e/package-lock.json + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + cache: true + + - name: Sync userfront locales + run: | + /bin/sh ./scripts/sync_userfront_locales.sh + + - name: Install userfront-e2e dependencies + run: | + cd userfront-e2e + npm ci + + - name: Build userfront WASM + run: | + cd userfront + rm -rf build/web + flutter build web --wasm --release + cd .. + node userfront/scripts/optimize-web-build.mjs userfront/build/web + + - name: Provision full browser matrix + run: | + cd userfront-e2e + npx playwright install --with-deps + + - name: Run full userfront-e2e tests + id: full-results + run: | + mkdir -p reports + cd userfront-e2e + workers="${PLAYWRIGHT_WORKERS:-4}" + case "$workers" in + ''|*[!0-9]*|0) workers=4 ;; + esac + any_failure=0 + + run_project() { + output_name="$1" + project_name="$2" + log_path="../reports/userfront-e2e-full-${project_name}.log" + + set +e + echo "[userfront-e2e-full] PLAYWRIGHT_WORKERS=${workers} npx playwright test --project=${project_name}" | tee "$log_path" + PLAYWRIGHT_WORKERS="$workers" npx playwright test --project="$project_name" --reporter=list 2>&1 | tee -a "$log_path" + exit_code=${PIPESTATUS[0]} + set -e + + if [ "$exit_code" -eq 0 ]; then + result="success" + else + result="failure" + any_failure=1 + fi + echo "${output_name}=${result}" >> "$GITHUB_OUTPUT" + } + + run_project chromium_desktop chromium-desktop + run_project chromium_mobile chromium-mobile-webapp + run_project firefox_desktop firefox-desktop + echo "firefox_mobile=skipped" >> "$GITHUB_OUTPUT" + run_project webkit_desktop webkit-desktop + run_project webkit_mobile webkit-mobile-webapp + + exit "$any_failure" + + - name: Upload userfront-e2e full artifacts + if: ${{ always() }} + uses: actions/upload-artifact@v3 + continue-on-error: true + with: + name: userfront-e2e-full-report + path: | + reports/userfront-e2e-full-*.log + userfront-e2e/playwright-report + userfront-e2e/test-results + if-no-files-found: ignore + + badge-updater: + needs: + - lint + - full-test-policy + - userfront-e2e-full + if: ${{ always() && needs.lint.result == 'success' && needs.full-test-policy.outputs.should_run == 'true' && github.ref == 'refs/heads/dev' }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + + - name: Restore published badge state + run: | + git fetch origin "+refs/heads/badges:refs/remotes/origin/badges" || true + if git show-ref --verify --quiet refs/remotes/origin/badges && \ + git cat-file -e refs/remotes/origin/badges:latest/badges.json 2>/dev/null; then + mkdir -p docs/badges + git archive --format=tar refs/remotes/origin/badges latest | tar -x + cp latest/* docs/badges/ + rm -rf latest + else + echo "No published badge state found." + fi + + - name: Update full E2E badge files + env: + USERFRONT_E2E_RESULT: ${{ needs.userfront-e2e-full.result }} + USERFRONT_E2E_FULL: "true" + USERFRONT_E2E_CHROMIUM_DESKTOP_RESULT: ${{ needs.userfront-e2e-full.outputs.chromium_desktop }} + USERFRONT_E2E_CHROMIUM_MOBILE_RESULT: ${{ needs.userfront-e2e-full.outputs.chromium_mobile }} + USERFRONT_E2E_FIREFOX_DESKTOP_RESULT: ${{ needs.userfront-e2e-full.outputs.firefox_desktop }} + USERFRONT_E2E_FIREFOX_MOBILE_RESULT: ${{ needs.userfront-e2e-full.outputs.firefox_mobile }} + USERFRONT_E2E_WEBKIT_DESKTOP_RESULT: ${{ needs.userfront-e2e-full.outputs.webkit_desktop }} + USERFRONT_E2E_WEBKIT_MOBILE_RESULT: ${{ needs.userfront-e2e-full.outputs.webkit_mobile }} + BADGE_UPDATE_CODE_CHECK: "false" + BADGE_SOURCE_BRANCH: dev + BADGE_SOURCE_SHA: ${{ github.sha }} + run: | + node scripts/update_code_check_badges.mjs + cat docs/badges/badges.json + + - name: Publish full E2E badge assets + run: | + if [ -z "$(git status --porcelain docs/badges)" ]; then + echo "No badge changes." + exit 0 + fi + + BADGE_BRANCH=badges + BADGE_WORKTREE="$(mktemp -d)" + BADGE_LATEST_DIR="${BADGE_WORKTREE}/latest" + BADGE_SHA_DIR="${BADGE_WORKTREE}/dev/${GITHUB_SHA}" + trap 'rm -rf "${BADGE_WORKTREE}"' EXIT + + git config user.name "gitea-actions" + git config user.email "gitea-actions@hmac.kr" + + git fetch origin "+refs/heads/${BADGE_BRANCH}:refs/remotes/origin/${BADGE_BRANCH}" || true + if git show-ref --verify --quiet "refs/remotes/origin/${BADGE_BRANCH}"; then + git worktree add --detach "${BADGE_WORKTREE}" "origin/${BADGE_BRANCH}" + else + git worktree add --detach "${BADGE_WORKTREE}" + git -C "${BADGE_WORKTREE}" checkout --orphan "${BADGE_BRANCH}" + git -C "${BADGE_WORKTREE}" rm -rf . || true + fi + + find "${BADGE_WORKTREE}" -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} + + mkdir -p "${BADGE_LATEST_DIR}" "${BADGE_SHA_DIR}" + cp docs/badges/*.svg "${BADGE_LATEST_DIR}/" + cp docs/badges/badges.json "${BADGE_LATEST_DIR}/badges.json" + cp docs/badges/*.svg "${BADGE_SHA_DIR}/" + cp docs/badges/badges.json "${BADGE_SHA_DIR}/badges.json" + + git -C "${BADGE_WORKTREE}" add . + if [ -z "$(git -C "${BADGE_WORKTREE}" status --porcelain)" ]; then + echo "No published badge changes." + exit 0 + fi + + git -C "${BADGE_WORKTREE}" commit -m "chore: publish userfront e2e full badge [skip ci]" + git -C "${BADGE_WORKTREE}" push origin HEAD:${BADGE_BRANCH} diff --git a/baron-sso/.gitignore b/baron-sso/.gitignore new file mode 100644 index 0000000..191012c --- /dev/null +++ b/baron-sso/.gitignore @@ -0,0 +1,61 @@ +# General +.env +.env_backup +.temp +.DS_Store +.idea/ +.vscode/ +.codex +.codex/ +.serena/ +.generated/ +config/.generated/ +*.swp +*.log +*.out +*.exe +.npm-cache/ +reports +reports/* +config/*.pem +common/node_modules +common/.baron-deps-install.lock + +# Docker Services Data (Volumes) +postgres_data/ +clickhouse_data/ +docker/ory/oathkeeper/logs/ + +# Backend (Go) +backend/main +backend/bin/ +backend/vendor/ +backend/tmp/ +backend/.env +backend/server + +# userfront (Flutter) +# Note: userfront might have its own .gitignore, but adding here just in case +userfront/build/ +userfront/.dart_tool/ +userfront/.packages +userfront/.pub/ +userfront/.env + +# Frontend test artifacts +adminfront/test-results/ +adminfront/test-results.nobody-backup/ +devfront/test-results/ +orgfront/test-results/ +adminfront/playwright-report/ +devfront/playwright-report/ +orgfront/playwright-report/ +adminfront/coverage/ +devfront/coverage/ +orgfront/coverage/ +orgfront/node_modules/ +orgfront/dist/ +orgfront/.vite/ +.pnpm-store +.playwright-mcp +node_modules diff --git a/baron-sso/Makefile b/baron-sso/Makefile new file mode 100644 index 0000000..99d9249 --- /dev/null +++ b/baron-sso/Makefile @@ -0,0 +1,484 @@ +# Baron SSO용 Docker Compose 헬퍼 + +# 환경 변수 로드 +ifneq (,$(wildcard ./.env)) + include .env + export +endif + +# Compose 파일 경로 +COMPOSE_INFRA := compose.infra.yaml +COMPOSE_ORY := compose.ory.yaml +COMPOSE_APP := docker-compose.yaml +AUTH_CONFIG_ENV := config/.generated/auth-config.env +DEV_SERVICES ?= backend adminfront devfront orgfront userfront +DEV_NETWORKS := baron_net ory-net hydranet kratosnet public_net +INFRA_CONTAINERS := baron_postgres baron_clickhouse baron_redis baron_gateway +ORY_CONTAINERS := ory_postgres ory_kratos ory_hydra ory_keto ory_oathkeeper ory_clickhouse ory_vector +APP_CONTAINERS := baron_backend baron_adminfront baron_devfront baron_orgfront baron_userfront +DROP_CONTAINERS := $(INFRA_CONTAINERS) $(ORY_CONTAINERS) $(APP_CONTAINERS) ory_stack_check + +COMPOSE_CLI_ENV_ARGS := +ifneq (,$(wildcard ./.env)) +COMPOSE_CLI_ENV_ARGS += --env-file .env +endif +COMPOSE_CLI_ENV_ARGS += --env-file $(AUTH_CONFIG_ENV) + +COMPOSE_DROP_ENV_ARGS := +ifneq (,$(wildcard ./.env)) +COMPOSE_DROP_ENV_ARGS += --env-file .env +endif + +DUMP_SERVICES ?= all +RESTORE_SERVICES ?= all +DUMP_MODE ?= maintenance +BACKUP_USE_DOCKER ?= true +BACKUP_TOOLS_IMAGE ?= baron-sso-backup-tools:local +BACKUP_TOOLS_DOCKERFILE ?= docker/backup-tools/Dockerfile +BACKUP_DOCKER_ENV_ARGS := +ifneq (,$(wildcard ./.env)) +BACKUP_DOCKER_ENV_ARGS += --env-file .env +endif +ifneq (,$(wildcard ./$(AUTH_CONFIG_ENV))) +BACKUP_DOCKER_ENV_ARGS += --env-file $(AUTH_CONFIG_ENV) +endif +BACKUP_DOCKER_RUN = docker run --rm $(BACKUP_DOCKER_ENV_ARGS) -e BACKUP_REPO_ROOT=/workspace -v /var/run/docker.sock:/var/run/docker.sock -v "$(CURDIR)":/workspace -v /tmp:/tmp -w /workspace $(BACKUP_TOOLS_IMAGE) + +.PHONY: build-auth-config validate-auth-config verify-auth-config render-ory-config up up-all up-infra up-ory up-app up-backend ensure-networks ensure-infra ensure-ory up-dev up-front-dev dev dev-debug down drop down-app down-backend down-infra down-ory check-infra ps logs-infra logs-ory logs-app backup-tools-build dump restore dump-verify restore-verify dump-list restore-plan upload-cloud dump-upload-cloud + +# --- 인증 설정 빌드/검증 --- +build-auth-config: + @echo "Building auth config..." + @mkdir -p config/.generated + @bash scripts/auth_config.sh build + +validate-auth-config: build-auth-config + @echo "Validating auth config..." + @bash scripts/auth_config.sh validate + +verify-auth-config: validate-auth-config + @echo "Verifying auth config wiring..." + @bash scripts/auth_config.sh verify + +render-ory-config: validate-auth-config + @echo "Rendering Ory config..." + @bash scripts/render_ory_config.sh + +# --- 기본 실행 --- +# 주의: --remove-orphan 사용 금지 (다른 스택이 orphan으로 판단되어 종료될 수 있음) +up: up-all + +up-all: ensure-networks render-ory-config + @echo "Starting ALL stacks (infra + ory + app)..." + docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_INFRA) -f $(COMPOSE_ORY) -f $(COMPOSE_APP) up --build -d + docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_INFRA) -f $(COMPOSE_ORY) -f $(COMPOSE_APP) restart kratos + +# --- 개별 스택 실행 --- +up-infra: ensure-networks + @echo "Starting Infra stack (postgres/clickhouse/redis)..." + docker compose -f $(COMPOSE_INFRA) up -d + +up-ory: ensure-networks render-ory-config + @echo "Starting Ory stack (kratos/hydra/keto/oathkeeper)..." + docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_ORY) up -d + docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_ORY) restart kratos + +up-app: ensure-networks render-ory-config + @echo "Starting App stack (backend/userfront/adminfront/devfront/orgfront)..." + docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_APP) up --build -d + +up-backend: ensure-networks render-ory-config + @echo "Starting Backend only..." + docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_APP) up --build -d backend + +ensure-networks: + @echo "Ensuring Docker networks..." + @for network in $(DEV_NETWORKS); do \ + if ! docker network inspect "$$network" >/dev/null 2>&1; then \ + echo "Creating Docker network $$network..."; \ + docker network create "$$network"; \ + else \ + echo "Docker network $$network already exists."; \ + fi; \ + done + +ensure-infra: ensure-networks + @echo "Ensuring Infra stack..." + @missing=0; \ + for container in $(INFRA_CONTAINERS); do \ + if [ "$$(docker inspect -f '{{.State.Running}}' "$$container" 2>/dev/null)" != "true" ]; then \ + missing=1; \ + break; \ + fi; \ + done; \ + if [ "$$missing" -eq 1 ]; then \ + echo "Starting missing Infra stack containers in daemon mode..."; \ + docker compose -f $(COMPOSE_INFRA) up -d; \ + else \ + echo "Infra stack is already running."; \ + fi + +ensure-ory: ensure-networks render-ory-config + @echo "Ensuring Ory stack..." + @missing=0; \ + for container in $(ORY_CONTAINERS); do \ + if [ "$$(docker inspect -f '{{.State.Running}}' "$$container" 2>/dev/null)" != "true" ]; then \ + missing=1; \ + break; \ + fi; \ + done; \ + if [ "$$missing" -eq 1 ]; then \ + echo "Starting missing Ory stack containers in daemon mode..."; \ + docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_ORY) up -d; \ + else \ + echo "Ory stack is already running. Restarting Kratos to apply rendered dev config..."; \ + docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_ORY) restart kratos; \ + fi + +up-dev: ensure-infra ensure-ory + @echo "Dev stack is up (infra + ory)." + +up-front-dev: up-infra up-ory up-backend + @echo "Dev stack is up (infra + ory + backend)." + +dev: up-dev + @echo "Starting development app containers in foreground attach mode..." + BACKEND_LOG_LEVEL=info CLIENT_LOG_DEBUG=false VITE_CLIENT_LOG_DEBUG=false docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_APP) up --build $(DEV_SERVICES) + +dev-debug: up-dev + @echo "Starting development app containers in foreground attach debug mode..." + BACKEND_LOG_LEVEL=debug CLIENT_LOG_DEBUG=true VITE_CLIENT_LOG_DEBUG=true USERFRONT_FLUTTER_RUN_FLAGS=--debug docker compose $(COMPOSE_CLI_ENV_ARGS) -f $(COMPOSE_APP) up --build $(DEV_SERVICES) + +# --- 종료 (Down) --- +down: + @echo "Stopping ALL stacks (infra + ory + app)..." + docker compose -f $(COMPOSE_INFRA) -f $(COMPOSE_ORY) -f $(COMPOSE_APP) down + +drop: + @echo "Dropping Baron SSO local Docker stack containers, volumes, and local images..." + -docker compose $(COMPOSE_DROP_ENV_ARGS) -f $(COMPOSE_INFRA) -f $(COMPOSE_ORY) -f $(COMPOSE_APP) down -v --rmi local + @echo "Removing any remaining fixed-name Baron SSO containers..." + @for container in $(DROP_CONTAINERS); do \ + docker rm -f "$$container" >/dev/null 2>&1 || true; \ + done + @echo "Drop complete. External Docker networks are preserved." + +down-app: + @echo "Stopping App stack..." + docker compose -f $(COMPOSE_APP) down + +down-backend: + @echo "Stopping Backend only..." + docker compose -f $(COMPOSE_APP) stop backend + +down-infra: + @echo "Stopping Infra stack..." + docker compose -f $(COMPOSE_INFRA) down + +down-ory: + @echo "Stopping Ory stack..." + docker compose -f $(COMPOSE_ORY) down + +# --- 유틸리티 --- +# 인프라 상태 확인 +check-infra: + @echo "Checking infra status..." + @if [ "$$(docker inspect -f '{{.State.Health.Status}}' baron_postgres 2>/dev/null)" != "healthy" ]; then \ + echo "Error: PostgreSQL is not running or not healthy."; \ + echo "Please run 'make up-infra' first."; \ + exit 1; \ + else \ + echo "PostgreSQL is healthy."; \ + fi + +ps: + docker compose -f $(COMPOSE_INFRA) -f $(COMPOSE_ORY) -f $(COMPOSE_APP) ps + +logs-infra: + docker compose -f $(COMPOSE_INFRA) logs -f + +logs-ory: + docker compose -f $(COMPOSE_ORY) logs -f + +logs-app: + docker compose -f $(COMPOSE_APP) logs -f + +# --- 백업/복구 --- +backup-tools-build: + docker build -f $(BACKUP_TOOLS_DOCKERFILE) -t $(BACKUP_TOOLS_IMAGE) . + +ifeq ($(BACKUP_USE_DOCKER),true) +dump: backup-tools-build + $(BACKUP_DOCKER_RUN) bash -lc 'DUMP_SERVICES="$(DUMP_SERVICES)" DUMP_MODE="$(DUMP_MODE)" BACKUP="$(BACKUP)" BACKUP_ROOT="$(BACKUP_ROOT)" scripts/backup/dump.sh' + +restore: backup-tools-build + $(BACKUP_DOCKER_RUN) bash -lc 'BACKUP="$(BACKUP)" DUMP_FILE="$(DUMP_FILE)" RESTORE_SERVICES="$(RESTORE_SERVICES)" CONFIRM_RESTORE="$(CONFIRM_RESTORE)" ALLOW_NON_EMPTY_RESTORE="$(ALLOW_NON_EMPTY_RESTORE)" RESTORE_REPORT="$(RESTORE_REPORT)" scripts/backup/restore.sh' + +dump-verify: backup-tools-build + $(BACKUP_DOCKER_RUN) bash -lc 'BACKUP="$(BACKUP)" scripts/backup/verify-dump.sh' + +restore-verify: backup-tools-build + $(BACKUP_DOCKER_RUN) bash -lc 'BACKUP="$(BACKUP)" scripts/backup/verify-restore.sh' + +dump-list: backup-tools-build + $(BACKUP_DOCKER_RUN) bash -lc 'BACKUP_ROOT="$(BACKUP_ROOT)" scripts/backup/dump-list.sh' + +restore-plan: backup-tools-build + $(BACKUP_DOCKER_RUN) bash -lc 'BACKUP="$(BACKUP)" DUMP_FILE="$(DUMP_FILE)" RESTORE_SERVICES="$(RESTORE_SERVICES)" CONFIRM_RESTORE="$(CONFIRM_RESTORE)" RESTORE_REPORT="$(RESTORE_REPORT)" scripts/backup/restore-plan.sh' + +upload-cloud: backup-tools-build + $(BACKUP_DOCKER_RUN) bash -lc 'WORKS_DRIVE_DRY_RUN="$(WORKS_DRIVE_DRY_RUN)" BACKUP="$(BACKUP)" scripts/backup/upload_cloud.sh' +else +dump: + DUMP_SERVICES="$(DUMP_SERVICES)" DUMP_MODE="$(DUMP_MODE)" BACKUP="$(BACKUP)" BACKUP_ROOT="$(BACKUP_ROOT)" scripts/backup/dump.sh + +restore: + BACKUP="$(BACKUP)" DUMP_FILE="$(DUMP_FILE)" RESTORE_SERVICES="$(RESTORE_SERVICES)" CONFIRM_RESTORE="$(CONFIRM_RESTORE)" ALLOW_NON_EMPTY_RESTORE="$(ALLOW_NON_EMPTY_RESTORE)" RESTORE_REPORT="$(RESTORE_REPORT)" scripts/backup/restore.sh + +dump-verify: + BACKUP="$(BACKUP)" scripts/backup/verify-dump.sh + +restore-verify: + BACKUP="$(BACKUP)" scripts/backup/verify-restore.sh + +dump-list: + BACKUP_ROOT="$(BACKUP_ROOT)" scripts/backup/dump-list.sh + +restore-plan: + BACKUP="$(BACKUP)" DUMP_FILE="$(DUMP_FILE)" RESTORE_SERVICES="$(RESTORE_SERVICES)" CONFIRM_RESTORE="$(CONFIRM_RESTORE)" RESTORE_REPORT="$(RESTORE_REPORT)" scripts/backup/restore-plan.sh + +upload-cloud: + WORKS_DRIVE_DRY_RUN="$(WORKS_DRIVE_DRY_RUN)" BACKUP="$(BACKUP)" scripts/backup/upload_cloud.sh +endif + +dump-upload-cloud: dump upload-cloud + +# --- 로컬 통합 코드 체크 --- +PLAYWRIGHT_BROWSERS_PATH := $(HOME)/.cache/ms-playwright +PLAYWRIGHT_CHROMIUM_COMPLETE := $(PLAYWRIGHT_BROWSERS_PATH)/chromium-1208/INSTALLATION_COMPLETE +PLAYWRIGHT_FIREFOX_COMPLETE := $(PLAYWRIGHT_BROWSERS_PATH)/firefox-1509/INSTALLATION_COMPLETE +PLAYWRIGHT_WEBKIT_COMPLETE := $(PLAYWRIGHT_BROWSERS_PATH)/webkit-2248/INSTALLATION_COMPLETE + +PLAYWRIGHT_INSTALL_ALL := sh -c 'if [ -f "$(PLAYWRIGHT_CHROMIUM_COMPLETE)" ] && [ -f "$(PLAYWRIGHT_FIREFOX_COMPLETE)" ] && [ -f "$(PLAYWRIGHT_WEBKIT_COMPLETE)" ]; then echo "Playwright browsers already installed"; else npx playwright install; fi' +PLAYWRIGHT_INSTALL_CHROMIUM := sh -c 'if [ -f "$(PLAYWRIGHT_CHROMIUM_COMPLETE)" ]; then echo "Playwright chromium already installed"; else npx playwright install chromium; fi' + +.PHONY: code-check code-check-lint code-check-test-jobs code-check-i18n code-check-i18n-values code-check-go-lint code-check-sync-userfront-locales code-check-userfront-install code-check-userfront-lint code-check-front-lint code-check-backend-tests code-check-userfront-tests code-check-adminfront-tests code-check-devfront-tests code-check-orgfront-tests code-check-userfront-e2e-tests + +CODE_CHECK_TEST_JOBS ?= 1 +PLAYWRIGHT_WORKERS ?= 1 +FLUTTER_TEST_CONCURRENCY ?= 1 + +code-check: code-check-lint code-check-test-jobs + @echo "code-check complete." + +code-check-lint: code-check-i18n code-check-i18n-values code-check-front-lint code-check-go-lint code-check-sync-userfront-locales code-check-userfront-install code-check-userfront-lint + +code-check-test-jobs: + @echo "==> run CI-equivalent test jobs (parallel)" + @$(MAKE) --no-print-directory -j$(CODE_CHECK_TEST_JOBS) --output-sync=target \ + code-check-backend-tests \ + code-check-userfront-tests \ + code-check-userfront-e2e-tests \ + code-check-adminfront-tests \ + code-check-devfront-tests \ + code-check-orgfront-tests + +code-check-i18n: + @echo "==> i18n resource check" + @mkdir -p reports + node tools/i18n-scanner/index.js + node tools/i18n-scanner/report.js + @cat reports/i18n-report.txt + +code-check-i18n-values: + @echo "==> i18n value quality check" + @mkdir -p reports + node tools/i18n-scanner/value-check.js + @cat reports/i18n-value-report.txt + +code-check-go-lint: + @echo "==> go lint/format check" + @if command -v golangci-lint >/dev/null 2>&1; then \ + cd backend && golangci-lint fmt -E gofmt -E gofumpt -d; \ + elif command -v docker >/dev/null 2>&1; then \ + docker run --rm \ + -v "$$(pwd)/backend:/app" \ + -w /app \ + golangci/golangci-lint:v2.10.1 \ + golangci-lint fmt -E gofmt -E gofumpt -d; \ + else \ + echo "ERROR: golangci-lint not found and docker is unavailable."; \ + echo "Install golangci-lint v2.10.1 or Docker to match CI lint step."; \ + exit 1; \ + fi + +code-check-sync-userfront-locales: + @echo "==> sync userfront locales" + /bin/sh ./scripts/sync_userfront_locales.sh + +code-check-userfront-install: + @echo "==> install userfront dependencies" + @if command -v flutter >/dev/null 2>&1; then \ + cd userfront && flutter pub get; \ + else \ + echo "WARNING: flutter not found, skipping userfront dependencies install."; \ + fi + +code-check-userfront-lint: + @echo "==> userfront format/analyze" + @if command -v dart >/dev/null 2>&1; then \ + cd userfront && dart format --output=none --set-exit-if-changed lib test; \ + else \ + echo "WARNING: dart not found, skipping userfront format check."; \ + fi + @if command -v flutter >/dev/null 2>&1; then \ + cd userfront && flutter analyze --no-fatal-warnings --no-fatal-infos; \ + else \ + echo "WARNING: flutter not found, skipping userfront analyze."; \ + fi + +code-check-front-lint: + @echo "==> adminfront biome lint/format check" + rm -rf adminfront/playwright-report adminfront/test-results + cd adminfront && CI=true npx pnpm install --frozen-lockfile --ignore-scripts + cd adminfront && npx biome lint . + cd adminfront && npx biome format . + @echo "==> devfront biome lint/format check" + rm -rf devfront/playwright-report devfront/test-results + @if [ -d devfront/node_modules ]; then \ + echo "devfront/node_modules already present; skipping npm install."; \ + else \ + cd devfront && npm ci --ignore-scripts; \ + fi + cd devfront && npx biome lint . + cd devfront && npx biome format . + @echo "==> orgfront biome lint/format check" + rm -rf orgfront/playwright-report orgfront/test-results + cd orgfront && npm ci --ignore-scripts + cd orgfront && npx biome lint . + cd orgfront && npx biome format . + +code-check-backend-tests: + @echo "==> backend tests" + cd backend && GOCACHE=/tmp/baron-sso-go-cache go test -v ./... + +code-check-userfront-tests: + @echo "==> userfront tests (isolated workspace)" + @if ! command -v flutter >/dev/null 2>&1; then \ + echo "WARNING: flutter not found, skipping userfront tests."; \ + exit 0; \ + fi; \ + tmp_dir="$$(mktemp -d /tmp/baron-sso-userfront-tests.XXXXXX)"; \ + trap 'rm -rf "$$tmp_dir"' EXIT INT TERM; \ + mkdir -p "$$tmp_dir/scripts"; \ + cp scripts/sync_userfront_locales.sh "$$tmp_dir/scripts/"; \ + cp -R locales "$$tmp_dir/locales"; \ + if command -v rsync >/dev/null 2>&1; then \ + rsync -a --delete \ + --exclude '.dart_tool' \ + --exclude 'build' \ + --exclude '.pub-cache' \ + --exclude '.flutter-plugins' \ + --exclude '.flutter-plugins-dependencies' \ + userfront/ "$$tmp_dir/userfront/"; \ + else \ + cp -R userfront "$$tmp_dir/userfront"; \ + rm -rf "$$tmp_dir/userfront/.dart_tool" "$$tmp_dir/userfront/build"; \ + fi; \ + cd "$$tmp_dir" && /bin/sh ./scripts/sync_userfront_locales.sh; \ + cd "$$tmp_dir/userfront" && flutter test --concurrency=$(FLUTTER_TEST_CONCURRENCY) + +code-check-adminfront-tests: + @echo "==> adminfront tests" + PLAYWRIGHT_WORKERS=$(PLAYWRIGHT_WORKERS) ./scripts/run_adminfront_ci_tests.sh adminfront-tests + +code-check-devfront-tests: + @echo "==> devfront tests" + @mkdir -p reports/devfront + @rm -rf reports/devfront/playwright-report reports/devfront/test-results + @status=0; \ + preview_pattern='[v]ite preview --host 127.0.0.1 --strictPort --port 4174'; \ + pkill -f "$$preview_pattern" >/dev/null 2>&1 || true; \ + trap 'pkill -f "$$preview_pattern" >/dev/null 2>&1 || true' EXIT INT TERM; \ + if [ -d devfront/node_modules ]; then \ + echo "devfront/node_modules already present; skipping npm install."; \ + else \ + (cd devfront && npm ci --ignore-scripts) || status=$$?; \ + fi; \ + if [ $$status -eq 0 ]; then \ + (cd devfront && $(PLAYWRIGHT_INSTALL_ALL)) || status=$$?; \ + fi; \ + if [ $$status -eq 0 ]; then \ + (cd devfront && PLAYWRIGHT_WORKERS=$(PLAYWRIGHT_WORKERS) npm test) || status=$$?; \ + fi; \ + [ -d devfront/playwright-report ] && cp -R devfront/playwright-report reports/devfront/ || true; \ + [ -d devfront/test-results ] && cp -R devfront/test-results reports/devfront/ || true; \ + exit $$status + +code-check-orgfront-tests: + @echo "==> orgfront tests" + @mkdir -p reports/orgfront + @rm -rf reports/orgfront/playwright-report reports/orgfront/test-results + @status=0; \ + (cd orgfront && npm ci --ignore-scripts) || status=$$?; \ + if [ $$status -eq 0 ]; then \ + (cd orgfront && $(PLAYWRIGHT_INSTALL_ALL)) || status=$$?; \ + fi; \ + if [ $$status -eq 0 ]; then \ + (cd orgfront && PLAYWRIGHT_WORKERS=$(PLAYWRIGHT_WORKERS) npm test) || status=$$?; \ + fi; \ + [ -d orgfront/playwright-report ] && cp -R orgfront/playwright-report reports/orgfront/ || true; \ + [ -d orgfront/test-results ] && cp -R orgfront/test-results reports/orgfront/ || true; \ + exit $$status + +code-check-userfront-e2e-tests: + @echo "==> userfront wasm playwright e2e tests (isolated workspace)" + @if ! command -v flutter >/dev/null 2>&1; then \ + echo "WARNING: flutter not found, skipping userfront e2e tests."; \ + exit 0; \ + fi; \ + mkdir -p reports/userfront-e2e; \ + rm -rf reports/userfront-e2e/playwright-report reports/userfront-e2e/test-results; \ + tmp_dir="$$(mktemp -d /tmp/baron-sso-userfront-e2e-tests.XXXXXX)"; \ + trap 'rm -rf "$$tmp_dir"' EXIT INT TERM; \ + mkdir -p "$$tmp_dir/scripts"; \ + cp scripts/sync_userfront_locales.sh "$$tmp_dir/scripts/"; \ + cp -R locales "$$tmp_dir/locales"; \ + if command -v rsync >/dev/null 2>&1; then \ + rsync -a --delete \ + --exclude '.dart_tool' \ + --exclude 'build' \ + --exclude '.pub-cache' \ + --exclude '.flutter-plugins' \ + --exclude '.flutter-plugins-dependencies' \ + userfront/ "$$tmp_dir/userfront/"; \ + rsync -a --delete \ + --exclude 'node_modules' \ + --exclude 'playwright-report' \ + --exclude 'test-results' \ + userfront-e2e/ "$$tmp_dir/userfront-e2e/"; \ + else \ + cp -R userfront "$$tmp_dir/userfront"; \ + rm -rf "$$tmp_dir/userfront/.dart_tool" "$$tmp_dir/userfront/build"; \ + cp -R userfront-e2e "$$tmp_dir/userfront-e2e"; \ + rm -rf "$$tmp_dir/userfront-e2e/node_modules" "$$tmp_dir/userfront-e2e/playwright-report" "$$tmp_dir/userfront-e2e/test-results"; \ + fi; \ + status=0; \ + (cd "$$tmp_dir" && /bin/sh ./scripts/sync_userfront_locales.sh) || status=$$?; \ + if [ $$status -eq 0 ]; then \ + (cd "$$tmp_dir/userfront-e2e" && npm ci) || status=$$?; \ + fi; \ + if [ $$status -eq 0 ]; then \ + (cd "$$tmp_dir/userfront" && flutter build web --wasm --release) || status=$$?; \ + fi; \ + if [ $$status -eq 0 ]; then \ + (cd "$$tmp_dir/userfront-e2e" && $(PLAYWRIGHT_INSTALL_ALL)) || status=$$?; \ + fi; \ + if [ $$status -eq 0 ]; then \ + port="$$(node -e "const net=require('node:net'); const s=net.createServer(); s.listen(0,'127.0.0.1',()=>{console.log(s.address().port); s.close();});")"; \ + echo "==> userfront-e2e using PORT=$$port"; \ + (cd "$$tmp_dir/userfront-e2e" && PORT=$$port PLAYWRIGHT_WORKERS=$(PLAYWRIGHT_WORKERS) npm test) || status=$$?; \ + fi; \ + [ -d "$$tmp_dir/userfront-e2e/playwright-report" ] && cp -R "$$tmp_dir/userfront-e2e/playwright-report" reports/userfront-e2e/ || true; \ + [ -d "$$tmp_dir/userfront-e2e/test-results" ] && cp -R "$$tmp_dir/userfront-e2e/test-results" reports/userfront-e2e/ || true; \ + exit $$status diff --git a/baron-sso/README.md b/baron-sso/README.md new file mode 100644 index 0000000..50e459e --- /dev/null +++ b/baron-sso/README.md @@ -0,0 +1,875 @@ +# Baron SSO + +[![dev](https://gitea.hmac.kr/baron/baron-sso/raw/branch/badges/latest/dev-sha.svg)](https://gitea.hmac.kr/baron/baron-sso/src/branch/dev) [![Code Check](https://gitea.hmac.kr/baron/baron-sso/raw/branch/badges/latest/code-check.svg)](https://gitea.hmac.kr/baron/baron-sso/actions/workflows/code_check.yml?branch=dev) [![Biome](https://gitea.hmac.kr/baron/baron-sso/raw/branch/badges/latest/biome.svg)](https://gitea.hmac.kr/baron/baron-sso/actions/workflows/code_check.yml?branch=dev) [![backend](https://gitea.hmac.kr/baron/baron-sso/raw/branch/badges/latest/backend-tests.svg)](https://gitea.hmac.kr/baron/baron-sso/actions/workflows/code_check.yml?branch=dev) +[![userfront](https://gitea.hmac.kr/baron/baron-sso/raw/branch/badges/latest/userfront.svg)](https://gitea.hmac.kr/baron/baron-sso/actions/workflows/code_check.yml?branch=dev) [![adminfront](https://gitea.hmac.kr/baron/baron-sso/raw/branch/badges/latest/adminfront.svg)](https://gitea.hmac.kr/baron/baron-sso/actions/workflows/code_check.yml?branch=dev) [![devfront](https://gitea.hmac.kr/baron/baron-sso/raw/branch/badges/latest/devfront.svg)](https://gitea.hmac.kr/baron/baron-sso/actions/workflows/code_check.yml?branch=dev) [![orgfront](https://gitea.hmac.kr/baron/baron-sso/raw/branch/badges/latest/orgfront.svg)](https://gitea.hmac.kr/baron/baron-sso/actions/workflows/code_check.yml?branch=dev) +[![chrome](https://gitea.hmac.kr/baron/baron-sso/raw/branch/badges/latest/userfront-chrome.svg)](https://gitea.hmac.kr/baron/baron-sso/actions/workflows/userfront_e2e_full_nightly.yml?branch=dev) [![firefox](https://gitea.hmac.kr/baron/baron-sso/raw/branch/badges/latest/userfront-firefox.svg)](https://gitea.hmac.kr/baron/baron-sso/actions/workflows/userfront_e2e_full_nightly.yml?branch=dev) [![safari](https://gitea.hmac.kr/baron/baron-sso/raw/branch/badges/latest/userfront-safari.svg)](https://gitea.hmac.kr/baron/baron-sso/actions/workflows/userfront_e2e_full_nightly.yml?branch=dev) + +badge는 `Code Check`가 `badges` 브랜치의 `latest/`와 `dev//`에 발행합니다. 최신 HTML/LCOV/JSON summary는 Gitea `Code Check`의 패키지별 `*-vitest-coverage-report` artifact에서 확인할 수 있습니다. + +**Baron 로그인**은 화이트 라벨링된 가족사의 모든 소프트웨어 Auth를 총괄하는 사용자 인증/인가 허브입니다. + +## 📂 프로젝트 구조 (Project Structure) + +``` +baron_sso/ +├── backend/ # Go Fiber 애플리케이션 +│ ├── cmd/server/ # 진입점 (Entry point) +│ ├── internal/ # 도메인, 핸들러, 저장소(Repository) +│ └── Dockerfile +├── userfront/ # Flutter 애플리케이션 +│ ├── src/ # UI 및 로직 +│ └── pubspec.yaml +├── adminfront/ # React 기반 관리 +│ ├── src/ # UI 및 로직 +│ └── pubspec.yaml +├── gateway/ # Nginx 기반 Gateway (UserFront 프록시) +├── compose.ory-stack.yaml # DB 서비스 (Postgres, ClickHouse) +├── compose.infra.yaml # DB 서비스 (Postgres, ClickHouse) +├── docker-compose.yaml # 앱 서비스 (Front, Back) +├── .env.sample # 환경 설정 템플릿 +└── README.md # 본 파일 +``` +* Ory Stack으로 모든 구성요소를 self-hosting 합니다. +* Backend는 Go (Fiber)로 구성된 Ory Stack의 유일한 Command 전송 포인트입니다. 모든 Command는 ClickHouse로 강제 전송되며 Audit Log 시스템을 구성합니다. +* Front는 Backend를 통해서만 연동하며 자체가 Ory Stack의 RP기도 합니다. 크게 3개 계층으로 분리됩니다. + * UserFront: Flutter로 구현된 커스텀 UI를 통해 매끄러운 사용자 경험을 보장합니다. + * 로그인 : 비밀번호, SMS, QR 스캔 등의 수단으로 로그인 가능 + * 향후 모바일 앱 지원으로 인증 Push 승인 등 MFA 확장 (예정) + * 정보변경, 앱 연동 이력 확인 등 개인화 기능 + * 사용 가능한 앱 리스트 (예정) + * AdminFront: 사용자 관리 등 Admin 기능 + * DevFront: RP 관리 등 개발자 기능 + + +## 🏗 아키텍처 (Architecture) + +### 0. Ory Stack +- Ory Kratos: 사용자 인증/계정 관리(Identity). +- Ory Hydra: OAuth2/OIDC 발급 및 토큰 관리. +- Ory Keto: 권한/정책 기반 접근 제어. +- Oathkeeper: 인증/인가 프록시 및 라우팅 게이트웨이. + +```mermaid +flowchart + subgraph Edge + OK["Oathkeeper
(Only Public Entry)"] + end + + subgraph App + BE["Backend
(Only Upstream)"] + end + + subgraph OryStack + KR[Kratos] + HY[Hydra] + KE[Keto] + KR --- HY --- KE + end + BE -->|Command| OryStack + OK -->|Query| KR + OK -->|Query| HY + OK -->|Query| KE +``` + +### 1. Backend (Go Fiber) +- **Language**: Go 1.26.2+ +- **Framework**: Fiber v2.25+ +- **Database**: + - **ClickHouse**: 감사 로그 (고성능 데이터 수집) + - **PostgreSQL**: 메타데이터 저장소 (Primary) +- **Features**: + - 인증용 SMS 발송 등 Ory-Stack으로 구현 어려운 부분 직접 구현 + - `POST /api/v1/audit`: 감사 로그 수집 API + - userfront가 바라보는 backend + +### 2. UserFront(Flutter Web/App) +- **Framework**: Flutter 3.38.0+ +- **Key Packages**: `flutter_riverpod`, `go_router` +- **Features**: + - 탭 기반 로그인 UI (비밀번호 기반 / 링크 기반 / QR 기반 등) + +### 3. adminfront(Web) +- **Framework**: Vite, React 19+, Shadcn/ui 등 +- **Features**: + - 사용자 관리, 권한 부여 등 관리자 기능 + - 앱 별 사용량(호출량) 등 통계 + - 핵심 Audit 대상 + +### 4. devfront(Web) +- **Framework**: Vite, React 19+, Shadcn/ui 등 +- **Features**: + - RP 등록 및 관리 + - RP별 Consent 관리 + +## 관리 데이터 Export/Import 정책 + +AdminFront의 테넌트와 사용자 export/import는 운영자가 CSV를 직접 검토하고 재반입할 수 있는 흐름을 기준으로 설계합니다. 기본 원칙은 내부 UUID를 불필요하게 노출하지 않고, 사람이 이해하기 쉬운 `slug`와 이름을 우선 사용하는 것입니다. + +### 공통 원칙 +- CSV는 Excel 호환을 위해 UTF-8 BOM을 포함해 내려받습니다. +- 기본 export는 시스템 내부 ID를 제외합니다. +- 같은 데이터를 정확히 재동기화해야 하는 운영 작업에서는 `includeIds=true` 옵션으로 내부 ID 컬럼을 포함할 수 있습니다. +- import는 preview/검토 단계를 거친 뒤 실행하는 것을 기본으로 합니다. +- 기존 데이터와 충돌 가능성이 있는 row는 자동 적용하지 않고 관리자 선택 또는 확인 상태로 표시합니다. +- 삭제는 export/import로 암묵 처리하지 않습니다. 삭제가 필요하면 별도 삭제 기능을 사용합니다. + +### Tenant Export +- 기본 컬럼은 운영자가 다시 import하기 쉬운 형태를 유지합니다. +- `includeIds=false`가 기본이며, 이 경우 내부 `tenant_id`는 제외합니다. +- `includeIds=true`를 사용하면 기존 테넌트 update 또는 staging/production 간 매핑 확인에 필요한 ID를 포함합니다. +- 주요 의미: + - `tenant_id`: 내부 UUID. 기본 export에서는 제외됩니다. + - `name`: 테넌트 표시 이름입니다. + - `type`: `PERSONAL`, `COMPANY`, `COMPANY_GROUP`, `USER_GROUP` 중 하나입니다. + - `parent_tenant_id`: 상위 테넌트 내부 ID입니다. + - `parent_tenant_slug`: 상위 테넌트를 slug로 연결할 때 사용합니다. + - `slug`: 운영상 사람이 다루는 테넌트 식별자입니다. + - `memo`: 설명 또는 비고입니다. + - `email_domain`: 테넌트에 연결된 이메일 도메인입니다. 여러 도메인은 `;`, `,`, 줄바꿈으로 구분할 수 있습니다. + +### Tenant Import +- 필수 컬럼은 `name`, `type`, `slug`입니다. +- 허용되는 header alias: + - `tenant_id`: `id`, `tenantid`, `tenant_id` + - `parent_tenant_id`: `parentid`, `parent_id`, `parenttenantid`, `parent_tenant_id` + - `parent_tenant_slug`: `parenttenantslug`, `parent_tenant_slug` + - `memo`: `memo`, `description` + - `email_domain`: `email-domain`, `emaildomain`, `email_domain`, `domain`, `domains` +- `tenant_id`가 있고 기존 테넌트가 있으면 update 대상으로 봅니다. +- `tenant_id`가 없으면 `slug` 기준으로 기존 테넌트를 찾고, 없으면 신규 생성 후보로 봅니다. +- `parent_tenant_slug`가 같은 import 파일 안에 있으면 부모 row를 먼저 처리하도록 정렬합니다. +- import preview는 이름/slug 유사도 기반 후보를 보여주며, 관리자가 기존 테넌트 사용, 신규 생성, skip 중 선택할 수 있어야 합니다. +- 외부 시스템에서 가져온 `tenant_id`처럼 현재 DB에 없는 ID는 충돌로 표시하고, 관리자가 새 slug 또는 기존 테넌트 매핑을 결정해야 합니다. + +### User Export +- 기본 컬럼은 `Email`, `Name`, `Phone`, `Status`, `tenant_slug`, `Position`, `JobTitle`, `CreatedAt`입니다. +- `includeIds=true`이면 `user_id`, `tenant_id`를 함께 포함합니다. +- 사용자 role은 export 기본 컬럼에서 제외합니다. role은 일괄 변경의 실수 위험이 크므로 명시적 관리 화면 또는 별도 정책으로 다룹니다. +- 사용자 metadata는 `Meta:` 컬럼으로 뒤에 추가됩니다. +- `includeIds=false`일 때는 `id`, `user_id`, `tenant_id`, `tenantid` 성격의 metadata key를 export에서 제외합니다. +- tenant admin의 export는 관리 가능한 테넌트 범위로 제한됩니다. + +### User Import +- 사용자 CSV의 기본 컬럼은 `email`, `name`, `phone`, `role`, `tenant_slug`, `department`, `position`, `jobTitle`입니다. +- `email`과 `name`은 CSV parsing 단계의 필수값입니다. +- backend 생성 단계에서는 `tenantSlug`도 필수입니다. +- `tenant`, `tenant_slug`, `companyCode` header는 사용자 소속 테넌트 slug로 매핑됩니다. +- `tenant_id`, `tenant_name`, `tenant_type`, `parent_tenant_id`, `parent_tenant_slug`, `parent_tenant_name`, `tenant_memo`, `email_domain` 컬럼이 있으면 사용자 import 과정에서 필요한 테넌트 생성/매핑 preview에 사용합니다. +- 위 기본 컬럼에 속하지 않는 컬럼은 사용자 metadata로 들어갑니다. +- 테넌트에 `userSchema`가 있으면 import 중 metadata required/validation/loginId 규칙을 적용합니다. +- 테넌트 schema에서 `isLoginId`로 지정된 metadata 값은 custom login ID로 동기화하며, 이메일/전화번호/예약어와 충돌하지 않아야 합니다. + +### 한맥가족 User Import Email 정책 +- 전체 시스템에서 `users.email`은 unique입니다. +- `active`, `temporary_leave`, `suspended`, `preboarding`, `baron_guest`, `extended_leave`, `archived` 등 모든 사용자 상태가 unique 검사 대상입니다. 특히 `preboarding`, `baron_guest`, `archived` 사용자는 email/local-part 선점 대상입니다. +- 한맥가족 테넌트 root(`hanmac-family`)와 그 하위 subtree에서는 이메일 도메인과 무관하게 `@` 앞 local-part도 unique 해야 합니다. +- 예: `han@hanmaceng.co.kr`가 한맥가족 구성원으로 있으면 `han@samaneng.com`은 한맥가족 구성원으로 생성할 수 없습니다. +- `email` 값이 `@hanmaceng.co.kr`처럼 도메인만 있으면 import preview에서 이름 기반 local-part를 제안합니다. +- 이름 기반 local-part 기본 규칙은 `이름 부분 초성 + 성 로마자`입니다. + - 예: `한치영` -> `치영`의 초성 `c + y` + 성 `han` -> `cyhan` +- 이미 `cyhan`, `cyhan1`이 있으면 다음 후보인 `cyhan2`를 제안합니다. +- 외부 로마자화 패키지는 backend 의존성으로 추가하지 않고, 내부 한글 음절 분해와 성씨/초성 매핑을 사용합니다. +- import preview의 row 상태: + - `valid`: unique와 이름 기반 권장 규칙을 모두 만족합니다. + - `suggested`: 도메인만 있거나 suffix 제안이 필요한 row입니다. + - `needsReview`: 이름 매핑이 애매해 관리자가 직접 확인해야 합니다. + - `ruleMismatch`: 최종 local-part가 `이름 이니셜 + 성 + 숫자 suffix` 규칙과 다릅니다. 예외 진행은 가능하지만 관리자에게 표시해야 합니다. + - `blockingError`: local-part 중복, email 형식 오류, 필수값 누락처럼 생성을 차단해야 하는 상태입니다. +- 단건 사용자 생성은 한맥가족 local-part 중복 시 자동 제안하지 않고 `409 Conflict`로 차단합니다. +- bulk import는 preview에서 제안/수정된 최종 email을 사용하되, backend가 생성 직전에 다시 unique 규칙을 검증합니다. + +### User Status 정책 +| 상태 | 표시명 | Baron 사용 | Works 처리 | 일반 조직도 | +| --- | --- | --- | --- | --- | +| `active` | 재직 | 가능 | 생성/갱신 | 노출 | +| `temporary_leave` | 단기휴무 | 가능 | 계정 유지 | 노출 | +| `suspended` | 정지 | 불가 | suspend | 노출 | +| `preboarding` | 입사대기 | 불가 | 생성 안 함 | 비노출 | +| `baron_guest` | Baron 게스트 | 가능 | 생성 금지, 기존 계정 delete/deprovision | 비노출 | +| `extended_leave` | 장기휴직 | 불가 | delete/deprovision | 비노출 | +| `archived` | 보관 | 불가 | delete/deprovision | 비노출 | + +- 기존 `inactive` 입력은 `preboarding`으로, `leave_of_absence` 입력은 `temporary_leave`로 호환 처리합니다. +- 이슈 #862의 초기 명칭 `baron_only`는 구현 명칭으로 사용하지 않고 `baron_guest`로 정리합니다. +- backend bootstrap은 남아 있는 legacy `users.status` 값을 `inactive -> preboarding`, `leave_of_absence -> temporary_leave`, `baron_only -> baron_guest`로 자동 정규화합니다. +- `archived` 사용자는 과거 이력 보존용 계정이며 AdminFront 같은 관리자 화면에서만 감사/운영/중복 확인 목적으로 조회할 수 있습니다. + + +### 4. 주요 시나리오 (Core Scenarios) +1. **Same Browser SSO**: Baron 로그인 서비스에 로그인된 상태에서 런처를 통해 타 앱/서비스로 이동 (자동 로그인). + 1.1. 단 약관동의(Consent) 이력이 없으면 Consent 단계로 이동 +2. **Cross-Device Auth**: 이메일 SMS 등의 수단으로 링크를 전달받고 해당 링크를 사용자가 클릭하면 최초 로그인 요청한 세션이 활성화 + 2.1 향후 App Push 등 2차 인증 강화수단 검토 필요 +3. **QR Login**: 최초 진입 시 사전 로그인되어 있는 웹/앱을 이용해 QR 코드를 스캔하여, QR코드가 로딩된 Device를 로그인 상태로 전환 + +### 5. Headless Login ID/Password Flow +- 목적: headless login을 허용한 클라이언트가 자체 로그인 화면에서 `ID/password`를 수집하되, Baron Backend가 OIDC 로그인 흐름만 계속 진행하고 RP에는 `sessionJwt`를 직접 넘기지 않습니다. +- 대상 엔드포인트: `POST /api/v1/auth/headless/password/login` +- 관련 구현: + - `backend/internal/handler/auth_handler.go` + - `backend/internal/domain/hydra_models.go` + - `backend/internal/handler/auth_handler_login_test.go` + +#### 호출 순서 +1. RP 브라우저가 Hydra Public의 `/oauth2/auth`를 호출해 OIDC 인증을 시작합니다. +2. Hydra가 로그인 단계로 넘긴 `login_challenge`를 RP가 확보합니다. +3. RP backend가 자기 private key로 `client_assertion` JWT를 서명합니다. +4. RP backend가 Baron Backend의 `POST /api/v1/auth/headless/password/login`에 `client_id`, `client_assertion`, `login_challenge`, `loginId`, `password`를 전송합니다. +5. Baron Backend가 Hydra login request와 RP 설정을 검증한 뒤 Kratos sign-in 및 Hydra login accept를 수행합니다. +6. 성공 시 Baron Backend는 `redirectTo`만 반환하고, RP 브라우저는 그 URL로 이동해 OIDC 흐름을 이어갑니다. + +#### 요청 바디 +```json +{ + "client_id": "headless-login-client", + "client_assertion": "", + "login_challenge": "", + "loginId": "employee001", + "password": "secret" +} +``` + +#### 성공 응답 +```json +{ + "status": "ok", + "provider": "ory", + "redirectTo": "https://rp.example.com/callback?code=..." +} +``` + +#### RP / Hydra 선행 조건 +- Hydra login request의 `client.client_id`와 요청 바디의 `client_id`가 반드시 같아야 합니다. +- client가 headless login 선행 조건을 만족해야 합니다. + - `headless_token_endpoint_auth_method == "private_key_jwt"` 또는 top-level `token_endpoint_auth_method == "private_key_jwt"` + - `headless_jwks_uri`가 존재해야 합니다. + - inline `headless_jwks`는 더 이상 지원하지 않습니다. +- `headless_login_enabled == true`가 필요합니다. +- `metadata.status == "inactive"`인 client는 차단됩니다. + +#### `client_assertion` 규칙 +- 구현상 `client_assertion`은 현재 필수입니다. +- 허용 서명 알고리즘: + - `RS256`, `RS384`, `RS512` + - `PS256`, `PS384`, `PS512` + - `ES256`, `ES384`, `ES512` + - `EdDSA` +- JWT claim의 `iss`와 `sub`는 모두 `client_id`와 같아야 합니다. +- `exp`는 현재 시각 이후여야 합니다. +- `nbf`, `iat`가 있으면 미래 시각이면 안 됩니다. +- `aud`는 다음 둘 중 하나와 일치해야 합니다. + - `https:///api/v1/auth/headless/password/login` + - `/api/v1/auth/headless/password/login` +- 서명 검증용 public key는 `headless_jwks_uri`에서만 읽습니다. + +#### 내부 JWKS 캐시 정책 +- Baron Backend는 `headless_jwks_uri`를 직접 외부 스펙으로 저장하고, 실제 JWKS 문서는 내부 캐시에 저장해 사용합니다. +- 등록/수정 이후에는 내부 캐시 동기화를 시도하고, 성공/실패 상태를 DevFront에서 확인할 수 있습니다. +- 로그인 시 재조회는 다음 조건으로 제한합니다. + - 캐시에 `kid`가 없을 때 + - `kid`는 있지만 서명 검증이 실패할 때 + - 캐시 TTL이 만료되었을 때 +- 그 외에는 내부 캐시를 사용합니다. +- 백그라운드 worker가 TTL보다 짧은 주기로 `jwksUri`를 선제 점검해 첫 사용자 실패를 줄입니다. + +#### DevFront 운영 액션 +- Settings > `Public Key Registration` 카드에서 다음 정보를 확인할 수 있습니다. + - 최근 JWKS 캐시 갱신 시각 + - 최근 검증 성공 시각 + - 최근 에러와 연속 실패 횟수 + - 현재 cached `kid` 목록 + - 파싱된 key summary + - `kid` + - `kty` + - `use` + - `alg` + - RSA key의 `n` preview (앞/뒤 일부만 표시) +- 수동 운영 액션: + - `Refresh JWKS Cache` + - `Revoke JWKS Cache` +- RP가 키를 교체했으면 실제 트래픽 전에 `Refresh JWKS Cache`를 먼저 호출하는 것을 권장합니다. + +#### 일반 로그인과의 차이 +- `POST /api/v1/auth/password/login` + - UserFront 기본 비밀번호 로그인용입니다. + - `login_challenge`가 없으면 `sessionJwt`를 반환합니다. + - `login_challenge`가 있으면 Hydra accept 후 `redirectTo`를 반환합니다. +- `POST /api/v1/auth/headless/password/login` + - headless login 허용 클라이언트 전용입니다. + - `client_assertion` 검증이 추가됩니다. + - 항상 `sessionJwt` 없이 `redirectTo`만 반환합니다. + +#### 실패 패턴 요약 +- `400 bad_request` + - 필수 필드 누락 + - `client_assertion` 누락 +- `401 invalid_client_assertion` + - `jwksUri` 조회 실패 + - 서명 불일치 + - `aud`/`iss`/`sub`/`exp` 검증 실패 +- `401 invalid_client_assertion` with explicit message + - `Headless login requires jwksUri. Inline jwks is not supported.` + - `Configured jwksUri returned no keys for headless login.` + - `Failed to refresh headless login jwks from jwksUri.` +- `403 forbidden` + - `client_id` 불일치 + - `headless_login_enabled` 미설정 + - inactive client +- `401 password_or_email_mismatch` + - 사용자 인증 실패 + + +### 전체 연결 구조도 + +```mermaid +flowchart TD + subgraph Clients ["External Clients"] + AF[adminfront] + DF[devfront] + UF["userfront"] + DS["일반SW"] + end + + subgraph AppService ["Control Plane"] + BE["Backend (Command/Audit Controller)"] + end + + subgraph OryBundle ["Ory Deployment Stack"] + direction TB + OK["Oathkeeper (Public Proxy/OIDC)"] + + subgraph OryEngines ["Ory Services"] + direction LR + HY["Hydra"] + KR["Kratos"] + KE["Keto"] + end + + ICH[(Internal Clickhouse)] + + %% Internal Flow within Bundle + OK -->|Routing/Queries| OryEngines + OK -.->|Access/Usage Log| ICH + end + + subgraph AuditDB ["Audit Storage"] + ECH[(External Clickhouse)] + end + + %% Key Command Path + AF & DF & UF & DS ==>|Actions / Commands| BE + + %% Backend Responsibilities + BE -->|Admin/State Control| OryEngines + BE -.->|Mandatory Audit Log| ECH + + %% Connection Note (Hidden flow mentioned in logic) + %% OK is technically the entry for OIDC, but removed as per request + + %% Styles + style OryBundle fill:#f8f9fa,stroke:#333,stroke-width:2px + style BE fill:#bbf,stroke:#333,stroke-width:2px + style ECH fill:#fdd,stroke:#333 + style ICH fill:#dfd,stroke:#333 + style OK fill:#f9f,stroke:#333 + style OryEngines fill:#fff,stroke:#999,stroke-dasharray: 5 5 +``` + + +Kratos가 사용자 SoT이며 Hydra는 순수 OIDC 토큰 엔진입니다. 비지니스로직은 Backend를 통해서, 기본 인증 로직은 Ory Stack을 통해 진행됩니다. + +### SSOT 및 Redis Cache 전략 + +Baron SSO는 “하나의 DB가 모든 데이터의 원본”인 구조가 아닙니다. 데이터 성격별로 원장이 다르며, Backend는 원장 쓰기 경로와 감사 로그를 중앙화하는 Control Plane입니다. Redis와 PostgreSQL projection은 성능과 운영 편의를 위한 read model/cache로만 사용하고, 원장과 불일치할 수 있다는 전제를 명시합니다. + +#### 데이터별 원본 위치 + +| 데이터 | SSOT | 보조 저장소/캐시 | 비고 | +| --- | --- | --- | --- | +| Identity subject, credentials, recovery/verification address | Ory Kratos `identities` | Redis identity mirror, PostgreSQL `users.id` 참조 | Kratos identity ID가 사용자 subject이며 WORKS `externalKey` 기준입니다. | +| 로그인 식별자 | Kratos traits, `user_login_ids` | Redis identity mirror | Kratos는 인증 식별자, PostgreSQL은 중복/정책 검증용 index입니다. | +| 사용자 이름, 이메일, 전화번호, role 기본값 | Kratos traits | PostgreSQL `users`, Redis mirror | 인증/profile 계산에 필요한 최소 identity 값만 Kratos에 유지합니다. | +| Baron 사용자 상태, soft delete, 운영 메타데이터 | PostgreSQL `users`, `users.metadata` | Redis mirror 조합 응답 | `users.deleted_at`은 Baron 운영 상태이며 Kratos identity 삭제와 같은 의미가 아닙니다. | +| 테넌트 tree, slug, 조직/부서/직무/직책 | PostgreSQL `tenants`, `users`, membership metadata | Redis/API response cache 가능 | 관계형 조직 데이터는 Kratos traits가 아니라 Backend DB가 원장입니다. | +| 권한/관계 | Ory Keto relation tuple | PostgreSQL outbox/status | Backend를 통해 relation command를 보내고 처리 상태를 추적합니다. | +| OAuth2/OIDC client, consent, token state | Ory Hydra | PostgreSQL `client_consents`, audit/read model | Hydra가 프로토콜 원장이며 로컬 테이블은 운영 조회/감사용입니다. | +| RP별 사용자 custom claim 값 | PostgreSQL `rp_user_metadata` | ID token/userinfo projection | RP 관리자 범위 데이터이며 전역 claim과 분리합니다. | +| 전역 사용자 custom claim 값 | PostgreSQL `users.metadata.global_custom_claims` | ID token projection | 전체 사용자 대상 claim으로 adminfront 사용자 상세에서만 관리합니다. | +| WORKS Mobile mapping/outbox/job 상태 | PostgreSQL `worksmobile_*` | WORKS API 비교 응답 cache 가능 | 외부 SaaS 연동 상태이며 identity 원장이 아닙니다. | +| 감사 로그/사용량 | ClickHouse, Oathkeeper/Ory 로그 | 화면별 summary cache 가능 | command와 보안 이벤트의 감사 원장입니다. | +| Headless JWKS 검증 상태 | Redis `headless:jwks:*` cache | DevFront 상태 카드 | RP public key 문서 자체는 외부 `jwksUri`가 원본입니다. | +| 로그인 코드, pending login, verification token | Redis short-lived key | 없음 | 만료 가능한 휘발성 상태입니다. 백업/복구 대상이 아닙니다. | + +#### SSOT 보장 원칙 + +1. Kratos/Hydra/Keto/WORKS로 향하는 쓰기 command는 Backend를 통과합니다. +2. Backend는 원장 write 성공 후 원장 ID를 기준으로 재조회하고, PostgreSQL read model 또는 Redis mirror를 write-through 갱신합니다. +3. write-through 갱신 실패 시 원장 write를 되돌린 것으로 간주하지 않습니다. 대신 mirror/cache 상태를 `stale` 또는 `failed`로 표시하고 drift report와 refresh 대상으로 둡니다. +4. Kratos Admin API 또는 Kratos DB를 Backend 밖에서 직접 수정하는 경로는 운영 정책상 금지합니다. 정비/DR처럼 예외가 필요한 경우에는 Redis mirror를 stale로 표시하고, full refresh와 drift report를 완료하기 전까지 cache 결과를 신뢰하지 않습니다. +5. PostgreSQL projection은 Kratos partial list를 full snapshot처럼 취급하지 않습니다. Kratos 목록 조회가 partial이면 로컬 사용자를 삭제/숨김 처리하지 않습니다. +6. frontend 대량 조회는 cursor 기반을 원칙으로 합니다. `limit=5000&offset=0` 같은 단일 대량 offset 조회는 사용자 수가 늘면 partial data를 전체처럼 보이게 만들 수 있으므로 신규 구현에서 금지합니다. +7. Redis cache miss가 발생한 단건 조회는 가능한 경우 SSOT로 fallback하고, fallback 성공 시 Redis를 갱신합니다. 목록 조회는 mirror 상태가 `ready`가 아니면 화면/API에 경고 상태를 함께 전달해야 합니다. + +#### Redis 사용 원칙 + +Redis는 원장이 아니라 cache/mirror 계층입니다. Redis 데이터 유실은 장애지만 데이터 유실 사고로 보지 않고, 원장 재조회와 refresh로 재수렴해야 합니다. + +| Redis 데이터 | 역할 | TTL/보존 정책 | 장애 시 처리 | +| --- | --- | --- | --- | +| `identity:mirror:{identityID}` | Kratos identity summary 단건 cache | 장기 mirror. refresh 상태와 함께 운영 | Kratos `GetIdentity` fallback 후 write-through | +| `identity:index:*` | identity 목록/검색 cursor index | mirror refresh 주기로 재작성 | `stale` 표시 후 full refresh | +| `identity:mirror:state` | mirror 상태, count, last error | 영구 상태 key | adminfront에서 경고 표시 | +| `headless:jwks:*` | RP headless login JWKS cache | JWKS TTL과 prefetch 정책 | kid miss/검증 실패/TTL 만료 시 재조회 | +| login/verification/pending 계열 key | 인증 흐름의 단기 상태 | 짧은 TTL 필수 | 만료 또는 유실 시 사용자가 흐름 재시작 | +| 일반 API response cache | 선택적 성능 cache | 짧은 TTL, invalidation 우선 | miss 시 Backend DB 또는 Ory 원장 조회 | + +운영 Redis 설정은 `maxmemory`와 `maxmemory_policy`가 명시되어야 합니다. identity mirror처럼 재수렴 가능한 데이터와 pending login처럼 사용자 흐름에 영향을 주는 단기 key가 같은 Redis를 공유하므로, eviction 발생 여부와 TTL 없는 key 증가를 운영 화면에서 볼 수 있어야 합니다. + +#### Redis 모니터링 계획 + +Redis 적정 설정 판단에 필요한 운영 지표를 adminfront에 노출하는 후속 작업은 이슈 [#1046](https://gitea.hmac.kr/baron/baron-sso/issues/1046)으로 분리했습니다. + +표시 대상은 Redis 연결/버전/uptime, `used_memory`, `maxmemory`, `maxmemory_policy`, keyspace hit/miss, expired/evicted keys, prefix별 key count, TTL 분포, `identity:mirror:state`, headless JWKS cache failure 요약입니다. 이 화면은 `super_admin` 전용으로 두고, Redis key value 자체는 노출하지 않습니다. + +--- + +## 🚀 시작하기 (Getting Started) + +### 사전 요구사항 (Prerequisites) +- Docker & Docker Compose +- Flutter SDK (로컬 개발용, 3.38.0+) +- Go (로컬 백엔드 개발용) + +### 환경 설정 (Environment Setup) +1. 예제 환경 설정 파일을 복사합니다. + ```bash + cp .env.sample .env + ``` +2. `.env`를 작성합니다. (아래 작성 규칙 필수) + +### `.env` 작성 규칙 (중요) +- `KEY=value` 한 줄만 사용하고, **값 뒤에 같은 줄 주석을 붙이지 않습니다.** +- 주석이 필요하면 반드시 **윗줄에 별도 주석 라인**으로 작성합니다. +- URL 값 끝에 공백이 들어가면 Hydra/Kratos 기동 실패로 이어질 수 있습니다. + +잘못된 예: +```env +USERFRONT_URL=https://sso.example.com # 이렇게 같은 줄 주석 금지 +``` + +올바른 예: +```env +# UserFront 공개 URL +USERFRONT_URL=https://sso.example.com +``` + +### `.env` 핵심 변수 가이드 +- `IDP_PROVIDER`: 기본 `ory` +- `USERFRONT_URL`: 브라우저 기준 공개 도메인 (예: `https://sso.example.com`) +- `OATHKEEPER_PUBLIC_URL`: 보통 `${USERFRONT_URL}` +- `HYDRA_PUBLIC_URL`: 보통 `${OATHKEEPER_PUBLIC_URL}/oidc` +- `KRATOS_BROWSER_URL`: 보통 `${OATHKEEPER_PUBLIC_URL}/auth` +- `KRATOS_UI_URL`: UserFront UI URL (로컬 예: `http://localhost:5000`) +- `ADMINFRONT_CALLBACK_URLS`: 콤마 구분 콜백 목록 (예: `http://localhost:5173/auth/callback`) +- `DEVFRONT_CALLBACK_URLS`: 콤마 구분 콜백 목록 (예: `http://localhost:5174/auth/callback`) + - 주의: callback URL 끝에 `/`가 붙으면 `make validate-auth-config`에서 실패 처리됩니다. +- `KRATOS_ALLOWED_RETURN_URLS_EXTRA`: 추가 허용 return URL (선택) + - 빈값: `[]` + - 다중값: `["https://a.example.com/callback","https://b.example.com/callback"]` 또는 `https://a.example.com/callback,https://b.example.com/callback` +- `KRATOS_ALLOWED_RETURN_URLS_JSON`: stage/prod에서 권장하는 전체 허용 return URL 목록 + - 공개 도메인, `/ko`, `/en`, `/auth/callback`, `/ko/auth/callback`, `/en/auth/callback`, 각 front callback을 포함해야 합니다. +- `CLIENT_LOG_DEBUG`: 클라이언트 로그 디버그 모드 강제 (기본: 비운영 `true`, 운영 `false`) + - 운영(`APP_ENV=production|prod`)에서 `true|1|on|yes` 설정 시 `INFO/DEBUG` 클라이언트 로그 수집 허용 + - 미설정(기본) 시 운영에서는 `WARN/ERROR`만 수집 +- `BACKEND_LOG_LEVEL`: Backend `slog` 레벨 override (선택) + - 허용 값: `debug`, `info`, `warn`, `error` + - 미설정 시 `APP_ENV` 기준으로 결정됩니다. + - `dev|local|development`: `debug` + - 그 외(`stage`, `production`, `prod` 등): `info` +- `USERFRONT_DEBUG_LOG`: UserFront 측 디버그 로그 fallback 플래그 + - `CLIENT_LOG_DEBUG`가 없을 때만 UserFront에서 대체로 참조 + +### 클라이언트 로그 정책 (중요) +- 기본 원칙: 운영 환경에서는 민감정보 보호를 우선하며, 과도한 로그 수집을 제한합니다. +- 환경별 동작: + - 비운영(`dev/local/stage` 등): 디버그 로그 허용 (`INFO/DEBUG/WARN/ERROR`) + - 운영(`production/prod`) + `CLIENT_LOG_DEBUG` 미설정: `WARN/ERROR`만 수집 + - 운영(`production/prod`) + `CLIENT_LOG_DEBUG=true`: 디버그 로그 허용 +- 민감정보는 환경과 무관하게 마스킹합니다. + - 예: `password`, `newPassword`, `token`, `authorization`, `cookie`, `sessionJwt` + - 문자열 패턴(`token=...`, `authorization:...`, JSON body 내 민감 key)도 마스킹 +- 상세 정책 문서: `docs/client-log-policy.md` + +### Backend 로그 정책 (중요) +- Backend 서버 로그는 `APP_ENV` 기준으로 기본 레벨이 정해집니다. + - `dev|local|development`: `debug` + - 그 외(`stage`, `production`, `prod` 등): `info` +- 운영/스테이징에서 장애 분석이 필요할 때만 `BACKEND_LOG_LEVEL=debug`를 일시적으로 설정하는 것을 권장합니다. +- headless login 같은 경로의 상세 진단 필드는 `debug` 레벨에서만 추가로 남습니다. +- 상세 정책 문서: `docs/backend-log-policy.md` + +### `.env` 작성 후 권장 점검 +```bash +make validate-auth-config +``` +위 검증은 callback/allowed_return_urls/게이트웨이 매핑 규칙을 점검하고 `config/.generated/auth-config.env`를 생성합니다. + +### 전체 스택 실행 (Running the Stack) + +#### 1. 네트워크 생성 (최초 1회) +Ory Stack과 애플리케이션 간 통신을 위한 도커 네트워크를 생성합니다. +```bash +# ory-net은 bridge 모드로 생성 +docker network create -d bridge ory-net +docker network create hydranet +docker network create kratosnet +docker network create public_net #서비스용 +``` + +#### 2. 인프라 및 Ory Stack 실행 +데이터베이스와 Ory 서비스(Kratos, Hydra, Keto 등)를 실행합니다. +```bash +# 권장: Make 실행 (인증 설정 검증 포함) +make up-dev +``` + +#### 3. 애플리케이션 실행 +userfront와 backend 서비스를 실행합니다. +```bash +make up-app +``` +(또는 전체 스택 한번에 실행: `make up-all`) + +### Make 기반 인증 설정 검증 (권장) +`up-*` 타깃은 실행 전 인증 리다이렉트 설정을 자동 검증합니다. + +```bash +# 1) 인증 설정 생성 +make build-auth-config + +# 2) 정적 검증 (callback / allowed_return_urls / 게이트웨이 매핑) +make validate-auth-config + +# 3) 배선 + (가능 시) 런타임 Hydra client 검증 +make verify-auth-config +``` + +- 생성 파일: `config/.generated/auth-config.env` (compose 실행 시 자동 주입) +- 게이트웨이 경유 환경은 URL 문자열 완전일치 대신 매핑 유효성(`direct_match` / `mapped_match`) 기준으로 검증합니다. +- 관련 정책 문서: `docs/oidc_redirect_mapping_validation_policy.md` + +### 권장 실행 순서 +```bash +cp .env.sample .env +# .env 편집 +make validate-auth-config +make up-dev +make up-app +``` + +직접 Compose를 사용하려면 다음처럼 env 파일을 함께 주입하세요. +```bash +docker compose --env-file .env --env-file config/.generated/auth-config.env -f compose.infra.yaml -f compose.ory.yaml up -d +docker compose --env-file .env --env-file config/.generated/auth-config.env -f docker-compose.yaml up -d +``` + +- **gateway (UserFront 프록시)**: http://localhost:5000 접속 +- **backend**: http://localhost:3000 (API) +- **ClickHouse**: http://localhost:8123 +- **Kratos Public**: http://localhost:4433 +- **Hydra Public**: http://localhost:4444 +- **Kratos UI (UserFront)**: http://localhost:5000 + +### 전체 백업/복구 + +전체 백업/복구는 CSV export/import가 아니라 Baron SSO와 Ory Stack 저장소를 같은 시점의 재해 복구 단위로 보존하는 절차입니다. 사용자 UUID, Kratos identity ID, Hydra/Keto 원장, WORKS 연동 mapping이 어긋나면 안 되므로 운영 복구는 DB dump와 설정 snapshot을 함께 다룹니다. + +#### 백업 실행 +```bash +# 전체 백업 +make dump + +# 출력 위치를 직접 지정 +make dump BACKUP=backups/baron-sso-backup-YYYYMMDD-HHMMSSZ + +# 일부 서비스만 백업 +make dump DUMP_SERVICES=postgres,ory-postgres,clickhouse,ory-clickhouse,config +make dump DUMP_SERVICES=ory-postgres,ory-clickhouse + +# 생성된 백업 검증 +make dump-verify BACKUP=backups/baron-sso-backup-YYYYMMDD-HHMMSSZ + +# WORKS Drive로 외부 분산 저장 +make upload-cloud BACKUP=backups/baron-sso-backup-YYYYMMDD-HHMMSSZ + +# 지정 경로로 dump 후 바로 WORKS Drive 업로드 +make dump-upload-cloud BACKUP=backups/baron-sso-backup-YYYYMMDD-HHMMSSZ + +# 로컬 백업 목록 +make dump-list +``` + +기본값은 `DUMP_SERVICES=all`, `DUMP_MODE=maintenance`입니다. `DUMP_SERVICES`는 다음 값을 콤마로 조합할 수 있습니다. + +| 값 | 대상 | +| --- | --- | +| `postgres` | Baron Postgres (`baron_postgres`, `${DB_NAME:-baron_sso}`) | +| `ory-postgres` | Ory Postgres의 `${KRATOS_DB:-ory_kratos}`, `${HYDRA_DB:-ory_hydra}`, `${KETO_DB:-ory_keto}` | +| `clickhouse` | Baron ClickHouse (`baron_clickhouse`) | +| `ory-clickhouse` | Ory ClickHouse (`ory_clickhouse`) | +| `config` | `.env` redacted copy, generated Ory config, gateway, 주요 compose 파일 | + +백업 산출물은 기본적으로 `backups/baron-sso-backup-YYYYMMDD-HHMMSSZ/` 아래에 생성됩니다. + +```text +manifest.json +checksums.sha256 +postgres/ +clickhouse/ +config/ +reports/ +``` + +#### WORKS Drive 외부 업로드 + +`make dump`, `make restore`, `make upload-cloud`는 기본적으로 `docker/backup-tools/Dockerfile`에서 빌드한 `baron-sso-backup-tools:local` 컨테이너 안에서 실행됩니다. 호스트에는 Docker와 Docker socket 접근 권한만 필요하고, `zstd`, `jq`, `curl`, `openssl`, `postgresql-client` 같은 백업/복구 도구는 backup-tools image에 포함됩니다. + +`make upload-cloud`는 기존 백업 디렉터리를 `baron-sso-backup-*.tar.zst`로 묶은 뒤 WORKS Drive에 업로드합니다. 압축 포맷은 `.tar.zst`로 고정되어 있고, 압축/해제는 backup-tools 컨테이너 내부의 `zstd`로 수행합니다. + +백업이 완료되면 `reports/backup-report.md`도 생성됩니다. 이 report에는 사용자 수, 테넌트 수, RP 수, Hydra client 수, WORKS 관련 row count, 서비스별 수행 시간이 Markdown 표로 기록됩니다. `make upload-cloud`는 `reports/*.md`만 WORKS Drive 대상 폴더 아래의 `reports` 하위 폴더로 업로드하며, 업로드 파일명은 `backup-report-YYYYMMDD-HHMMSSZ.md`처럼 업로드 시각을 붙입니다. `reports/cloud-upload.json`은 로컬 업로드 실행 기록으로만 남기고 Drive에는 업로드하지 않습니다. + +```bash +# 권장: 백업 경로를 명시해서 dump와 upload를 분리 +make dump BACKUP=backups/baron-sso-backup-YYYYMMDD-HHMMSSZ +make upload-cloud BACKUP=backups/baron-sso-backup-YYYYMMDD-HHMMSSZ + +# 또는 같은 BACKUP 경로로 연속 실행 +make dump-upload-cloud BACKUP=backups/baron-sso-backup-YYYYMMDD-HHMMSSZ + +# 실제 업로드 전 endpoint와 target만 확인 +make upload-cloud BACKUP=backups/baron-sso-backup-YYYYMMDD-HHMMSSZ WORKS_DRIVE_DRY_RUN=true + +# 예외적으로 호스트 도구로 직접 실행 +make restore BACKUP_USE_DOCKER=false BACKUP=backups/baron-sso-backup-YYYYMMDD-HHMMSSZ CONFIRM_RESTORE=baron-sso +``` + +주요 변수: + +| 변수 | 설명 | +| --- | --- | +| `WORKS_DRIVE_TARGET` | `sharedrive`, `mydrive`, `group`, `sharedfolder` 중 하나. 기본값은 `sharedrive`입니다. | +| `WORKS_DRIVE_SHARED_DRIVE_ID` | `WORKS_DRIVE_TARGET=sharedrive`일 때 공용 드라이브 ID입니다. | +| `WORKS_DRIVE_PARENT_FILE_ID` | 업로드할 대상 폴더의 WORKS Drive `fileId`입니다. 폴더 이름이나 경로가 아니며, 비우면 대상 drive/folder root에 업로드합니다. | +| `WORKS_DRIVE_USER_ID` | `mydrive` 또는 `sharedfolder` 대상 사용자 ID입니다. 기본값은 `me`입니다. | +| `WORKS_DRIVE_GROUP_ID` | `WORKS_DRIVE_TARGET=group`일 때 조직/그룹 ID입니다. | +| `WORKS_DRIVE_SHARED_FOLDER_ID` | `WORKS_DRIVE_TARGET=sharedfolder`일 때 공유받은 폴더 ID입니다. | +| `WORKS_DRIVE_ACCESS_TOKEN` | Drive API 호출용 Bearer token입니다. Drive API는 `file` scope가 필요합니다. | +| `WORKS_DRIVE_ACCESS_TOKEN_FILE` | access token을 파일에서 읽을 때 사용합니다. | +| `WORKS_DRIVE_ACCESS_TOKEN_CMD` | access token을 명령 출력으로 주입할 때 사용합니다. | +| `WORKS_DRIVE_OAUTH_SCOPE` | Drive 업로드 앱 OAuth token에 사용할 scope입니다. 기본값은 `file`입니다. | +| `WORKS_DRIVE_OAUTH_CLIENT_ID` | Drive 업로드 앱의 OAuth client ID입니다. 계정 동기화용 `WORKS_ADMIN_OAUTH_CLIENT_ID`와 분리합니다. | +| `WORKS_DRIVE_OAUTH_CLIENT_SECRET` | Drive 업로드 앱의 OAuth client secret입니다. | +| `WORKS_DRIVE_OAUTH_REFRESH_TOKEN` | Drive 업로드 앱의 refresh token입니다. 명시 access token이 없으면 이 값으로 access token을 갱신합니다. | +| `WORKS_DRIVE_OAUTH_CLIENT_SERVICE_ACCOUNT` | Drive 업로드 앱의 service account입니다. JWT `sub`에 들어갑니다. | +| `WORKS_DRIVE_OAUTH_CLIENT_PRIVATE_KEY_FILE` | Drive 업로드 앱 private key 파일입니다. 예: `./config/worksmobile-driveapp-private-key.pem` | +| `WORKS_DRIVE_SPLIT_SIZE` | 분할 업로드 시 part 크기입니다. 기본값은 `9000M`입니다. | +| `WORKS_DRIVE_MAX_SINGLE_FILE_BYTES` | 이 값보다 archive가 크면 split part로 나눕니다. 기본값 `0`은 자동 분할 비활성입니다. | +| `WORKS_DRIVE_FORCE_SPLIT` | `true`이면 크기와 무관하게 split part로 업로드합니다. | +| `WORKS_DRIVE_OVERWRITE` | WORKS Drive upload URL 생성 요청의 overwrite 플래그입니다. 기본값은 `false`입니다. | +| `WORKS_DRIVE_UPLOAD_REPORTS` | `true`이면 `reports/*.md`를 Drive의 report 폴더로 함께 업로드합니다. 기본값은 `true`입니다. | +| `WORKS_DRIVE_REPORT_FOLDER_NAME` | Markdown report를 업로드할 하위 폴더 이름입니다. 기본값은 `reports`입니다. | + +Drive API는 업로드 URL 생성 후 해당 URL에 multipart `Filedata`로 실제 파일을 전송하는 2단계 방식입니다. 계정 동기화용 `WORKS_ADMIN_OAUTH_*`와 Drive 업로드용 `WORKS_DRIVE_OAUTH_*`는 서로 다른 앱/키로 관리합니다. token 우선순위는 `WORKS_DRIVE_ACCESS_TOKEN`, `WORKS_DRIVE_ACCESS_TOKEN_FILE`, `WORKS_DRIVE_ACCESS_TOKEN_CMD`, `WORKS_DRIVE_OAUTH_REFRESH_TOKEN`, 서비스 계정 JWT fallback 순서입니다. 운영에서는 Drive API 권한과 `file` scope 위임 정책을 먼저 확인해야 합니다. + +#### 복구 계획과 복구 실행 +```bash +# 복구 전 계획 확인 +make restore-plan BACKUP=backups/baron-sso-backup-YYYYMMDD-HHMMSSZ \ + RESTORE_SERVICES=postgres,ory-postgres,clickhouse,ory-clickhouse,config \ + CONFIRM_RESTORE=baron-sso + +# 복구 실행 +make restore BACKUP=backups/baron-sso-backup-YYYYMMDD-HHMMSSZ \ + RESTORE_SERVICES=postgres,ory-postgres,clickhouse,ory-clickhouse,config \ + CONFIRM_RESTORE=baron-sso + +# .tar.zst archive를 직접 복구 입력으로 사용 +make restore DUMP_FILE=backups/baron-sso-backup-YYYYMMDD-HHMMSSZ.tar.zst \ + RESTORE_SERVICES=all \ + CONFIRM_RESTORE=baron-sso + +# report 경로를 명시 +make restore BACKUP=backups/baron-sso-backup-YYYYMMDD-HHMMSSZ \ + CONFIRM_RESTORE=baron-sso \ + RESTORE_REPORT=reports/restore/baron-sso-restore-report.json + +# 복구 후 기본 검증 +make restore-verify BACKUP=backups/baron-sso-backup-YYYYMMDD-HHMMSSZ +``` + +복구는 반드시 빈 volume 또는 restore 전용 stack에서 수행하는 것을 기본 정책으로 합니다. `make restore`는 `BACKUP` 또는 `DUMP_FILE` 중 하나와 `CONFIRM_RESTORE=baron-sso`가 없으면 실패하고, 기본적으로 non-empty Postgres 대상에는 복구하지 않습니다. 승인된 restore rehearsal에서만 `ALLOW_NON_EMPTY_RESTORE=true`를 사용하세요. `DUMP_FILE=.tar.zst` 해제도 backup-tools 컨테이너에서 수행하므로 호스트 `zstd` 설치에 의존하지 않습니다. + +`make restore`는 복구 report를 JSON과 Markdown으로 남깁니다. `BACKUP` 디렉터리 입력의 기본 JSON report는 `/reports/restore-report.json`이고, `DUMP_FILE` archive 입력의 기본 JSON report는 `reports/restore/-restore-report.json`입니다. 같은 경로에 `.md` 확장자의 Markdown 요약도 함께 생성됩니다. `RESTORE_REPORT`로 직접 지정할 수 있습니다. report에는 입력 archive, 복구 서비스, checksum 검증 상태, 복구 후 대상 row count 비교 결과가 기록됩니다. + +`config` 복구는 운영 파일을 직접 덮어쓰지 않고 `config-restored/`에 풀어 수동 검토하도록 합니다. migration은 자동 실행하지 않으며, Ory Stack과 backend 기동 후 super admin login, 대표 OIDC login, WORKS comparison dry-run을 통과하기 전까지 WORKS relay를 자동 재개하지 않습니다. + +#### 백업/복구 범위 + +필수 백업 대상: +- Baron Postgres: users, tenants, user_login_ids, user_groups, RP metadata, WORKS mapping/outbox 등 +- Ory Postgres: Kratos identity/credentials/session, Hydra client/consent/token state, Keto relation tuple +- Baron ClickHouse: 감사 로그와 RP usage event +- Ory ClickHouse: Oathkeeper/Ory 계열 접근 로그 +- 설정 snapshot: `.env` redacted copy, generated Ory config, gateway, compose 파일 + +기본 제외 대상: +- Redis: pending login, short code, cache 등 휘발성 데이터이므로 복구 후 재수렴 대상으로 봅니다. +- 프론트 빌드 산출물: 소스와 이미지 태그로 재생성합니다. +- coverage, reports, test-results 같은 로컬 개발 산출물 + +상세 설계와 운영 정책은 `docs/backup-restore-design.md`를 기준으로 유지합니다. + +### MCP 서버 (Hydra/Kratos/Keto) +MCP 서버는 기존 Hydra/Kratos에 연결하며 별도 Ory 스택이나 포트를 추가로 띄우지 않습니다. +프로덕션에서는 실행하지 않도록 `mcp` 프로파일을 로컬에서만 켜세요. + +```bash +docker compose -f compose.ory.yaml --profile mcp up -d hydra-mcp-server kratos-mcp-server keto-mcp-server +``` + +- MCP 서버는 stdio 기반이라 외부 포트를 열지 않습니다. +- 최초 실행시거나 빌드된 이미지가 없으면 `docker compose -f mcp/compose.mcp.ory.yaml build' 후에 사용 가능합니다 + +```toml +[mcp_servers.kratos-mcp] +command = "docker" +args = ["compose", "-f", "mcp/compose.mcp.ory.yaml", "run", "--rm", "--no-deps", "kratos-mcp-server"] + +[mcp_servers.kratos-mcp.env] +KRATOS_ADMIN_URL = "http://kratos:4434" + +[mcp_servers.hydra-mcp] +command = "docker" +args = ["compose", "-f", "mcp/compose.mcp.ory.yaml", "run", "--rm", "--no-deps", "hydra-mcp-server"] + +[mcp_servers.hydra-mcp.env] +HYDRA_PUBLIC_URL = "http://hydra:4444" +HYDRA_ADMIN_URL = "http://hydra:4445" + +[mcp_servers.keto-mcp] +command = "docker" +args = ["compose", "-f", "mcp/compose.mcp.ory.yaml", "run", "--rm", "--no-deps", "keto-mcp-server"] + +[mcp_servers.keto-mcp.env] +KETO_READ_URL = "http://keto:4466" +KETO_WRITE_URL = "http://keto:4467" +``` + +## 🌐 i18n 구조 (간략) +- **Root locales**: `locales/template.toml`, `locales/ko.toml`, `locales/en.toml`은 현재 `userfront`와 전역 i18n 검증 기준 리소스입니다. +- **Common locales**: `common/locales/template.toml`, `common/locales/ko.toml`, `common/locales/en.toml`은 `ui.common.*`, `msg.common.*` 같은 React 공통 문구 레이어입니다. +- **React(Admin/Dev/Org)**: `adminfront/src/lib/i18n.ts`, `devfront/src/lib/i18n.ts`, `orgfront/src/lib/i18n.ts`에서 `t(key, fallback, vars)`를 사용하며 `common locale -> app locale override` 순서로 TOML을 `?raw` 로드합니다. +- **Flutter(User)**: `userfront/lib/i18n.dart`에서 `tr(key, fallback, params)` 사용. `locales/*.toml`을 `tools/i18n-scanner/gen-flutter-i18n.js`로 `userfront/lib/i18n_data.dart`에 사전 생성합니다. +- **UserFront 동기화 규칙**: `locales/*.toml`을 수정한 뒤에는 반드시 `./scripts/sync_userfront_locales.sh`를 실행해 `userfront/assets/translations/*.toml`과 런타임 번역 리소스를 동기화합니다. +- **검증**: `node tools/i18n-scanner/index.js`로 `root locales`와 `common/locales`의 코드-키-로케일 동기화 상태를 함께 점검합니다. + +## 🧪 Code Check CI +워크플로우 파일: `.gitea/workflows/code_check.yml` + +### 트리거 +- `push` (`dev` 브랜치) +- `pull_request` (`dev` 대상) +- `workflow_dispatch` (수동 실행) + +### workflow_dispatch 입력값 +- `run_lint`: Go/Flutter lint 실행 여부 +- `run_backend_tests`: backend 테스트 실행 여부 +- `run_userfront_tests`: userfront 테스트 실행 여부 +- `run_userfront_e2e_tests`: userfront WASM Playwright E2E 실행 여부 +- `run_adminfront_tests`: adminfront 테스트 실행 여부 +- `run_devfront_tests`: devfront 테스트 실행 여부 + +### 실행 잡 +- `lint` +- `backend-tests` +- `userfront-tests` +- `userfront-e2e-tests` +- `adminfront-tests` +- `devfront-tests` + +### 프런트 테스트 브라우저 프리비저닝 정책 +- `userfront-tests` + - `flutter test`(VM)만 실행 + - `locale_storage` 정책 테스트는 엔진 단위로 통합되어 별도 브라우저 실행이 필요하지 않음 +- `adminfront-tests`, `devfront-tests` + - Playwright 기반 테스트 + - `npx playwright install --with-deps`로 브라우저/OS 의존성을 사전 설치 + +### 실패 보고서 확인 방법 +테스트가 실패하면 다음이 자동 생성됩니다. +- Job Summary: 실패 원인 요약(Markdown) 즉시 확인 +- Artifact: 상세 로그/리포트 다운로드 + - `backend-test-failure-report` + - `userfront-test-failure-report` + - `adminfront-test-failure-report` + - `devfront-test-failure-report` + +### userfront `locale_storage` 테스트 정책 +- `locale_storage_platform_test.dart`는 `LocaleStorageEngine` 기반 정책 테스트로 통합되었습니다. +- 일반 `flutter test`(VM) 실행에 포함되며, 브라우저 전용 `kIsWeb` 케이스를 사용하지 않습니다. +- 단일 파일만 확인하려면 다음 명령을 사용합니다. + - `flutter test test/locale_storage_platform_test.dart` + +### userfront WASM Playwright E2E +- 워크스페이스: `userfront-e2e/` +- 빌드+실행: + - `cd userfront-e2e && npm run test:wasm` +- 빌드 결과가 이미 있을 때: + - `cd userfront-e2e && npm test` +- Makefile 타깃: + - `make code-check-userfront-e2e-tests` +- 전수 인벤토리: + - `https://gitea.hmac.kr/baron/baron-sso/wiki/UserFront-WASM-E2E-Inventory.-` + +### 로컬 개발 (Manual) +Docker 없이는 개발할 수 없지만 Backend 및 [user/admin/dev]Front 코드는 개발모드로 수정하며 개발가능. +백그라운드로 infra 및 ory stack이 구동중이라는 가정 + +**Backend:** +```bash +cd backend +go mod tidy +go run cmd/server/main.go +``` + +**userfront:** +```bash +cd userfront +flutter pub get +flutter run -d chrome +# 정책: 웹 빌드는 기본적으로 WASM 사용 +flutter build web --wasm +``` + +**adminfront:** +```bash +cd adminfront +npm install +npm run dev +``` + +**devfront:** +```bash +cd devfront +npm install +npm run dev +``` +--- + + +## 버그 대응 대원칙 (필수) +- 모든 버그 수정은 반드시 **재현 테스트를 먼저 작성**합니다. (Failing test first) +- 재현 테스트 없이 코드만 먼저 고치는 행위를 금지합니다. +- 수정 후에는 **해당 재현 테스트가 통과할 때까지 반복**해서 원인 분석/수정/검증을 수행합니다. +- “테스트 통과”는 최소 기준입니다. 실제 재현 시나리오(로그인, 새로고침, 리다이렉트 등)까지 확인한 뒤에만 이슈를 종료합니다. +- 관련 변경이 발생하면 테스트 문서(`docs/test-plan/*`, `docs/trouble-shooting/*`)를 함께 업데이트합니다. diff --git a/baron-sso/README_en.md b/baron-sso/README_en.md new file mode 100644 index 0000000..79fba21 --- /dev/null +++ b/baron-sso/README_en.md @@ -0,0 +1,110 @@ +# Baron SSO + +**Baron SSO** is a white-labeled User Authentication Hub and Unified Launcher. +It leverages **Descope** for secure, passwordless authentication (Enchanted Link / Magic Link) and provides a custom Flutter UI for a seamless user experience. A Go (Fiber) backend manages Audit Logs via ClickHouse. + +## 🏗 Architecture + +### 1. Frontend (Flutter Web) +- **Framework**: Flutter 3.38.0+ +- **Organization**: `kr.co.baroncs` +- **Key Packages**: `descope`, `flutter_riverpod`, `go_router` +- **Features**: + - Login UI with Tabs (Email / SMS) + - Descope SDK Integration (Enchanted Link, Magic Link) + +### 2. Backend (Go Fiber) +- **Language**: Go 1.26.2+ +- **Framework**: Fiber v2.25+ +- **Database**: + - **ClickHouse**: Audit Logs (High performance ingestion) + - **PostgreSQL**: Metadata storage (Primary) +- **Features**: + - `POST /api/v1/audit`: Endpoint to ingest audit logs. + +### 3. Infrastructure (Docker) +- **Services**: `postgres`, `clickhouse` (defined in `compose.infra.yaml`) +- **App**: `userfront`, `backend` (defined in `docker-compose.yaml`) + +--- + +## 🚀 Getting Started + +### Prerequisites +- Docker & Docker Compose +- Flutter SDK (for local development, 3.38.0+) +- Go (for local backend development) + +### Environment Setup +1. Copy the sample environment file. + ```bash + cp .env.sample .env + ``` + +2. Set the **IDP priority and Ory admin endpoints**. The default is Ory first with Descope as fallback. + ```env + IDP_PROVIDER=ory,descope + KRATOS_ADMIN_URL=http://kratos:4434 + HYDRA_ADMIN_URL=http://hydra:4445 + ``` + +### Running the Stack + +#### 1. Start Infrastructure (Databases) +Start the persistent data layer first. +```bash +docker compose -f compose.infra.yaml up -d +``` + +#### 2. Start Applications +Start the userfront and backend services. +```bash +docker compose up +``` + +- **userfront**: Accessible at http://localhost:5000 +- **backend**: API active at http://localhost:3000 +- **ClickHouse**: http://localhost:8123 + +### Local Development (Manual) +If you prefer running without Docker for code editing: + +**Backend:** +```bash +cd backend +go mod tidy +go run cmd/server/main.go +``` + +**userfront:** +```bash +cd userfront +flutter pub get +flutter run -d chrome +``` + +--- + +## 📂 Project Structure + +``` +baron_sso/ +├── backend/ # Go Fiber Application +│ ├── cmd/server/ # Entry point +│ ├── internal/ # Domain, Handlers, Repository +│ └── Dockerfile +├── userfront/ # Flutter Application +│ ├── lib/ # UI & Logic +│ └── pubspec.yaml +├── compose.infra.yaml # DB Services (Postgres, ClickHouse) +├── docker-compose.yaml # App Services +├── .env.sample # Env Config Template +└── README.md # This file +``` + +## 📝 Status & Roadmap +- [x] **Phase 1**: Initial Setup & Architecture (Done) +- [x] **Phase 2**: Backend Audit API (Done) +- [x] **Phase 3**: Frontend Login UI & Descope Auth Logic (Done) +- [ ] **Phase 4**: Connect Frontend to Audit API (Todo) +- [ ] **Phase 5**: Dashboard & Unified Launcher (Todo) diff --git a/baron-sso/adminfront/.gitignore b/baron-sso/adminfront/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/baron-sso/adminfront/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/baron-sso/adminfront/Dockerfile b/baron-sso/adminfront/Dockerfile new file mode 100644 index 0000000..305e302 --- /dev/null +++ b/baron-sso/adminfront/Dockerfile @@ -0,0 +1,40 @@ +FROM node:lts AS build + +WORKDIR /workspace + +ENV CI=true +ENV ADMINFRONT_BUILD_OUT_DIR=/workspace/adminfront/dist + +RUN corepack enable && corepack prepare pnpm@10.5.2 --activate + +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +COPY common ./common +COPY adminfront ./adminfront + +ARG VITE_ADMIN_PUBLIC_URL +ARG VITE_OIDC_AUTHORITY +ARG VITE_OIDC_CLIENT_ID +ARG ORGFRONT_URL +ENV VITE_ADMIN_PUBLIC_URL=$VITE_ADMIN_PUBLIC_URL +ENV VITE_OIDC_AUTHORITY=$VITE_OIDC_AUTHORITY +ENV VITE_OIDC_CLIENT_ID=$VITE_OIDC_CLIENT_ID +ENV ORGFRONT_URL=$ORGFRONT_URL + +RUN pnpm install --frozen-lockfile --ignore-scripts + +WORKDIR /workspace/adminfront +RUN npm run build + +FROM node:24-alpine AS production + +WORKDIR /app +ENV NODE_ENV=production +ENV FRONTEND_DIST_DIR=/app/dist +ENV PORT=5173 + +COPY scripts/serve_frontend_prod.mjs ./serve_frontend_prod.mjs +COPY --from=build /workspace/adminfront/dist ./dist + +EXPOSE 5173 + +CMD ["node", "./serve_frontend_prod.mjs"] diff --git a/baron-sso/adminfront/README.md b/baron-sso/adminfront/README.md new file mode 100644 index 0000000..b25f6e8 --- /dev/null +++ b/baron-sso/adminfront/README.md @@ -0,0 +1,29 @@ +# Admin Front (React 19 + Vite) + +관리자 포털을 위한 React/Vite 기반 웹입니다. 이슈 #60 스펙을 바탕으로 라우팅, 서버 상태, 스타일 토큰을 세팅했고 특정 벤더에 종속되지 않는 IDP 연동 훅 포인트를 남겨두었습니다. + +## 주요 스택 +- React 19, Vite 8, TypeScript(strict) +- React Router v6 (data router) +- TanStack Query v5 +- Tailwind CSS v3 + shadcn/ui 컴포넌트(seed: Button/Card/Badge/Input/Table/Avatar) +- Axios 클라이언트 스텁: Bearer + `X-Tenant-ID` 헤더 주입 준비 +- React Hook Form + Zod (추가 예정) +- Biome (formatter/linter) + +## 실행 +```bash +npm install +npm run dev +``` + +## 구조 +- `src/app`: 라우터, QueryClient 등 전역 설정 +- `src/components/layout`: App 레이아웃/네비게이션 +- `src/features`: dashboard, clients, audit, auth 등 화면 스캐폴딩 +- `src/lib/apiClient.ts`: Axios 인스턴스(토큰/테넌트 헤더 주입 스텁) + +## 다음 작업 가이드 +- IDP 중립 Auth 레이어 추가 후 관리자 역할 가드/세션 TTL 적용 +- 테넌트 선택 UI 추가 → `X-Tenant-ID` 헤더에 반영 +- shadcn/ui 도입 및 폼/테이블 컴포넌트 연결 diff --git a/baron-sso/adminfront/biome.json b/baron-sso/adminfront/biome.json new file mode 100644 index 0000000..cad9eca --- /dev/null +++ b/baron-sso/adminfront/biome.json @@ -0,0 +1,7 @@ +{ + "root": true, + "extends": ["../common/config/biome.base.json"], + "files": { + "includes": [".vite"] + } +} diff --git a/baron-sso/adminfront/index.html b/baron-sso/adminfront/index.html new file mode 100644 index 0000000..7e8d702 --- /dev/null +++ b/baron-sso/adminfront/index.html @@ -0,0 +1,13 @@ + + + + + + + 바론 어드민 서비스 + + +
+ + + diff --git a/baron-sso/adminfront/package-lock.json b/baron-sso/adminfront/package-lock.json new file mode 100644 index 0000000..24178a8 --- /dev/null +++ b/baron-sso/adminfront/package-lock.json @@ -0,0 +1,6081 @@ +{ + "name": "adminfront", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "adminfront", + "version": "0.0.0", + "dependencies": { + "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@tanstack/react-query": "^5.100.10", + "@tanstack/react-query-devtools": "^5.100.10", + "@tanstack/react-virtual": "^3.13.24", + "axios": "^1.16.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^1.14.0", + "oidc-client-ts": "^3.5.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-hook-form": "^7.75.0", + "react-oidc-context": "^3.3.1", + "react-router-dom": "^7.15.0", + "tailwind-merge": "^3.6.0", + "zod": "^4.4.3" + }, + "devDependencies": { + "@biomejs/biome": "2.4.16", + "@playwright/test": "^1.60.0", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^25.7.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@types/react-router-dom": "^5.3.3", + "@vitejs/plugin-react": "^6.0.1", + "@vitest/coverage-v8": "4.1.6", + "autoprefixer": "^10.5.0", + "jsdom": "^28.1.0", + "playwright": "1.60.0", + "postcss": "^8.5.14", + "tailwindcss": "^3.4.19", + "tailwindcss-animate": "^1.0.7", + "typescript": "^6.0.3", + "vite": "^8.0.14", + "vitest": "^4.1.6" + }, + "engines": { + "node": ">=24.0.0" + } + }, + "node_modules/@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "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/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.6" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@biomejs/biome": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.16.tgz", + "integrity": "sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.4.16", + "@biomejs/cli-darwin-x64": "2.4.16", + "@biomejs/cli-linux-arm64": "2.4.16", + "@biomejs/cli-linux-arm64-musl": "2.4.16", + "@biomejs/cli-linux-x64": "2.4.16", + "@biomejs/cli-linux-x64-musl": "2.4.16", + "@biomejs/cli-win32-arm64": "2.4.16", + "@biomejs/cli-win32-x64": "2.4.16" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.16.tgz", + "integrity": "sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.16.tgz", + "integrity": "sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.16.tgz", + "integrity": "sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.16.tgz", + "integrity": "sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.16.tgz", + "integrity": "sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.16.tgz", + "integrity": "sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.16.tgz", + "integrity": "sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.16.tgz", + "integrity": "sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", + "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.1.tgz", + "integrity": "sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.2.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.4.tgz", + "integrity": "sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "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/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/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "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/@oxc-project/types": { + "version": "0.132.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", + "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@playwright/test": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", + "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.11.tgz", + "integrity": "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.3", + "@radix-ui/react-primitive": "2.1.4", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", + "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", + "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", + "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", + "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", + "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", + "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", + "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", + "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", + "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", + "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", + "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", + "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", + "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", + "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", + "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tanstack/query-core": { + "version": "5.100.10", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.10.tgz", + "integrity": "sha512-8UR0yJR+GiQ40m3lPhUr0xbfAupe6GSQiksSBSa9SM2NjezFyxXCIA69/lz8cSoNKZLrw1/PktIyQBJcVeMi3w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.100.10", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.100.10.tgz", + "integrity": "sha512-3DmJf25hDPus5IpVvp6ujXv6bKV2zPzI9vpbAmpJigsL/H6DPvPjmf7/Q9yVKEke//8fgeQ45abjgnLuyYxAiw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.100.10", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.10.tgz", + "integrity": "sha512-FLaZf2RCrA/Zgp4aiu5tG3TyasTRO7aZ99skxQpr3Hg/zXOhu6yq5FZCYQ/tRaJtM9ylnoK8tFK7PolXQadv6Q==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.100.10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.100.10", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.100.10.tgz", + "integrity": "sha512-zes0+o9ef5rAZXJ9f/SeaLs2nufJaeVkZkl/Or9NGrWVF41kL9Od9ED9nCwtQlgiF2VGtrzhEw5AU/igAO+aAg==", + "license": "MIT", + "dependencies": { + "@tanstack/query-devtools": "5.100.10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.100.10", + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.13.24", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.24.tgz", + "integrity": "sha512-aIJvz5OSkhNIhZIpYivrxrPTKYsjW9Uzy+sP/mx0S3sev2HyvPb7xmjbYvokzEpfgYHy/HjzJ2zFAETuUfgCpg==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.14.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.14.0.tgz", + "integrity": "sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.8.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.8.0.tgz", + "integrity": "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", + "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.6.tgz", + "integrity": "sha512-36l628fQ/9a/8ihy97eOtEnvWQEdqULQOJtcaxtoNq0G1w3Mxd4szSahOaMM9/NGyZ+hyKcMtIW/WIxq0XQViQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.6", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.6", + "vitest": "4.1.6" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz", + "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.6", + "@vitest/utils": "4.1.6", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz", + "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.6", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz", + "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz", + "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.6", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz", + "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.6", + "@vitest/utils": "4.1.6", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz", + "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz", + "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.6", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "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", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "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/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/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.2.tgz", + "integrity": "sha512-dKmJxJsGItLmc5CYZKuEjuG6GnBs6PG4gohMhyFOWKaNQoYCuRZJDECaBlHmcG0lv2wc2E0uU8lESmBEumC3DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "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/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "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.2", + "caniuse-lite": "^1.0.30001787", + "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/axios": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.29", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz", + "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "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/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.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "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.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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-lite": { + "version": "1.0.30001792", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", + "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/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/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/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "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/cssstyle": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.2.0.tgz", + "integrity": "sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.0.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.28", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.6" + }, + "engines": { + "node": ">=20" + } + }, + "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/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "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/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "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/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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==", + "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/electron-to-chromium": { + "version": "1.5.356", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.356.tgz", + "integrity": "sha512-9NgFd7m5t5MCJ5rUSjJITUXAH9mEGlrlofnMf4YEr+pz6JlP7cWmTAH+JFmbPnaSW8koVTkuW7pacORWAnA5Yw==", + "dev": true, + "license": "ISC" + }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "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==", + "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==", + "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/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/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "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/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/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/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "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/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/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "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==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "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-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "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-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "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==", + "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.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-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-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-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/jsdom": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz", + "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@acemir/cssom": "^0.9.31", + "@asamuzakjp/dom-selector": "^6.8.1", + "@bramus/specificity": "^2.4.2", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^6.0.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "undici": "^7.21.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/jsdom/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "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/lru-cache": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.0.tgz", + "integrity": "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/lucide-react": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.16.0.tgz", + "integrity": "sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", + "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "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/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/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "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==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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==", + "license": "MIT" + }, + "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.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "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/node-releases": { + "version": "2.0.44", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz", + "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", + "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/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/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/oidc-client-ts": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.5.0.tgz", + "integrity": "sha512-l2q8l9CTCTOlbX+AnK4p3M+4CEpKpyQhle6blQkdFhm0IsBqsxm15bYaSa11G7pWdsYr6epdsRZxJpCyCRbT8A==", + "license": "Apache-2.0", + "dependencies": { + "jwt-decode": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "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/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "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": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "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/playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "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-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-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-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-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/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=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/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/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.6" + } + }, + "node_modules/react-hook-form": { + "version": "7.75.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.75.0.tgz", + "integrity": "sha512-Ovv94H+0p3sJ7B9B5QxPuCP1u8V/cHuVGyH55cSwodYDtoJwK+fqk3vjfIgSX59I2U/bU4z0nRJ9HMLpNiWEmw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/react-oidc-context": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/react-oidc-context/-/react-oidc-context-3.3.1.tgz", + "integrity": "sha512-/Azvm9W4DhhOtSDBE73kFInh1b6zZRRfILKbgmk2syExMF0PCYJOn/dGdOOi2BFX8x0rCeUe45NXHU+/+xDcrQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "oidc-client-ts": "^3.1.0", + "react": ">=16.14.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "7.15.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.1.tgz", + "integrity": "sha512-R8rl9HhgikFYoPJymnUtPXWbnDb3oget6lQnfIoupbt61aT9aOhRkDsY2XRhZRyX1Z/8a5sL74fXmFNm3NRK5A==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.15.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.15.1.tgz", + "integrity": "sha512-AzF62gjY6U9rkMq4RfP/r2EVtQ7DMfNMjyOp/flLTCrtRylLiK4wT4pSq6O8rOXZ2eXdZYJPEYe+ifomiv+Igg==", + "license": "MIT", + "dependencies": { + "react-router": "7.15.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "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/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/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "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/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/rolldown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", + "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.132.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.2", + "@rolldown/binding-darwin-arm64": "1.0.2", + "@rolldown/binding-darwin-x64": "1.0.2", + "@rolldown/binding-freebsd-x64": "1.0.2", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", + "@rolldown/binding-linux-arm64-gnu": "1.0.2", + "@rolldown/binding-linux-arm64-musl": "1.0.2", + "@rolldown/binding-linux-ppc64-gnu": "1.0.2", + "@rolldown/binding-linux-s390x-gnu": "1.0.2", + "@rolldown/binding-linux-x64-gnu": "1.0.2", + "@rolldown/binding-linux-x64-musl": "1.0.2", + "@rolldown/binding-openharmony-arm64": "1.0.2", + "@rolldown/binding-wasm32-wasi": "1.0.2", + "@rolldown/binding-win32-arm64-msvc": "1.0.2", + "@rolldown/binding-win32-x64-msvc": "1.0.2" + } + }, + "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/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "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/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/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/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "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-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "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/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/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/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.30.tgz", + "integrity": "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.30" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.30.tgz", + "integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==", + "dev": true, + "license": "MIT" + }, + "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/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "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/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/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "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", + "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/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "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/vite": { + "version": "8.0.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", + "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.2", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/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/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.6.tgz", + "integrity": "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.6", + "@vitest/mocker": "4.1.6", + "@vitest/pretty-format": "4.1.6", + "@vitest/runner": "4.1.6", + "@vitest/snapshot": "4.1.6", + "@vitest/spy": "4.1.6", + "@vitest/utils": "4.1.6", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.6", + "@vitest/browser-preview": "4.1.6", + "@vitest/browser-webdriverio": "4.1.6", + "@vitest/coverage-istanbul": "4.1.6", + "@vitest/coverage-v8": "4.1.6", + "@vitest/ui": "4.1.6", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/baron-sso/adminfront/package.json b/baron-sso/adminfront/package.json new file mode 100644 index 0000000..40b671e --- /dev/null +++ b/baron-sso/adminfront/package.json @@ -0,0 +1,68 @@ +{ + "name": "adminfront", + "private": true, + "version": "0.0.0", + "type": "module", + "engines": { + "node": ">=24.0.0" + }, + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "tsc -b && vite build", + "lint": "biome check .", + "lint:fix": "biome check . --write", + "format": "biome format . --write", + "preview": "vite preview", + "test": "playwright test", + "test:coverage": "vitest run --coverage --bail 1", + "test:unit": "vitest run --bail 1", + "test:ui": "playwright test --ui", + "i18n-scan": "cd .. && node tools/i18n-scanner/index.js && node tools/i18n-scanner/report.js" + }, + "dependencies": { + "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@tanstack/react-query": "^5.100.10", + "@tanstack/react-query-devtools": "^5.100.10", + "@tanstack/react-virtual": "^3.13.24", + "axios": "^1.16.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^1.14.0", + "oidc-client-ts": "^3.5.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-hook-form": "^7.75.0", + "react-oidc-context": "^3.3.1", + "react-router-dom": "^7.15.0", + "tailwind-merge": "^3.6.0", + "zod": "^4.4.3" + }, + "devDependencies": { + "@biomejs/biome": "2.4.16", + "@playwright/test": "^1.60.0", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^25.7.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@types/react-router-dom": "^5.3.3", + "@vitejs/plugin-react": "^6.0.1", + "@vitest/coverage-v8": "4.1.6", + "autoprefixer": "^10.5.0", + "jsdom": "^28.1.0", + "playwright": "1.60.0", + "postcss": "^8.5.14", + "tailwindcss": "^3.4.19", + "tailwindcss-animate": "^1.0.7", + "typescript": "^6.0.3", + "vite": "^8.0.14", + "vitest": "^4.1.6" + } +} diff --git a/baron-sso/adminfront/playwright.config.ts b/baron-sso/adminfront/playwright.config.ts new file mode 100644 index 0000000..e2b56df --- /dev/null +++ b/baron-sso/adminfront/playwright.config.ts @@ -0,0 +1,94 @@ +import { createRequire } from "node:module"; +import { defineConfig, devices } from "@playwright/test"; + +const require = createRequire(import.meta.url); +const { shouldIncludeWebKit } = + require("../scripts/playwrightHostDeps.cjs") as { + shouldIncludeWebKit: () => boolean; + }; + +const configuredWorkers = process.env.PLAYWRIGHT_WORKERS + ? Number.parseInt(process.env.PLAYWRIGHT_WORKERS, 10) + : undefined; +const port = Number.parseInt(process.env.PORT ?? "5173", 10); +const defaultBaseUrl = `http://127.0.0.1:${port}`; +const baseURL = process.env.BASE_URL ?? defaultBaseUrl; +const reuseExistingServer = !process.env.CI && !process.env.PORT; +const chromiumExecutablePath = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./tests", + timeout: 60 * 1000, + expect: { + timeout: 15000, + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: configuredWorkers ?? (process.env.CI ? 1 : undefined), + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [["html", { open: "never" }], ["list"]], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL, + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "retain-on-failure", + locale: "ko-KR", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { + ...devices["Desktop Chrome"], + launchOptions: chromiumExecutablePath + ? { executablePath: chromiumExecutablePath } + : undefined, + }, + }, + + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + + ...(shouldIncludeWebKit() + ? [ + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + ] + : []), + ], + + /* Run your local dev server before starting the tests */ + webServer: process.env.BASE_URL + ? undefined + : { + command: process.env.CI + ? `pnpm exec vite preview --host 127.0.0.1 --port ${port} --strictPort` + : `pnpm exec vite --host 127.0.0.1 --port ${port} --strictPort`, + url: `http://127.0.0.1:${port}`, + reuseExistingServer, + timeout: 180 * 1000, + }, +}); diff --git a/baron-sso/adminfront/pnpm-lock.yaml b/baron-sso/adminfront/pnpm-lock.yaml new file mode 100644 index 0000000..f402328 --- /dev/null +++ b/baron-sso/adminfront/pnpm-lock.yaml @@ -0,0 +1,3722 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@radix-ui/react-avatar': + specifier: ^1.1.11 + version: 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-scroll-area': + specifier: ^1.2.10 + version: 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-select': + specifier: ^2.2.6 + version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': + specifier: ^1.2.4 + version: 1.2.4(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-switch': + specifier: ^1.2.6 + version: 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@tanstack/react-query': + specifier: ^5.100.10 + version: 5.100.10(react@19.2.6) + '@tanstack/react-query-devtools': + specifier: ^5.100.10 + version: 5.100.10(@tanstack/react-query@5.100.10(react@19.2.6))(react@19.2.6) + '@tanstack/react-virtual': + specifier: ^3.13.24 + version: 3.13.24(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + axios: + specifier: ^1.16.1 + version: 1.16.1 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: ^1.14.0 + version: 1.16.0(react@19.2.6) + oidc-client-ts: + specifier: ^3.5.0 + version: 3.5.0 + react: + specifier: ^19.2.6 + version: 19.2.6 + react-dom: + specifier: ^19.2.6 + version: 19.2.6(react@19.2.6) + react-hook-form: + specifier: ^7.75.0 + version: 7.76.0(react@19.2.6) + react-oidc-context: + specifier: ^3.3.1 + version: 3.3.1(oidc-client-ts@3.5.0)(react@19.2.6) + react-router-dom: + specifier: ^7.15.0 + version: 7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + tailwind-merge: + specifier: ^3.6.0 + version: 3.6.0 + zod: + specifier: ^4.4.3 + version: 4.4.3 + devDependencies: + '@biomejs/biome': + specifier: 2.4.16 + version: 2.4.16 + '@playwright/test': + specifier: ^1.60.0 + version: 1.60.0 + '@testing-library/jest-dom': + specifier: ^6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@testing-library/user-event': + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) + '@types/node': + specifier: ^25.7.0 + version: 25.8.0 + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@types/react-router-dom': + specifier: ^5.3.3 + version: 5.3.3 + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.2(vite@8.0.14(@types/node@25.8.0)(jiti@1.21.7)) + '@vitest/coverage-v8': + specifier: 4.1.6 + version: 4.1.6(vitest@4.1.6) + autoprefixer: + specifier: ^10.5.0 + version: 10.5.0(postcss@8.5.14) + jsdom: + specifier: ^28.1.0 + version: 28.1.0 + playwright: + specifier: 1.60.0 + version: 1.60.0 + postcss: + specifier: ^8.5.14 + version: 8.5.14 + tailwindcss: + specifier: ^3.4.19 + version: 3.4.19 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.19) + typescript: + specifier: ^6.0.3 + version: 6.0.3 + vite: + specifier: ^8.0.14 + version: 8.0.14(@types/node@25.8.0)(jiti@1.21.7) + vitest: + specifier: ^4.1.6 + version: 4.1.6(@types/node@25.8.0)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.14(@types/node@25.8.0)(jiti@1.21.7)) + +packages: + + '@acemir/cssom@0.9.31': + resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} + + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@asamuzakjp/css-color@5.1.11': + resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@6.8.1': + resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==} + + '@asamuzakjp/generational-cache@1.0.1': + resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@biomejs/biome@2.4.16': + resolution: {integrity: sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.4.16': + resolution: {integrity: sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.4.16': + resolution: {integrity: sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.4.16': + resolution: {integrity: sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-arm64@2.4.16': + resolution: {integrity: sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-linux-x64-musl@2.4.16': + resolution: {integrity: sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-x64@2.4.16': + resolution: {integrity: sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-win32-arm64@2.4.16': + resolution: {integrity: sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.4.16': + resolution: {integrity: sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@csstools/color-helpers@6.0.2': + resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.2.1': + resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.1.1': + resolution: {integrity: sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.4': + resolution: {integrity: sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@exodus/bytes@1.15.0': + resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@oxc-project/types@0.132.0': + resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==} + + '@playwright/test@1.60.0': + resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==} + engines: {node: '>=18'} + hasBin: true + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-avatar@1.1.11': + resolution: {integrity: sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.3': + resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.6': + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.2.6': + resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-is-hydrated@0.1.0': + resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@rolldown/binding-android-arm64@1.0.2': + resolution: {integrity: sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.2': + resolution: {integrity: sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.2': + resolution: {integrity: sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.2': + resolution: {integrity: sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.2': + resolution: {integrity: sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.2': + resolution: {integrity: sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.2': + resolution: {integrity: sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.2': + resolution: {integrity: sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.2': + resolution: {integrity: sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.2': + resolution: {integrity: sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.2': + resolution: {integrity: sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.2': + resolution: {integrity: sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.2': + resolution: {integrity: sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.2': + resolution: {integrity: sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.2': + resolution: {integrity: sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tanstack/query-core@5.100.10': + resolution: {integrity: sha512-8UR0yJR+GiQ40m3lPhUr0xbfAupe6GSQiksSBSa9SM2NjezFyxXCIA69/lz8cSoNKZLrw1/PktIyQBJcVeMi3w==} + + '@tanstack/query-devtools@5.100.10': + resolution: {integrity: sha512-3DmJf25hDPus5IpVvp6ujXv6bKV2zPzI9vpbAmpJigsL/H6DPvPjmf7/Q9yVKEke//8fgeQ45abjgnLuyYxAiw==} + + '@tanstack/react-query-devtools@5.100.10': + resolution: {integrity: sha512-zes0+o9ef5rAZXJ9f/SeaLs2nufJaeVkZkl/Or9NGrWVF41kL9Od9ED9nCwtQlgiF2VGtrzhEw5AU/igAO+aAg==} + peerDependencies: + '@tanstack/react-query': ^5.100.10 + react: ^18 || ^19 + + '@tanstack/react-query@5.100.10': + resolution: {integrity: sha512-FLaZf2RCrA/Zgp4aiu5tG3TyasTRO7aZ99skxQpr3Hg/zXOhu6yq5FZCYQ/tRaJtM9ylnoK8tFK7PolXQadv6Q==} + peerDependencies: + react: ^18 || ^19 + + '@tanstack/react-virtual@3.13.24': + resolution: {integrity: sha512-aIJvz5OSkhNIhZIpYivrxrPTKYsjW9Uzy+sP/mx0S3sev2HyvPb7xmjbYvokzEpfgYHy/HjzJ2zFAETuUfgCpg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/virtual-core@3.14.0': + resolution: {integrity: sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q==} + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/history@4.7.11': + resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==} + + '@types/node@25.8.0': + resolution: {integrity: sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react-router-dom@5.3.3': + resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} + + '@types/react-router@5.1.20': + resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + + '@vitejs/plugin-react@6.0.2': + resolution: {integrity: sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + + '@vitest/coverage-v8@4.1.6': + resolution: {integrity: sha512-36l628fQ/9a/8ihy97eOtEnvWQEdqULQOJtcaxtoNq0G1w3Mxd4szSahOaMM9/NGyZ+hyKcMtIW/WIxq0XQViQ==} + peerDependencies: + '@vitest/browser': 4.1.6 + vitest: 4.1.6 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@4.1.6': + resolution: {integrity: sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==} + + '@vitest/mocker@4.1.6': + resolution: {integrity: sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.6': + resolution: {integrity: sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==} + + '@vitest/runner@4.1.6': + resolution: {integrity: sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==} + + '@vitest/snapshot@4.1.6': + resolution: {integrity: sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==} + + '@vitest/spy@4.1.6': + resolution: {integrity: sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==} + + '@vitest/utils@4.1.6': + resolution: {integrity: sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==} + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.2: + resolution: {integrity: sha512-dKmJxJsGItLmc5CYZKuEjuG6GnBs6PG4gohMhyFOWKaNQoYCuRZJDECaBlHmcG0lv2wc2E0uU8lESmBEumC3DQ==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + autoprefixer@10.5.0: + resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + axios@1.16.1: + resolution: {integrity: sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==} + + baseline-browser-mapping@2.10.30: + resolution: {integrity: sha512-xjOFN16Ha1+Rz4nFYKqHU/LSB+gx/Vi3yQLX7r7sAW+Wa+8hhF2h4pvqTrTMc8+WcDBEunnUurr46Jvv0jk3Vg==} + engines: {node: '>=6.0.0'} + hasBin: true + + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001792: + resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + cssstyle@6.2.0: + resolution: {integrity: sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==} + engines: {node: '>=20'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + electron-to-chromium@1.5.357: + resolution: {integrity: sha512-NHlTIQDK8fmVwHwuIzmXYEJ1Ewq3D9wDNc0cWXxDGysP6Pb21giwGNkxiTifyKy/4SoPuN5l6GLP1W9Sv7zB2g==} + + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} + + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsdom@28.1.0: + resolution: {integrity: sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jwt-decode@4.0.0: + resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} + engines: {node: '>=18'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lru-cache@11.3.6: + resolution: {integrity: sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==} + engines: {node: 20 || >=22} + + lucide-react@1.16.0: + resolution: {integrity: sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.3: + resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.44: + resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + oidc-client-ts@3.5.0: + resolution: {integrity: sha512-l2q8l9CTCTOlbX+AnK4p3M+4CEpKpyQhle6blQkdFhm0IsBqsxm15bYaSa11G7pWdsYr6epdsRZxJpCyCRbT8A==} + engines: {node: '>=18'} + + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + playwright-core@1.60.0: + resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.60.0: + resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} + engines: {node: '>=18'} + hasBin: true + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + 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 + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.14: + resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@19.2.6: + resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} + peerDependencies: + react: ^19.2.6 + + react-hook-form@7.76.0: + resolution: {integrity: sha512-eKtLGgFeSgkHqQD8J59AMZ9a4uD1D83iSIzt4YlTGD7liDen5rrjcUO1rVIGd9yC1gofryjtHbv+4ny4hkLWlw==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-oidc-context@3.3.1: + resolution: {integrity: sha512-/Azvm9W4DhhOtSDBE73kFInh1b6zZRRfILKbgmk2syExMF0PCYJOn/dGdOOi2BFX8x0rCeUe45NXHU+/+xDcrQ==} + engines: {node: '>=18'} + peerDependencies: + oidc-client-ts: ^3.1.0 + react: '>=16.14.0' + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-router-dom@7.15.1: + resolution: {integrity: sha512-AzF62gjY6U9rkMq4RfP/r2EVtQ7DMfNMjyOp/flLTCrtRylLiK4wT4pSq6O8rOXZ2eXdZYJPEYe+ifomiv+Igg==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.15.1: + resolution: {integrity: sha512-R8rl9HhgikFYoPJymnUtPXWbnDb3oget6lQnfIoupbt61aT9aOhRkDsY2XRhZRyX1Z/8a5sL74fXmFNm3NRK5A==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@19.2.6: + resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rolldown@1.0.2: + resolution: {integrity: sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tailwind-merge@3.6.0: + resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} + + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + + tailwindcss@3.4.19: + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.1.2: + resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} + engines: {node: '>=18'} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tldts-core@7.0.30: + resolution: {integrity: sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==} + + tldts@7.0.30: + resolution: {integrity: sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==} + hasBin: true + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + undici@7.25.0: + resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} + engines: {node: '>=20.18.1'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite@8.0.14: + resolution: {integrity: sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.6: + resolution: {integrity: sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.6 + '@vitest/browser-preview': 4.1.6 + '@vitest/browser-webdriverio': 4.1.6 + '@vitest/coverage-istanbul': 4.1.6 + '@vitest/coverage-v8': 4.1.6 + '@vitest/ui': 4.1.6 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + +snapshots: + + '@acemir/cssom@0.9.31': {} + + '@adobe/css-tools@4.4.4': {} + + '@alloc/quick-lru@5.2.0': {} + + '@asamuzakjp/css-color@5.1.11': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@asamuzakjp/dom-selector@6.8.1': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.3.6 + + '@asamuzakjp/generational-cache@1.0.1': {} + + '@asamuzakjp/nwsapi@2.3.9': {} + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/runtime@7.29.2': {} + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@1.0.2': {} + + '@biomejs/biome@2.4.16': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.4.16 + '@biomejs/cli-darwin-x64': 2.4.16 + '@biomejs/cli-linux-arm64': 2.4.16 + '@biomejs/cli-linux-arm64-musl': 2.4.16 + '@biomejs/cli-linux-x64': 2.4.16 + '@biomejs/cli-linux-x64-musl': 2.4.16 + '@biomejs/cli-win32-arm64': 2.4.16 + '@biomejs/cli-win32-x64': 2.4.16 + + '@biomejs/cli-darwin-arm64@2.4.16': + optional: true + + '@biomejs/cli-darwin-x64@2.4.16': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.4.16': + optional: true + + '@biomejs/cli-linux-arm64@2.4.16': + optional: true + + '@biomejs/cli-linux-x64-musl@2.4.16': + optional: true + + '@biomejs/cli-linux-x64@2.4.16': + optional: true + + '@biomejs/cli-win32-arm64@2.4.16': + optional: true + + '@biomejs/cli-win32-x64@2.4.16': + optional: true + + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@csstools/color-helpers@6.0.2': {} + + '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.0.2 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.4(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@exodus/bytes@1.15.0': {} + + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/react-dom@2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + + '@floating-ui/utils@0.2.11': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@oxc-project/types@0.132.0': {} + + '@playwright/test@1.60.0': + dependencies: + playwright: 1.60.0 + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-avatar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-context': 1.1.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-context@1.1.3(@types/react@19.2.14)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + aria-hidden: 1.2.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.14)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-id@1.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + aria-hidden: 1.2.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/rect': 1.1.1 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + aria-hidden: 1.2.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-slot@1.2.4(@types/react@19.2.14)(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.14)(react@19.2.6)': + dependencies: + react: 19.2.6 + use-sync-external-store: 1.6.0(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/rect@1.1.1': {} + + '@rolldown/binding-android-arm64@1.0.2': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.2': + optional: true + + '@rolldown/binding-darwin-x64@1.0.2': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.2': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.2': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.2': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.2': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.2': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.2': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.2': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.2': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.2': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.2': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.2': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.2': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@standard-schema/spec@1.1.0': {} + + '@tanstack/query-core@5.100.10': {} + + '@tanstack/query-devtools@5.100.10': {} + + '@tanstack/react-query-devtools@5.100.10(@tanstack/react-query@5.100.10(react@19.2.6))(react@19.2.6)': + dependencies: + '@tanstack/query-devtools': 5.100.10 + '@tanstack/react-query': 5.100.10(react@19.2.6) + react: 19.2.6 + + '@tanstack/react-query@5.100.10(react@19.2.6)': + dependencies: + '@tanstack/query-core': 5.100.10 + react: 19.2.6 + + '@tanstack/react-virtual@3.13.24(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@tanstack/virtual-core': 3.14.0 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + + '@tanstack/virtual-core@3.14.0': {} + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.29.2 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@babel/runtime': 7.29.2 + '@testing-library/dom': 10.4.1 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/aria-query@5.0.4': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} + + '@types/history@4.7.11': {} + + '@types/node@25.8.0': + dependencies: + undici-types: 7.24.6 + + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + + '@types/react-router-dom@5.3.3': + dependencies: + '@types/history': 4.7.11 + '@types/react': 19.2.14 + '@types/react-router': 5.1.20 + + '@types/react-router@5.1.20': + dependencies: + '@types/history': 4.7.11 + '@types/react': 19.2.14 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + + '@vitejs/plugin-react@6.0.2(vite@8.0.14(@types/node@25.8.0)(jiti@1.21.7))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.0.14(@types/node@25.8.0)(jiti@1.21.7) + + '@vitest/coverage-v8@4.1.6(vitest@4.1.6)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.6 + ast-v8-to-istanbul: 1.0.2 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.3 + obug: 2.1.1 + std-env: 4.1.0 + tinyrainbow: 3.1.0 + vitest: 4.1.6(@types/node@25.8.0)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.14(@types/node@25.8.0)(jiti@1.21.7)) + + '@vitest/expect@4.1.6': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.6 + '@vitest/utils': 4.1.6 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.6(vite@8.0.14(@types/node@25.8.0)(jiti@1.21.7))': + dependencies: + '@vitest/spy': 4.1.6 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.14(@types/node@25.8.0)(jiti@1.21.7) + + '@vitest/pretty-format@4.1.6': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.6': + dependencies: + '@vitest/utils': 4.1.6 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.6': + dependencies: + '@vitest/pretty-format': 4.1.6 + '@vitest/utils': 4.1.6 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.6': {} + + '@vitest/utils@4.1.6': + dependencies: + '@vitest/pretty-format': 4.1.6 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + agent-base@7.1.4: {} + + ansi-regex@5.0.1: {} + + ansi-styles@5.2.0: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + arg@5.0.2: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.2: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + + asynckit@0.4.0: {} + + autoprefixer@10.5.0(postcss@8.5.14): + dependencies: + browserslist: 4.28.2 + caniuse-lite: 1.0.30001792 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.14 + postcss-value-parser: 4.2.0 + + axios@1.16.1: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.5 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + + baseline-browser-mapping@2.10.30: {} + + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + + binary-extensions@2.3.0: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.30 + caniuse-lite: 1.0.30001792 + electron-to-chromium: 1.5.357 + node-releases: 2.0.44 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001792: {} + + chai@6.2.2: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + clsx@2.1.1: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@4.1.1: {} + + convert-source-map@2.0.0: {} + + cookie@1.1.1: {} + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css.escape@1.5.1: {} + + cssesc@3.0.0: {} + + cssstyle@6.2.0: + dependencies: + '@asamuzakjp/css-color': 5.1.11 + '@csstools/css-syntax-patches-for-csstree': 1.1.4(css-tree@3.2.1) + css-tree: 3.2.1 + lru-cache: 11.3.6 + + csstype@3.2.3: {} + + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + delayed-stream@1.0.0: {} + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + detect-node-es@1.1.0: {} + + didyoumean@1.2.2: {} + + dlv@1.1.3: {} + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + electron-to-chromium@1.5.357: {} + + entities@8.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@2.1.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.3 + + escalade@3.2.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + expect-type@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + follow-redirects@1.16.0: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.3 + mime-types: 2.1.35 + + fraction.js@5.3.4: {} + + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + 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.3 + math-intrinsics: 1.1.0 + + get-nonce@1.0.1: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + gopd@1.2.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.3: + dependencies: + function-bind: 1.1.2 + + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.0 + transitivePeerDependencies: + - '@noble/hashes' + + html-escaper@2.0.2: {} + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + indent-string@4.0.0: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.3 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jiti@1.21.7: {} + + js-tokens@10.0.0: {} + + js-tokens@4.0.0: {} + + jsdom@28.1.0: + dependencies: + '@acemir/cssom': 0.9.31 + '@asamuzakjp/dom-selector': 6.8.1 + '@bramus/specificity': 2.4.2 + '@exodus/bytes': 1.15.0 + cssstyle: 6.2.0 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.1 + undici: 7.25.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + - supports-color + + jwt-decode@4.0.0: {} + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + lru-cache@11.3.6: {} + + lucide-react@1.16.0(react@19.2.6): + dependencies: + react: 19.2.6 + + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.3: + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.8.1 + + math-intrinsics@1.1.0: {} + + mdn-data@2.27.1: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + min-indent@1.0.1: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.12: {} + + node-releases@2.0.44: {} + + normalize-path@3.0.0: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + obug@2.1.1: {} + + oidc-client-ts@3.5.0: + dependencies: + jwt-decode: 4.0.0 + + parse5@8.0.1: + dependencies: + entities: 8.0.0 + + path-parse@1.0.7: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + + playwright-core@1.60.0: {} + + playwright@1.60.0: + dependencies: + playwright-core: 1.60.0 + optionalDependencies: + fsevents: 2.3.2 + + postcss-import@15.1.0(postcss@8.5.14): + dependencies: + postcss: 8.5.14 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.12 + + postcss-js@4.1.0(postcss@8.5.14): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.14 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.14): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.14 + + postcss-nested@6.2.0(postcss@8.5.14): + dependencies: + postcss: 8.5.14 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.14: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + proxy-from-env@2.1.0: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + react-dom@19.2.6(react@19.2.6): + dependencies: + react: 19.2.6 + scheduler: 0.27.0 + + react-hook-form@7.76.0(react@19.2.6): + dependencies: + react: 19.2.6 + + react-is@17.0.2: {} + + react-oidc-context@3.3.1(oidc-client-ts@3.5.0)(react@19.2.6): + dependencies: + oidc-client-ts: 3.5.0 + react: 19.2.6 + + react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.6): + dependencies: + react: 19.2.6 + react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.6) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.14 + + react-remove-scroll@2.7.2(@types/react@19.2.14)(react@19.2.6): + dependencies: + react: 19.2.6 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.14)(react@19.2.6) + react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.6) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.14)(react@19.2.6) + use-sidecar: 1.1.3(@types/react@19.2.14)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + + react-router-dom@7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-router: 7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + + react-router@7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + cookie: 1.1.1 + react: 19.2.6 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 19.2.6(react@19.2.6) + + react-style-singleton@2.2.3(@types/react@19.2.14)(react@19.2.6): + dependencies: + get-nonce: 1.0.1 + react: 19.2.6 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.14 + + react@19.2.6: {} + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + require-from-string@2.0.2: {} + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rolldown@1.0.2: + dependencies: + '@oxc-project/types': 0.132.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.2 + '@rolldown/binding-darwin-arm64': 1.0.2 + '@rolldown/binding-darwin-x64': 1.0.2 + '@rolldown/binding-freebsd-x64': 1.0.2 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.2 + '@rolldown/binding-linux-arm64-gnu': 1.0.2 + '@rolldown/binding-linux-arm64-musl': 1.0.2 + '@rolldown/binding-linux-ppc64-gnu': 1.0.2 + '@rolldown/binding-linux-s390x-gnu': 1.0.2 + '@rolldown/binding-linux-x64-gnu': 1.0.2 + '@rolldown/binding-linux-x64-musl': 1.0.2 + '@rolldown/binding-openharmony-arm64': 1.0.2 + '@rolldown/binding-wasm32-wasi': 1.0.2 + '@rolldown/binding-win32-arm64-msvc': 1.0.2 + '@rolldown/binding-win32-x64-msvc': 1.0.2 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.27.0: {} + + semver@7.8.1: {} + + set-cookie-parser@2.7.2: {} + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@4.1.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.16 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + symbol-tree@3.2.4: {} + + tailwind-merge@3.6.0: {} + + tailwindcss-animate@1.0.7(tailwindcss@3.4.19): + dependencies: + tailwindcss: 3.4.19 + + tailwindcss@3.4.19: + 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.3 + 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.5.14 + postcss-import: 15.1.0(postcss@8.5.14) + postcss-js: 4.1.0(postcss@8.5.14) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.14) + postcss-nested: 6.2.0(postcss@8.5.14) + postcss-selector-parser: 6.1.2 + resolve: 1.22.12 + sucrase: 3.35.1 + transitivePeerDependencies: + - tsx + - yaml + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinybench@2.9.0: {} + + tinyexec@1.1.2: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinyrainbow@3.1.0: {} + + tldts-core@7.0.30: {} + + tldts@7.0.30: + dependencies: + tldts-core: 7.0.30 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tough-cookie@6.0.1: + dependencies: + tldts: 7.0.30 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + + ts-interface-checker@0.1.13: {} + + tslib@2.8.1: {} + + typescript@6.0.3: {} + + undici-types@7.24.6: {} + + undici@7.25.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.6): + dependencies: + react: 19.2.6 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.14 + + use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.6): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.6 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.14 + + use-sync-external-store@1.6.0(react@19.2.6): + dependencies: + react: 19.2.6 + + util-deprecate@1.0.2: {} + + vite@8.0.14(@types/node@25.8.0)(jiti@1.21.7): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.2 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 25.8.0 + fsevents: 2.3.3 + jiti: 1.21.7 + + vitest@4.1.6(@types/node@25.8.0)(@vitest/coverage-v8@4.1.6)(jsdom@28.1.0)(vite@8.0.14(@types/node@25.8.0)(jiti@1.21.7)): + dependencies: + '@vitest/expect': 4.1.6 + '@vitest/mocker': 4.1.6(vite@8.0.14(@types/node@25.8.0)(jiti@1.21.7)) + '@vitest/pretty-format': 4.1.6 + '@vitest/runner': 4.1.6 + '@vitest/snapshot': 4.1.6 + '@vitest/spy': 4.1.6 + '@vitest/utils': 4.1.6 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.1.2 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 8.0.14(@types/node@25.8.0)(jiti@1.21.7) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.8.0 + '@vitest/coverage-v8': 4.1.6(vitest@4.1.6) + jsdom: 28.1.0 + transitivePeerDependencies: + - msw + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.15.0 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + zod@4.4.3: {} diff --git a/baron-sso/adminfront/postcss.config.js b/baron-sso/adminfront/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/baron-sso/adminfront/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/baron-sso/adminfront/public/LINE_WORKS_Appicon.svg b/baron-sso/adminfront/public/LINE_WORKS_Appicon.svg new file mode 100644 index 0000000..7cf048c --- /dev/null +++ b/baron-sso/adminfront/public/LINE_WORKS_Appicon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/baron-sso/adminfront/public/vite.svg b/baron-sso/adminfront/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/baron-sso/adminfront/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/baron-sso/adminfront/scripts/runtime-mode.sh b/baron-sso/adminfront/scripts/runtime-mode.sh new file mode 100644 index 0000000..feffa2b --- /dev/null +++ b/baron-sso/adminfront/scripts/runtime-mode.sh @@ -0,0 +1,144 @@ +#!/usr/bin/env sh +set -eu + +app_env="$(printf '%s' "${APP_ENV:-development}" | tr '[:upper:]' '[:lower:]')" + +if [ -z "${VITE_ADMIN_PUBLIC_URL:-}" ] && [ -n "${ADMINFRONT_URL:-}" ]; then + export VITE_ADMIN_PUBLIC_URL="$ADMINFRONT_URL" +fi + +if [ -z "${VITE_ADMIN_PUBLIC_URL:-}" ] && [ -n "${ADMINFRONT_CALLBACK_URLS:-}" ]; then + first_admin_callback="${ADMINFRONT_CALLBACK_URLS%%,*}" + case "$first_admin_callback" in + http://*/auth/callback | https://*/auth/callback) + export VITE_ADMIN_PUBLIC_URL="${first_admin_callback%/auth/callback}" + ;; + esac +fi + +case "$app_env" in + production|prod|stage|staging) + mode="production" + ;; + *) + mode="development" + ;; +esac + +if [ "${1:-}" = "--print-admin-public-url" ]; then + printf '%s\n' "${VITE_ADMIN_PUBLIC_URL:-}" + exit 0 +fi + +if [ "${1:-}" = "--print-mode" ]; then + printf '%s\n' "$mode" + exit 0 +fi + +ensure_frontend_dependencies() { + APP_PACKAGE_NAME="adminfront" + + # Detect workspace root + if [ -f "/workspace/pnpm-workspace.yaml" ]; then + WORKSPACE_ROOT="/workspace" + elif [ -f "../../pnpm-workspace.yaml" ]; then + WORKSPACE_ROOT="../.." + else + WORKSPACE_ROOT="" + fi + + # Manage dependencies from the real workspace tree if possible, otherwise use current dir. + if [ -n "$WORKSPACE_ROOT" ]; then + WORKSPACE_DIR="$WORKSPACE_ROOT" + LOCK_FILE="$WORKSPACE_ROOT/pnpm-lock.yaml" + COMMON_PACKAGE_FILE="$WORKSPACE_ROOT/common/package.json" + INSTALL_CMD="cd $WORKSPACE_ROOT && CI=true pnpm install --filter ${APP_PACKAGE_NAME}... --frozen-lockfile --ignore-scripts" + elif [ -f "pnpm-lock.yaml" ]; then + WORKSPACE_DIR="." + LOCK_FILE="pnpm-lock.yaml" + COMMON_PACKAGE_FILE="/workspace/common/package.json" + INSTALL_CMD="CI=true pnpm install --frozen-lockfile --ignore-scripts" + else + WORKSPACE_DIR="." + LOCK_FILE="package-lock.json" + COMMON_PACKAGE_FILE="/workspace/common/package.json" + INSTALL_CMD="npm ci" + fi + + if [ ! -f "$WORKSPACE_DIR/package.json" ]; then + return 0 + fi + + lock_mode="" + lock_file="$WORKSPACE_DIR/.baron-deps-install.lock" + + acquire_install_lock() { + if command -v flock >/dev/null 2>&1; then + lock_mode="flock" + exec 9>"$lock_file" + flock 9 + trap 'release_install_lock' EXIT INT TERM + return 0 + fi + + lock_mode="mkdir" + while ! mkdir "$lock_file" 2>/dev/null; do + sleep 1 + done + trap 'release_install_lock' EXIT INT TERM + } + + release_install_lock() { + trap - EXIT INT TERM + + if [ "$lock_mode" = "flock" ]; then + flock -u 9 || true + exec 9>&- + return 0 + fi + + if [ "$lock_mode" = "mkdir" ]; then + rmdir "$lock_file" >/dev/null 2>&1 || true + fi + } + + if command -v sha256sum >/dev/null 2>&1; then + deps_hash="$(sha256sum "$WORKSPACE_DIR/package.json" "$LOCK_FILE" "$COMMON_PACKAGE_FILE" package.json 2>/dev/null | sha256sum | awk '{print $1}')" + else + deps_hash="$(cksum "$WORKSPACE_DIR/package.json" "$LOCK_FILE" "$COMMON_PACKAGE_FILE" package.json 2>/dev/null | cksum | awk '{print $1}')" + fi + deps_stamp="node_modules/.baron-deps-hash" + installed_hash="$(cat "$deps_stamp" 2>/dev/null || true)" + + if [ "$installed_hash" != "$deps_hash" ]; then + echo "Installing frontend dependencies..." + acquire_install_lock + if command -v sha256sum >/dev/null 2>&1; then + deps_hash="$(sha256sum "$WORKSPACE_DIR/package.json" "$LOCK_FILE" "$COMMON_PACKAGE_FILE" package.json 2>/dev/null | sha256sum | awk '{print $1}')" + else + deps_hash="$(cksum "$WORKSPACE_DIR/package.json" "$LOCK_FILE" "$COMMON_PACKAGE_FILE" package.json 2>/dev/null | cksum | awk '{print $1}')" + fi + installed_hash="$(cat "$deps_stamp" 2>/dev/null || true)" + if [ "$installed_hash" = "$deps_hash" ]; then + release_install_lock + return 0 + fi + + eval "$INSTALL_CMD" + + mkdir -p node_modules + printf '%s\n' "$deps_hash" > "$deps_stamp" + release_install_lock + fi +} + +ensure_frontend_dependencies + +if [ "$mode" = "production" ]; then + echo "Running in production mode with custom static server..." + export ADMINFRONT_BUILD_OUT_DIR="${ADMINFRONT_BUILD_OUT_DIR:-/tmp/baron-sso-adminfront-dist}" + exec sh -c "npm run build && node ./scripts/serve-prod.mjs" +fi + +echo "Running in development mode..." +exec npm run dev -- --host 0.0.0.0 diff --git a/baron-sso/adminfront/scripts/serve-prod.mjs b/baron-sso/adminfront/scripts/serve-prod.mjs new file mode 100644 index 0000000..ec25704 --- /dev/null +++ b/baron-sso/adminfront/scripts/serve-prod.mjs @@ -0,0 +1,160 @@ +import { readFile, stat } from "node:fs/promises"; +import { createServer } from "node:http"; +import { extname, join, normalize, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const _rootDir = fileURLToPath(new URL("..", import.meta.url)); +const distDir = resolve( + process.env.ADMINFRONT_BUILD_OUT_DIR ?? "/tmp/baron-sso-adminfront-dist", +); +const host = process.env.HOST ?? "0.0.0.0"; +const port = Number(process.env.PORT ?? process.env.ADMINFRONT_PORT ?? 5173); +const backendTarget = new URL( + process.env.API_PROXY_TARGET || "http://localhost:3000", +); + +const contentTypes = { + ".css": "text/css; charset=utf-8", + ".html": "text/html; charset=utf-8", + ".js": "application/javascript; charset=utf-8", + ".json": "application/json; charset=utf-8", + ".map": "application/json; charset=utf-8", + ".mjs": "application/javascript; charset=utf-8", + ".svg": "image/svg+xml", +}; + +function getContentType(filePath) { + return ( + contentTypes[extname(filePath).toLowerCase()] ?? "application/octet-stream" + ); +} + +function sendJson(res, statusCode, body) { + res.writeHead(statusCode, { + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "no-store", + }); + res.end(JSON.stringify(body)); +} + +function toSafePath(pathname) { + const decoded = decodeURIComponent(pathname); + const relative = decoded.replace(/^\/+/, ""); + const safe = normalize(relative).replace(/^(\.\.(?:[\\/]|$))+/, ""); + return join(distDir, safe); +} + +async function tryReadFile(filePath) { + try { + return await readFile(filePath); + } catch { + return null; + } +} + +async function proxyToBackend(req, res, pathname, search) { + const target = new URL(pathname + search, backendTarget); + const headers = new Headers(); + + for (const [key, value] of Object.entries(req.headers)) { + if (!value) continue; + if (key === "host" || key === "content-length" || key === "connection") { + continue; + } + if (Array.isArray(value)) { + headers.set(key, value.join(", ")); + continue; + } + headers.set(key, value); + } + + const hasBody = !["GET", "HEAD"].includes(req.method ?? "GET"); + const response = await fetch(target, { + method: req.method, + headers, + body: hasBody ? req : undefined, + duplex: hasBody ? "half" : undefined, + }); + + const responseHeaders = new Headers(response.headers); + responseHeaders.delete("content-length"); + responseHeaders.delete("transfer-encoding"); + responseHeaders.delete("connection"); + + res.writeHead(response.status, Object.fromEntries(responseHeaders.entries())); + + if (req.method === "HEAD") { + res.end(); + return; + } + + const arrayBuffer = await response.arrayBuffer(); + res.end(Buffer.from(arrayBuffer)); +} + +async function serveStatic(req, res, pathname) { + const indexPath = join(distDir, "index.html"); + const filePath = toSafePath(pathname); + + let resolvedPath = filePath; + try { + const fileStat = await stat(resolvedPath); + if (fileStat.isDirectory()) { + resolvedPath = join(resolvedPath, "index.html"); + } + } catch { + resolvedPath = indexPath; + } + + let body = await tryReadFile(resolvedPath); + if (!body) { + body = await tryReadFile(indexPath); + resolvedPath = indexPath; + } + + if (!body) { + sendJson(res, 500, { error: "dist_not_found" }); + return; + } + + res.writeHead(200, { + "Content-Type": getContentType(resolvedPath), + "Cache-Control": resolvedPath.endsWith("index.html") + ? "no-cache" + : "public, max-age=31536000, immutable", + }); + + if (req.method === "HEAD") { + res.end(); + return; + } + + res.end(body); +} + +createServer(async (req, res) => { + try { + const url = new URL( + req.url ?? "/", + `http://${req.headers.host ?? "localhost"}`, + ); + const { pathname, search } = url; + + if (pathname === "/api" || pathname.startsWith("/api/")) { + await proxyToBackend(req, res, pathname, search); + return; + } + + const normalizedPath = pathname === "/" ? "/index.html" : pathname; + await serveStatic(req, res, normalizedPath); + } catch (error) { + sendJson(res, 500, { + error: "internal_server_error", + message: error instanceof Error ? error.message : String(error), + }); + } +}).listen(port, host, () => { + console.log( + `Adminfront production server listening on http://${host}:${port}`, + ); +}); diff --git a/baron-sso/adminfront/seed-tenant.csv b/baron-sso/adminfront/seed-tenant.csv new file mode 100644 index 0000000..5eabc9b --- /dev/null +++ b/baron-sso/adminfront/seed-tenant.csv @@ -0,0 +1,13 @@ +id,name,type,parent_tenant_slug,slug,memo,email_domain,visibility,org_unit_type,worksmobile_sync +038326b6-954a-48a7-a85f-efd83f62b82a,한맥가족,COMPANY_GROUP,,hanmac-family,한맥가족 기본 루트 테넌트,,,, +5530ca6e-c5e6-4bf0-84d6-76c6a8fb70ee,총괄기획&기술개발센터,COMPANY,hanmac-family,gpdtdc,네이버웍스 총괄기획&기술개발센터 GPDTDC_DOMAIN_ID, baroncs.co.kr,,, +9caf62e1-297d-4e8f-870b-61780998bbeb,삼안,COMPANY,hanmac-family,saman,네이버웍스 삼안 SAMAN_DOMAIN_ID, samaneng.com,,, +369c1843-56af-4344-9c21-0e01197ab861,한맥기술,COMPANY,hanmac-family,hanmac,네이버웍스 한맥 HANMAC_DOMAIN_ID, hanmaceng.co.kr,,, +96369f12-6b66-4b2a-a916-d1c99d326f02,바론그룹,COMPANY_GROUP,hanmac-family,baron-group,네이버웍스 바론그룹 BARONGROUP_DOMAIN_ID,brsw.kr,,, +5a03efd2-e62f-4243-800d-58334bf48b2f,한라산업개발,COMPANY,hanmac-family,halla,네이버웍스 한라 HALLA_DOMAIN_ID,hallasanup.com,,, +c18a8284-0008-48aa-9cdf-9f47ab79a2a9,(주)장헌,COMPANY,baron-group,jangheon,,jangheon.com,,, +b2fcf17f-7085-4bfe-9663-d8a2f2f4b2d6,장헌산업,COMPANY,baron-group,jangheon-sanup,,jangheon.co.kr,,, +e57cb22c-383e-4489-8c2f-0c5431917e86,(주)피티씨,COMPANY,baron-group,ptc,,pre-cast.co.kr,,, +4d0f26b9-702c-4bc6-8996-46e9eedfdeb7,MH_manager,USER_GROUP,hanmac-family,mhd,맨아워 대시보드 권한 보유자그룹,,private,,no +e41adf79-3d15-4807-8303-afbdb0f2bab7,SW_uploader,USER_GROUP,hanmac-family,sw-uploader,소프트웨어 배포 권한 그룹,,private,,no +9607eb7b-04d2-42ab-80fe-780fe21c7e8f,Personal,PERSONAL,,personal,개인 사용자 기본 루트 테넌트,,,, diff --git a/baron-sso/adminfront/src/app/queryClient.ts b/baron-sso/adminfront/src/app/queryClient.ts new file mode 100644 index 0000000..9064efe --- /dev/null +++ b/baron-sso/adminfront/src/app/queryClient.ts @@ -0,0 +1,7 @@ +import { QueryClient } from "@tanstack/react-query"; + +import { queryClientDefaultOptions } from "../../../common/core/query/queryClient"; + +export const queryClient = new QueryClient({ + defaultOptions: queryClientDefaultOptions, +}); diff --git a/baron-sso/adminfront/src/app/routes.test.tsx b/baron-sso/adminfront/src/app/routes.test.tsx new file mode 100644 index 0000000..64ff532 --- /dev/null +++ b/baron-sso/adminfront/src/app/routes.test.tsx @@ -0,0 +1,62 @@ +import { matchRoutes } from "react-router-dom"; +import { describe, expect, it } from "vitest"; +import { buildAdminAuthRedirectUris } from "../lib/authConfig"; +import { adminRoutes } from "./routes"; + +describe("admin routes", () => { + it("accepts the auth callback path generated from the public admin URL", () => { + const { redirectUri } = buildAdminAuthRedirectUris( + "https://sadmin.hmac.kr", + ); + const callbackPath = new URL(redirectUri).pathname; + + const matches = matchRoutes(adminRoutes, callbackPath); + + expect(callbackPath).toBe("/auth/callback"); + expect(matches?.at(-1)?.route.path).toBe("/auth/callback"); + }); + + it("registers the super-admin Ory SSOT system route", () => { + const matches = matchRoutes(adminRoutes, "/system/ory-ssot"); + + expect(matches?.at(-1)?.route.path).toBe("system/ory-ssot"); + }); + + it("registers the super-admin data integrity management route", () => { + const matches = matchRoutes(adminRoutes, "/system/data-integrity"); + + expect(matches?.at(-1)?.route.path).toBe("system/data-integrity"); + }); + + it("routes global custom claim settings before user detail id matching", () => { + const matches = matchRoutes(adminRoutes, "/users/custom-claims"); + const leafRoute = matches?.at(-1)?.route; + + expect(leafRoute?.path).toBe("users/custom-claims"); + expect(getRouteElementName(leafRoute?.element)).toBe( + "GlobalCustomClaimsPage", + ); + }); + + it("keeps protected admin pages behind an auth guard before mounting the layout", () => { + const rootRoute = adminRoutes.find((route) => route.path === "/"); + const protectedShellRoute = rootRoute?.children?.[0]; + + expect(getRouteElementName(rootRoute?.element)).toBe("AuthGuard"); + expect(getRouteElementName(protectedShellRoute?.element)).toBe("AppLayout"); + expect(protectedShellRoute?.children?.at(0)?.index).toBe(true); + }); +}); + +function getRouteElementName(element: unknown) { + if ( + typeof element === "object" && + element !== null && + "type" in element && + typeof element.type === "function" + ) { + return element.type.name; + } + + return undefined; +} diff --git a/baron-sso/adminfront/src/app/routes.tsx b/baron-sso/adminfront/src/app/routes.tsx new file mode 100644 index 0000000..339a14d --- /dev/null +++ b/baron-sso/adminfront/src/app/routes.tsx @@ -0,0 +1,81 @@ +import type { RouteObject } from "react-router-dom"; +import { createBrowserRouter } from "react-router-dom"; +import AppLayout from "../components/layout/AppLayout"; +import ApiKeyCreatePage from "../features/api-keys/ApiKeyCreatePage"; +import ApiKeyListPage from "../features/api-keys/ApiKeyListPage"; +import AuditLogsPage from "../features/audit/AuditLogsPage"; +import AuthCallbackPage from "../features/auth/AuthCallbackPage"; +import AuthGuard from "../features/auth/AuthGuard"; +import AuthPage from "../features/auth/AuthPage"; +import LoginPage from "../features/auth/LoginPage"; +import DataIntegrityPage from "../features/integrity/DataIntegrityPage"; +import GlobalOverviewPage from "../features/overview/GlobalOverviewPage"; +import UserProjectionPage from "../features/projections/UserProjectionPage"; +import { TenantAdminsAndOwnersTab } from "../features/tenants/routes/TenantAdminsAndOwnersTab"; +import TenantCreatePage from "../features/tenants/routes/TenantCreatePage"; +import TenantDetailPage from "../features/tenants/routes/TenantDetailPage"; +import TenantListPage from "../features/tenants/routes/TenantListPage"; +import { TenantProfilePage } from "../features/tenants/routes/TenantProfilePage"; +import { TenantSchemaPage } from "../features/tenants/routes/TenantSchemaPage"; +import { TenantWorksmobilePage } from "../features/tenants/routes/TenantWorksmobilePage"; +import TenantUserGroupsTab from "../features/user-groups/routes/TenantUserGroupsTab"; +import GlobalCustomClaimsPage from "../features/users/GlobalCustomClaimsPage"; +import UserCreatePage from "../features/users/UserCreatePage"; +import UserDetailPage from "../features/users/UserDetailPage"; +import UserListPage from "../features/users/UserListPage"; +import { ADMIN_AUTH_CALLBACK_PATH } from "../lib/authConfig"; + +export const adminRoutes: RouteObject[] = [ + { + path: "/login", + element: , + }, + { + path: ADMIN_AUTH_CALLBACK_PATH, + element: , + }, + { + path: "/", + element: , + children: [ + { + element: , + children: [ + { index: true, element: }, + { path: "audit-logs", element: }, + { path: "auth", element: }, + { path: "users", element: }, + { path: "users/custom-claims", element: }, + { path: "users/new", element: }, + { path: "users/:id", element: }, + { path: "tenants", element: }, + { path: "tenants/new", element: }, + { path: "worksmobile", element: }, + { + path: "tenants/:tenantId", + element: , + children: [ + { index: true, element: }, + { path: "permissions", element: }, + { path: "organization", element: }, + { path: "schema", element: }, + ], + }, + { + path: "tenants/:tenantId/organization/:id", + element: , + }, + { path: "api-keys", element: }, + { path: "api-keys/new", element: }, + { path: "system/ory-ssot", element: }, + { path: "system/data-integrity", element: }, + ], + }, + ], + }, +]; + +export const router = createBrowserRouter( + adminRoutes, + // React Router v7 플래그는 Provider에서 적용합니다. +); diff --git a/baron-sso/adminfront/src/assets/react.svg b/baron-sso/adminfront/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/baron-sso/adminfront/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/baron-sso/adminfront/src/components/auth/RoleGuard.tsx b/baron-sso/adminfront/src/components/auth/RoleGuard.tsx new file mode 100644 index 0000000..6872747 --- /dev/null +++ b/baron-sso/adminfront/src/components/auth/RoleGuard.tsx @@ -0,0 +1,43 @@ +import { useQuery } from "@tanstack/react-query"; +import type * as React from "react"; +import { fetchMe } from "../../lib/adminApi"; +import { normalizeAdminRole } from "../../lib/roles"; + +interface RoleGuardProps { + children: React.ReactNode; + roles: string[]; + fallback?: React.ReactNode; +} + +/** + * RoleGuard conditionally renders children based on the current user's role. + * + * Usage: + * + * + * + */ +export function RoleGuard({ + children, + roles, + fallback = null, +}: RoleGuardProps) { + const { data: profile, isLoading } = useQuery({ + queryKey: ["me"], + queryFn: fetchMe, + staleTime: 5 * 60 * 1000, // 5 minutes + }); + + if (isLoading) return null; + + const userRole = normalizeAdminRole(profile?.role); + const hasAccess = roles + .map((role) => normalizeAdminRole(role)) + .includes(userRole); + + if (!hasAccess) { + return <>{fallback}; + } + + return <>{children}; +} diff --git a/baron-sso/adminfront/src/components/common/LanguageSelector.test.tsx b/baron-sso/adminfront/src/components/common/LanguageSelector.test.tsx new file mode 100644 index 0000000..d49d654 --- /dev/null +++ b/baron-sso/adminfront/src/components/common/LanguageSelector.test.tsx @@ -0,0 +1,32 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import LanguageSelector from "./LanguageSelector"; + +vi.mock("../../lib/i18n", () => ({ + t: (_key: string, fallback?: string) => fallback ?? "", +})); + +describe("LanguageSelector", () => { + beforeEach(() => { + window.localStorage.clear(); + vi.restoreAllMocks(); + }); + + it("updates locale without reloading the page", () => { + const dispatchSpy = vi.spyOn(window, "dispatchEvent"); + window.localStorage.setItem("locale", "ko"); + + render(); + + fireEvent.change(screen.getByRole("combobox"), { + target: { value: "en" }, + }); + + expect(window.localStorage.getItem("locale")).toBe("en"); + expect( + dispatchSpy.mock.calls.some( + ([event]) => event instanceof Event && event.type === "localechange", + ), + ).toBe(true); + }); +}); diff --git a/baron-sso/adminfront/src/components/common/LanguageSelector.tsx b/baron-sso/adminfront/src/components/common/LanguageSelector.tsx new file mode 100644 index 0000000..94d3536 --- /dev/null +++ b/baron-sso/adminfront/src/components/common/LanguageSelector.tsx @@ -0,0 +1,69 @@ +import { useEffect, useState } from "react"; +import { LOCALE_STORAGE_KEY } from "../../../../common/core/i18n"; +import { t } from "../../lib/i18n"; + +const SUPPORTED_LOCALES = ["ko", "en"] as const; + +type Locale = (typeof SUPPORTED_LOCALES)[number]; + +function resolveLocale(): Locale { + if (typeof window === "undefined") { + return "ko"; + } + + const stored = window.localStorage.getItem(LOCALE_STORAGE_KEY); + if (stored === "ko" || stored === "en") { + return stored; + } + + const pathLocale = window.location.pathname.split("/")[1]; + if (pathLocale === "ko" || pathLocale === "en") { + return pathLocale; + } + + const browserLang = window.navigator.language.toLowerCase(); + return browserLang.startsWith("ko") ? "ko" : "en"; +} + +function LanguageSelector() { + const [locale, setLocale] = useState(resolveLocale()); + + useEffect(() => { + const syncLocale = () => { + setLocale(resolveLocale()); + }; + + window.addEventListener("localechange", syncLocale); + window.addEventListener("storage", syncLocale); + + return () => { + window.removeEventListener("localechange", syncLocale); + window.removeEventListener("storage", syncLocale); + }; + }, []); + + const handleChange = (next: Locale) => { + if (next === locale) { + return; + } + window.localStorage.setItem(LOCALE_STORAGE_KEY, next); + setLocale(next); + window.dispatchEvent(new Event("localechange")); + }; + + return ( + + ); +} + +export default LanguageSelector; diff --git a/baron-sso/adminfront/src/components/common/LocaleRefreshBoundary.test.tsx b/baron-sso/adminfront/src/components/common/LocaleRefreshBoundary.test.tsx new file mode 100644 index 0000000..24e945d --- /dev/null +++ b/baron-sso/adminfront/src/components/common/LocaleRefreshBoundary.test.tsx @@ -0,0 +1,34 @@ +import { act, render, screen } from "@testing-library/react"; +import { beforeEach, describe, expect, it } from "vitest"; +import LocaleRefreshBoundary from "./LocaleRefreshBoundary"; + +let renderCount = 0; + +function RenderCounter() { + renderCount += 1; + return {renderCount}; +} + +describe("LocaleRefreshBoundary", () => { + beforeEach(() => { + window.localStorage.clear(); + renderCount = 0; + }); + + it("re-renders children when locale changes", async () => { + render( + + + , + ); + + expect(screen.getByText("1")).toBeInTheDocument(); + + await act(async () => { + window.localStorage.setItem("locale", "en"); + window.dispatchEvent(new Event("localechange")); + }); + + expect(screen.getByText("2")).toBeInTheDocument(); + }); +}); diff --git a/baron-sso/adminfront/src/components/common/LocaleRefreshBoundary.tsx b/baron-sso/adminfront/src/components/common/LocaleRefreshBoundary.tsx new file mode 100644 index 0000000..64cc384 --- /dev/null +++ b/baron-sso/adminfront/src/components/common/LocaleRefreshBoundary.tsx @@ -0,0 +1,27 @@ +import { Fragment, type ReactNode, useEffect, useState } from "react"; + +type LocaleRefreshBoundaryProps = { + children: ReactNode; +}; + +function LocaleRefreshBoundary({ children }: LocaleRefreshBoundaryProps) { + const [localeVersion, setLocaleVersion] = useState(0); + + useEffect(() => { + const syncLocale = () => { + setLocaleVersion((current) => current + 1); + }; + + window.addEventListener("localechange", syncLocale); + window.addEventListener("storage", syncLocale); + + return () => { + window.removeEventListener("localechange", syncLocale); + window.removeEventListener("storage", syncLocale); + }; + }, []); + + return {children}; +} + +export default LocaleRefreshBoundary; diff --git a/baron-sso/adminfront/src/components/layout/AppLayout.test.tsx b/baron-sso/adminfront/src/components/layout/AppLayout.test.tsx new file mode 100644 index 0000000..47c5d89 --- /dev/null +++ b/baron-sso/adminfront/src/components/layout/AppLayout.test.tsx @@ -0,0 +1,186 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { fireEvent, render, screen } from "@testing-library/react"; +import { MemoryRouter, Route, Routes } from "react-router-dom"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { createI18nMock } from "../../test/i18nMock"; +import AppLayout from "./AppLayout"; + +const authState = { + isAuthenticated: true, + isLoading: false, + user: { + access_token: "access-token", + expires_at: Math.floor(Date.now() / 1000) + 120, + profile: { + sub: "admin-1", + name: "Admin User", + email: "admin@example.com", + }, + }, + signinSilent: vi.fn(async () => undefined), + removeUser: vi.fn(), +}; + +vi.mock("react-oidc-context", () => ({ + useAuth: () => authState, +})); + +vi.mock("../../lib/i18n", () => createI18nMock()); + +vi.mock("../../lib/adminApi", () => ({ + fetchMe: vi.fn(async () => ({ + id: "admin-1", + name: "Fetched Admin", + email: "fetched@example.com", + role: "super_admin", + tenantId: "tenant-1", + manageableTenants: [ + { + id: "tenant-1", + name: "GPDTDC", + slug: "gpdtdc", + type: "COMPANY", + }, + { + id: "tenant-2", + name: "기술연구팀", + slug: "gpdtdc-rnd", + type: "ORGANIZATION", + }, + ], + })), +})); + +function renderLayout(entry = "/users") { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { retry: false }, + mutations: { retry: false }, + }, + }); + + return render( + + + + }> + Users outlet} /> + User detail outlet} /> + Tenant outlet} + /> + Worksmobile outlet} /> + Login outlet} /> + + + + , + ); +} + +describe("admin AppLayout", () => { + beforeEach(() => { + ( + window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean } + )._IS_TEST_MODE = true; + authState.isAuthenticated = true; + authState.isLoading = false; + authState.user.expires_at = Math.floor(Date.now() / 1000) + 120; + authState.signinSilent.mockClear(); + authState.removeUser.mockClear(); + window.localStorage.clear(); + vi.spyOn(window, "confirm").mockReturnValue(true); + }); + + it("renders admin navigation, fetched profile, and outlet content", async () => { + renderLayout(); + + expect(await screen.findByText("Fetched Admin")).toBeInTheDocument(); + expect(screen.getByText("Admin Control")).toBeInTheDocument(); + expect(screen.getByText("Users outlet")).toBeInTheDocument(); + expect(screen.getByText("Tenants")).toBeInTheDocument(); + expect(screen.getByText("Org Chart")).toBeInTheDocument(); + expect(screen.getByText("Worksmobile")).toBeInTheDocument(); + expect(screen.getByText("Ory SSOT System")).toBeInTheDocument(); + expect(screen.getByText("Data Integrity")).toBeInTheDocument(); + const navigation = screen.getByRole("navigation"); + const navLabels = Array.from(navigation.querySelectorAll("a")).map((link) => + link.textContent?.trim(), + ); + expect(navLabels).toEqual([ + "Overview", + "Tenants", + "Org Chart", + "Worksmobile", + "Ory SSOT System", + "Data Integrity", + "Users", + "Auth Guard", + "API Keys", + "Audit Logs", + ]); + const worksmobileIcon = screen.getByTestId("worksmobile-nav-icon"); + expect(worksmobileIcon.tagName.toLowerCase()).toBe("svg"); + expect(worksmobileIcon).toHaveAttribute("fill", "none"); + expect(worksmobileIcon.querySelectorAll("path")).toHaveLength(4); + expect(worksmobileIcon.querySelector('path[fill="white"]')).toBeNull(); + }); + + it("toggles the sidebar and persists the collapsed state", async () => { + renderLayout(); + + const collapseButton = await screen.findByRole("button", { + name: "사이드바 접기", + }); + fireEvent.click(collapseButton); + + expect(window.localStorage.getItem("baron_shell_sidebar_collapsed")).toBe( + "true", + ); + expect( + screen.getByRole("button", { name: "사이드바 펼치기" }), + ).toBeInTheDocument(); + }); + + it("opens profile menu, navigates, toggles theme/session, and logs out", async () => { + renderLayout(); + + const themeButton = await screen.findByRole("button", { + name: "테마 전환", + }); + fireEvent.click(themeButton); + expect(document.documentElement.classList.contains("dark")).toBe(true); + + fireEvent.click(screen.getByRole("button", { name: "계정 메뉴 열기" })); + expect(screen.getByText("Manageable Tenants")).toBeInTheDocument(); + + const sessionSwitch = screen.getByRole("switch"); + fireEvent.click(sessionSwitch); + expect(window.localStorage.getItem("baron_session_expiry_enabled")).toBe( + "false", + ); + + fireEvent.click(screen.getByText("기술연구팀")); + expect(await screen.findByText("Tenant outlet")).toBeInTheDocument(); + + fireEvent.click(screen.getByRole("button", { name: "계정 메뉴 열기" })); + fireEvent.click(screen.getAllByText("내 정보")[0]); + expect(await screen.findByText("User detail outlet")).toBeInTheDocument(); + + fireEvent.click(screen.getByRole("button", { name: "계정 메뉴 열기" })); + fireEvent.click(screen.getAllByText("Logout")[1]); + expect(window.confirm).toHaveBeenCalled(); + expect(authState.removeUser).toHaveBeenCalled(); + }, 10_000); + + it("attempts silent renewal on user activity when session is near expiry", async () => { + authState.user.expires_at = Math.floor(Date.now() / 1000) + 60; + + renderLayout(); + await screen.findByText("Fetched Admin"); + fireEvent.keyDown(window, { key: "Tab" }); + + expect(authState.signinSilent).toHaveBeenCalled(); + }); +}); diff --git a/baron-sso/adminfront/src/components/layout/AppLayout.tsx b/baron-sso/adminfront/src/components/layout/AppLayout.tsx new file mode 100644 index 0000000..3cf9fcf --- /dev/null +++ b/baron-sso/adminfront/src/components/layout/AppLayout.tsx @@ -0,0 +1,837 @@ +import { useQuery } from "@tanstack/react-query"; +import { + Building2, + ChevronDown, + Database, + Key, + KeyRound, + LayoutDashboard, + LogOut, + Moon, + Network, + NotebookTabs, + ShieldCheck, + ShieldHalf, + Sun, + User as UserIcon, + Users, +} from "lucide-react"; +import * as React from "react"; +import { useEffect, useRef, useState } from "react"; +import { useAuth } from "react-oidc-context"; +import { NavLink, Outlet, useLocation, useNavigate } from "react-router-dom"; +import { + AppSidebar, + applyShellTheme, + buildShellProfileSummary, + buildShellSessionStatus, + readShellSessionExpiryEnabled, + readShellSidebarCollapsed, + readShellTheme, + type ShellSidebarNavItem, + type ShellTranslator, + shellLayoutClasses, + writeShellSessionExpiryEnabled, + writeShellSidebarCollapsed, +} from "../../../../common/shell"; +import { canAccessWorksmobile } from "../../features/tenants/routes/worksmobileAccess"; +import { buildAuthenticatedOrgChartUrl } from "../../features/users/orgChartPicker"; +import { fetchMe } from "../../lib/adminApi"; +import { debugLog } from "../../lib/debugLog"; +import { t } from "../../lib/i18n"; +import { isSuperAdminRole } from "../../lib/roles"; +import { + shouldAttemptSlidingSessionRenew, + shouldAttemptUnlimitedSessionRenew, +} from "../../lib/sessionSliding"; +import LanguageSelector from "../common/LanguageSelector"; + +const LOCALE_CHANGED_EVENT = "baron_locale_changed"; + +const staticNavItems: ShellSidebarNavItem[] = [ + { + labelKey: "ui.admin.nav.overview", + labelFallback: "Overview", + to: "/", + icon: LayoutDashboard, + end: true, + }, + { + labelKey: "ui.admin.nav.users", + labelFallback: "Users", + to: "/users", + icon: Users, + }, + { + labelKey: "ui.admin.nav.auth_guard", + labelFallback: "Auth Guard", + to: "/auth", + icon: KeyRound, + }, + { + labelKey: "ui.admin.nav.api_keys", + labelFallback: "API Keys", + to: "/api-keys", + icon: Key, + }, + { + labelKey: "ui.admin.nav.audit_logs", + labelFallback: "Audit Logs", + to: "/audit-logs", + icon: NotebookTabs, + }, +]; + +type SessionStatusProps = { + expiresAtSec?: number | null; + t: ShellTranslator; +}; + +function useSessionStatus({ expiresAtSec, t }: SessionStatusProps) { + const [nowMs, setNowMs] = useState(() => Date.now()); + + useEffect(() => { + const timer = window.setInterval(() => { + setNowMs(Date.now()); + }, 1000); + + return () => { + window.clearInterval(timer); + }; + }, []); + + return buildShellSessionStatus({ expiresAtSec, nowMs, t }); +} + +function SessionStatusBadge(props: SessionStatusProps) { + const sessionStatus = useSessionStatus(props); + + return ( + + {sessionStatus.text} + + ); +} + +function SessionStatusText(props: SessionStatusProps) { + const sessionStatus = useSessionStatus(props); + + return <>{sessionStatus.text}; +} + +function LineWorksNavIcon({ size = 18 }: { size?: number | string }) { + const iconSize = typeof size === "number" ? size : Number.parseFloat(size); + return ( + + ); +} + +function AppLayout() { + const auth = useAuth(); + const location = useLocation(); + const navigate = useNavigate(); + const profileMenuRef = useRef(null); + const isRenewInFlightRef = useRef(false); + const lastRenewAttemptAtRef = useRef(0); + const lastVisitedRouteRef = useRef(null); + const isDevelopmentRuntime = import.meta.env.MODE === "development"; + const [theme, setTheme] = useState<"light" | "dark">(readShellTheme); + const [isProfileOpen, setIsProfileOpen] = useState(false); + const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(() => + readShellSidebarCollapsed(false), + ); + const [isSessionExpiryEnabled, setIsSessionExpiryEnabled] = useState(() => + readShellSessionExpiryEnabled(!isDevelopmentRuntime), + ); + const { data: profile } = useQuery({ + queryKey: ["me"], + queryFn: async () => { + debugLog("[AppLayout] Fetching profile..."); + try { + const data = await fetchMe(); + debugLog("[AppLayout] Profile fetched successfully:", data.email); + return data; + } catch (err) { + console.error("[AppLayout] Failed to fetch profile:", err); + throw err; + } + }, + enabled: + (auth.isAuthenticated && !auth.isLoading) || + import.meta.env.MODE === "development" || + (window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean }) + ._IS_TEST_MODE === true, + }); + + const navItems = React.useMemo(() => { + const items = [...staticNavItems]; + const _isTest = + (window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean }) + ._IS_TEST_MODE === true; + const effectiveRole = profile?.role; + + const isSuperAdmin = isSuperAdminRole(effectiveRole); + const _manageableCount = profile?.manageableTenants?.length ?? 0; + const showWorksmobile = canAccessWorksmobile({ + ...profile, + role: effectiveRole ?? profile?.role, + }); + const filteredItems = items.filter((item) => { + if (item.to === "/api-keys") return isSuperAdmin; + return true; + }); + + const orgfrontUrl = buildAuthenticatedOrgChartUrl( + import.meta.env.ORGFRONT_URL || "http://localhost:5175", + { includeInternal: true }, + ); + + if (isSuperAdmin) { + filteredItems.splice(1, 0, { + labelKey: "ui.admin.nav.tenants", + labelFallback: "Tenants", + to: "/tenants", + icon: Building2, + }); + filteredItems.splice(2, 0, { + labelKey: "ui.admin.nav.org_chart", + labelFallback: "Org Chart", + to: orgfrontUrl, + icon: Network, + isExternal: true, + }); + if (showWorksmobile) { + filteredItems.splice(3, 0, { + labelKey: "ui.admin.nav.worksmobile", + labelFallback: "Worksmobile", + to: "/worksmobile", + icon: LineWorksNavIcon, + }); + } + filteredItems.splice(4, 0, { + labelKey: "ui.admin.nav.ory_ssot", + labelFallback: "Ory SSOT System", + to: "/system/ory-ssot", + icon: Database, + }); + filteredItems.splice(5, 0, { + labelKey: "ui.admin.nav.data_integrity", + labelFallback: "Data Integrity", + to: "/system/data-integrity", + icon: ShieldCheck, + }); + } else { + // Non-superadmins + filteredItems.splice(1, 0, { + labelKey: "ui.admin.nav.org_chart", + labelFallback: "Org Chart", + to: orgfrontUrl, + icon: Network, + isExternal: true, + }); + if (showWorksmobile) { + filteredItems.splice(2, 0, { + labelKey: "ui.admin.nav.worksmobile", + labelFallback: "Worksmobile", + to: "/worksmobile", + icon: LineWorksNavIcon, + }); + } + } + + return filteredItems; + }, [profile]); + + const handleLogout = () => { + if ( + window.confirm(t("msg.admin.logout_confirm", "로그아웃 하시겠습니까?")) + ) { + window.localStorage.removeItem("admin_session"); + auth.removeUser(); + navigate("/login"); + } + }; + + useEffect(() => { + const isTest = + (window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean }) + ._IS_TEST_MODE === true; + + debugLog("[AppLayout] Auth state check:", { + isLoading: auth.isLoading, + isAuthenticated: auth.isAuthenticated, + isTest, + }); + + if (!auth.isLoading && !auth.isAuthenticated && !isTest) { + console.warn("[AppLayout] Not authenticated, redirecting to /login"); + navigate("/login"); + } + }, [auth.isLoading, auth.isAuthenticated, navigate]); + + useEffect(() => { + if (auth.user?.access_token) { + window.localStorage.setItem("admin_session", auth.user.access_token); + } + }, [auth.user]); + + useEffect(() => { + applyShellTheme(theme); + }, [theme]); + + useEffect(() => { + if (!isDevelopmentRuntime) { + return; + } + + const rerenderDevelopmentShell = () => { + // Re-render when locale changes + }; + + window.addEventListener(LOCALE_CHANGED_EVENT, rerenderDevelopmentShell); + + return () => { + window.removeEventListener( + LOCALE_CHANGED_EVENT, + rerenderDevelopmentShell, + ); + }; + }, []); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + profileMenuRef.current && + !profileMenuRef.current.contains(event.target as Node) + ) { + setIsProfileOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + + useEffect(() => { + const maybeRenewSession = async () => { + const now = Date.now(); + if ( + !shouldAttemptSlidingSessionRenew({ + expiresAtSec: auth.user?.expires_at, + nowMs: now, + isEnabled: isSessionExpiryEnabled, + isAuthenticated: auth.isAuthenticated, + isLoading: auth.isLoading, + isRenewInFlight: isRenewInFlightRef.current, + lastAttemptAtMs: lastRenewAttemptAtRef.current, + }) + ) { + return; + } + + isRenewInFlightRef.current = true; + lastRenewAttemptAtRef.current = now; + + try { + await auth.signinSilent(); + } catch (error) { + console.error("세션 자동 연장에 실패했습니다.", error); + } finally { + isRenewInFlightRef.current = false; + } + }; + + const handleUserAction = () => { + void maybeRenewSession(); + }; + + window.addEventListener("pointerdown", handleUserAction); + window.addEventListener("keydown", handleUserAction); + + return () => { + window.removeEventListener("pointerdown", handleUserAction); + window.removeEventListener("keydown", handleUserAction); + }; + }, [ + auth, + auth.isAuthenticated, + auth.isLoading, + auth.user?.expires_at, + isSessionExpiryEnabled, + ]); + + useEffect(() => { + if (isDevelopmentRuntime) { + return; + } + + const maybeKeepSessionAlive = async () => { + const now = Date.now(); + if ( + !shouldAttemptUnlimitedSessionRenew({ + expiresAtSec: auth.user?.expires_at, + nowMs: now, + isEnabled: isSessionExpiryEnabled, + isAuthenticated: auth.isAuthenticated, + isLoading: auth.isLoading, + isRenewInFlight: isRenewInFlightRef.current, + lastAttemptAtMs: lastRenewAttemptAtRef.current, + }) + ) { + return; + } + + isRenewInFlightRef.current = true; + lastRenewAttemptAtRef.current = now; + + try { + await auth.signinSilent(); + } catch (error) { + console.error("세션 무제한 유지 갱신에 실패했습니다.", error); + } finally { + isRenewInFlightRef.current = false; + } + }; + + const timer = window.setInterval(() => { + void maybeKeepSessionAlive(); + }, 30_000); + + void maybeKeepSessionAlive(); + + return () => { + window.clearInterval(timer); + }; + }, [ + auth, + auth.isAuthenticated, + auth.isLoading, + auth.user?.expires_at, + isSessionExpiryEnabled, + ]); + + useEffect(() => { + const routeKey = `${location.pathname}${location.search}${location.hash}`; + if (lastVisitedRouteRef.current === null) { + lastVisitedRouteRef.current = routeKey; + return; + } + + if (lastVisitedRouteRef.current === routeKey) { + return; + } + + lastVisitedRouteRef.current = routeKey; + + const now = Date.now(); + if ( + !shouldAttemptSlidingSessionRenew({ + expiresAtSec: auth.user?.expires_at, + nowMs: now, + isEnabled: isSessionExpiryEnabled, + isAuthenticated: auth.isAuthenticated, + isLoading: auth.isLoading, + isRenewInFlight: isRenewInFlightRef.current, + lastAttemptAtMs: lastRenewAttemptAtRef.current, + }) + ) { + return; + } + + isRenewInFlightRef.current = true; + lastRenewAttemptAtRef.current = now; + + void auth + .signinSilent() + .catch((error) => { + console.error("세션 자동 연장에 실패했습니다.", error); + }) + .finally(() => { + isRenewInFlightRef.current = false; + }); + }, [ + auth, + auth.isAuthenticated, + auth.isLoading, + auth.user?.expires_at, + isSessionExpiryEnabled, + location.hash, + location.pathname, + location.search, + ]); + + const toggleTheme = () => { + setTheme((prev) => (prev === "light" ? "dark" : "light")); + }; + + const profileSummary = buildShellProfileSummary({ + profileName: + profile?.name || + auth.user?.profile.name?.toString() || + auth.user?.profile.preferred_username?.toString(), + profileEmail: profile?.email || auth.user?.profile.email?.toString(), + fallbackName: t("ui.shell.profile.unknown_name", "Unknown User"), + fallbackEmail: t("ui.shell.profile.unknown_email", "unknown@example.com"), + }); + const profileRoleKey = profile?.role || "user"; + const handleSessionExpiryToggle = () => { + setIsSessionExpiryEnabled((prev) => { + const next = !prev; + writeShellSessionExpiryEnabled(next); + return next; + }); + }; + const handleSidebarToggle = () => { + setIsSidebarCollapsed((prev) => { + const next = !prev; + writeShellSidebarCollapsed(next); + return next; + }); + }; + const sidebarNavContent = ( +
+ {navItems.map((item) => { + const { labelKey, labelFallback, to, icon: Icon, isExternal } = item; + const label = t(labelKey, labelFallback); + + if (isExternal) { + return ( + + + + {label} + + + ); + } + + return ( + + [ + shellLayoutClasses.navItemBase, + isSidebarCollapsed + ? shellLayoutClasses.navItemBaseCollapsed + : "", + item.isActive !== undefined + ? item.isActive + ? shellLayoutClasses.navItemActive + : shellLayoutClasses.navItemIdle + : isActive + ? shellLayoutClasses.navItemActive + : shellLayoutClasses.navItemIdle, + ].join(" ") + } + title={label} + aria-label={label} + > + + {label} + + ); + })} +
+ ); + const sidebarFooterContent = ( +
+ +
+ ); + + if (auth.isLoading) { + return ( +
+
+
+ ); + } + + return ( +
+ } + navContent={sidebarNavContent} + footerContent={sidebarFooterContent} + collapsed={isSidebarCollapsed} + onToggleCollapsed={handleSidebarToggle} + collapseLabel={t("ui.shell.sidebar.collapse", "사이드바 접기")} + expandLabel={t("ui.shell.sidebar.expand", "사이드바 펼치기")} + /> + +
+
+
+
+

+ {t("ui.admin.header.plane", "ADMIN PLANE")} +

+ + {t("ui.admin.header.subtitle", "Manage your organization")} + +
+ +
+ + + {isSessionExpiryEnabled ? ( + + ) : null} +
+ + + {isProfileOpen ? ( +
+

+ {t("ui.shell.profile.menu_title", "Account")} +

+
+
+

+ {profileSummary.name} +

+

+ {profileSummary.email} +

+
+
+ + {t( + `ui.shell.role.${profileRoleKey}`, + profileRoleKey.toUpperCase(), + )} + +
+
+ +
+
+
+

+ {t( + "ui.shell.session.auto_extend", + "세션 만료 관리", + )} +

+

+ {isSessionExpiryEnabled ? ( + + ) : ( + t( + "ui.shell.session.disabled", + "세션 만료 비활성화", + ) + )} +

+
+ +
+
+ + {profile?.manageableTenants && + profile.manageableTenants.length > 0 ? ( +
+

+ {t( + "ui.admin.profile.manageable_tenants", + "Manageable Tenants", + )} +

+
+ {profile.manageableTenants.map((tenant) => ( + + ))} +
+
+ ) : null} + + + +
+ ) : null} +
+
+
+
+
+ +
+
+
+ ); +} + +export default AppLayout; diff --git a/baron-sso/adminfront/src/components/ui/avatar.test.tsx b/baron-sso/adminfront/src/components/ui/avatar.test.tsx new file mode 100644 index 0000000..42175b6 --- /dev/null +++ b/baron-sso/adminfront/src/components/ui/avatar.test.tsx @@ -0,0 +1,49 @@ +import type React from "react"; +import { act } from "react"; +import { createRoot } from "react-dom/client"; +import { afterEach, describe, expect, it } from "vitest"; +import { Avatar, AvatarFallback, AvatarImage } from "./avatar"; + +let container: HTMLDivElement | null = null; + +const render = async (element: React.ReactElement) => { + container = document.createElement("div"); + document.body.appendChild(container); + const root = createRoot(container); + await act(async () => { + root.render(element); + }); + return root; +}; + +afterEach(() => { + if (container) { + container.remove(); + container = null; + } +}); + +describe("Avatar", () => { + it("renders image and fallback with merged classes", async () => { + const root = await render( + + + AU + , + ); + + const avatar = container?.querySelector("[data-testid='avatar']"); + const fallback = container?.textContent; + + expect(avatar?.className).toContain("custom-root"); + expect(fallback).toContain("AU"); + + await act(async () => { + root.unmount(); + }); + }); +}); diff --git a/baron-sso/adminfront/src/components/ui/avatar.tsx b/baron-sso/adminfront/src/components/ui/avatar.tsx new file mode 100644 index 0000000..ab41556 --- /dev/null +++ b/baron-sso/adminfront/src/components/ui/avatar.tsx @@ -0,0 +1,47 @@ +import * as AvatarPrimitive from "@radix-ui/react-avatar"; +import * as React from "react"; +import { cn } from "../../lib/utils"; + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Avatar.displayName = AvatarPrimitive.Root.displayName; + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarImage.displayName = AvatarPrimitive.Image.displayName; + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; + +export { Avatar, AvatarFallback, AvatarImage }; diff --git a/baron-sso/adminfront/src/components/ui/badge.test.tsx b/baron-sso/adminfront/src/components/ui/badge.test.tsx new file mode 100644 index 0000000..bf08bf8 --- /dev/null +++ b/baron-sso/adminfront/src/components/ui/badge.test.tsx @@ -0,0 +1,26 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { Badge } from "./badge"; + +describe("Badge Component", () => { + it("renders correctly with children", () => { + render(Active); + expect(screen.getByText("Active")).toBeInTheDocument(); + }); + + it("applies variant classes correctly", () => { + const { rerender } = render(Secondary); + let badge = screen.getByText("Secondary"); + expect(badge).toHaveClass("bg-secondary"); + + rerender(Default); + badge = screen.getByText("Default"); + expect(badge).toHaveClass("text-foreground"); + }); + + it("applies custom className", () => { + render(Custom); + const badge = screen.getByText("Custom"); + expect(badge).toHaveClass("custom-class"); + }); +}); diff --git a/baron-sso/adminfront/src/components/ui/badge.tsx b/baron-sso/adminfront/src/components/ui/badge.tsx new file mode 100644 index 0000000..d30c5d1 --- /dev/null +++ b/baron-sso/adminfront/src/components/ui/badge.tsx @@ -0,0 +1,21 @@ +import type * as React from "react"; +import { + type CommonBadgeVariant, + getCommonBadgeClasses, +} from "../../../../common/ui/badge"; +import { cn } from "../../lib/utils"; + +export interface BadgeProps extends React.HTMLAttributes { + variant?: CommonBadgeVariant; +} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ); +} + +export { Badge }; diff --git a/baron-sso/adminfront/src/components/ui/button.test.tsx b/baron-sso/adminfront/src/components/ui/button.test.tsx new file mode 100644 index 0000000..5925035 --- /dev/null +++ b/baron-sso/adminfront/src/components/ui/button.test.tsx @@ -0,0 +1,38 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi } from "vitest"; +import { Button } from "./button"; + +describe("Button Component", () => { + it("renders correctly with children", () => { + render(); + expect( + screen.getByRole("button", { name: /click me/i }), + ).toBeInTheDocument(); + }); + + it("applies variant classes correctly", () => { + const { rerender } = render(); + const button = screen.getByRole("button", { name: /delete/i }); + expect(button).toHaveClass("bg-destructive"); + + rerender(); + const outlineButton = screen.getByRole("button", { name: /cancel/i }); + expect(outlineButton).toHaveClass("border-input"); + }); + + it("calls onClick when clicked", async () => { + const onClick = vi.fn(); + const user = userEvent.setup(); + render(); + + await user.click(screen.getByRole("button", { name: /click me/i })); + expect(onClick).toHaveBeenCalledTimes(1); + }); + + it("is disabled when the disabled prop is passed", () => { + render(); + const button = screen.getByRole("button", { name: /disabled button/i }); + expect(button).toBeDisabled(); + }); +}); diff --git a/baron-sso/adminfront/src/components/ui/button.tsx b/baron-sso/adminfront/src/components/ui/button.tsx new file mode 100644 index 0000000..1066cf1 --- /dev/null +++ b/baron-sso/adminfront/src/components/ui/button.tsx @@ -0,0 +1,31 @@ +import { Slot } from "@radix-ui/react-slot"; +import * as React from "react"; +import { + type CommonButtonSize, + type CommonButtonVariant, + getCommonButtonClasses, +} from "../../../../common/ui/button"; +import { cn } from "../../lib/utils"; + +export interface ButtonProps + extends React.ButtonHTMLAttributes { + variant?: CommonButtonVariant; + size?: CommonButtonSize; + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, +); +Button.displayName = "Button"; + +export { Button }; diff --git a/baron-sso/adminfront/src/components/ui/card.test.tsx b/baron-sso/adminfront/src/components/ui/card.test.tsx new file mode 100644 index 0000000..4bde79e --- /dev/null +++ b/baron-sso/adminfront/src/components/ui/card.test.tsx @@ -0,0 +1,35 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "./card"; + +describe("Card Component", () => { + it("renders card structure correctly", () => { + render( + + + Card Title + Card Description + + Card Content + Card Footer + , + ); + + expect(screen.getByText("Card Title")).toBeInTheDocument(); + expect(screen.getByText("Card Description")).toBeInTheDocument(); + expect(screen.getByText("Card Content")).toBeInTheDocument(); + expect(screen.getByText("Card Footer")).toBeInTheDocument(); + }); + + it("applies custom className to Card", () => { + const { container } = render(); + expect(container.firstChild).toHaveClass("custom-card"); + }); +}); diff --git a/baron-sso/adminfront/src/components/ui/card.tsx b/baron-sso/adminfront/src/components/ui/card.tsx new file mode 100644 index 0000000..246b528 --- /dev/null +++ b/baron-sso/adminfront/src/components/ui/card.tsx @@ -0,0 +1,58 @@ +import type * as React from "react"; +import { + commonCardClass, + commonCardContentClass, + commonCardDescriptionClass, + commonCardFooterClass, + commonCardHeaderClass, + commonCardTitleClass, +} from "../../../../common/ui/card"; +import { cn } from "../../lib/utils"; + +function Card({ className, ...props }: React.HTMLAttributes) { + return
; +} + +function CardHeader({ + className, + ...props +}: React.HTMLAttributes) { + return
; +} + +function CardTitle({ + className, + ...props +}: React.HTMLAttributes) { + return

; +} + +function CardDescription({ + className, + ...props +}: React.HTMLAttributes) { + return

; +} + +function CardContent({ + className, + ...props +}: React.HTMLAttributes) { + return

; +} + +function CardFooter({ + className, + ...props +}: React.HTMLAttributes) { + return
; +} + +export { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +}; diff --git a/baron-sso/adminfront/src/components/ui/checkbox.test.tsx b/baron-sso/adminfront/src/components/ui/checkbox.test.tsx new file mode 100644 index 0000000..89c2979 --- /dev/null +++ b/baron-sso/adminfront/src/components/ui/checkbox.test.tsx @@ -0,0 +1,19 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { Checkbox } from "./checkbox"; + +describe("Checkbox Component", () => { + it("adds a fallback id for browser autofill diagnostics", () => { + render(); + + expect(screen.getByRole("checkbox")).toHaveAttribute("id"); + }); + + it("keeps explicit id and name values", () => { + render(); + const checkbox = screen.getByRole("checkbox"); + + expect(checkbox).toHaveAttribute("id", "explicit-checkbox"); + expect(checkbox).toHaveAttribute("name", "explicit-name"); + }); +}); diff --git a/baron-sso/adminfront/src/components/ui/checkbox.tsx b/baron-sso/adminfront/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..9ad1252 --- /dev/null +++ b/baron-sso/adminfront/src/components/ui/checkbox.tsx @@ -0,0 +1,36 @@ +import * as React from "react"; +import { cn } from "../../lib/utils"; + +export interface CheckboxProps + extends Omit, "onChange"> { + onCheckedChange?: (checked: boolean | "indeterminate") => void; +} + +const Checkbox = React.forwardRef( + ({ className, onCheckedChange, id, name, ...props }, ref) => { + const fallbackId = React.useId(); + const fieldId = id ?? (name ? undefined : fallbackId); + + const handleChange = (e: React.ChangeEvent) => { + onCheckedChange?.(e.target.checked); + }; + + return ( + + ); + }, +); +Checkbox.displayName = "Checkbox"; + +export { Checkbox }; diff --git a/baron-sso/adminfront/src/components/ui/dialog.focus-scope.test.tsx b/baron-sso/adminfront/src/components/ui/dialog.focus-scope.test.tsx new file mode 100644 index 0000000..95a9741 --- /dev/null +++ b/baron-sso/adminfront/src/components/ui/dialog.focus-scope.test.tsx @@ -0,0 +1,23 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogTitle, +} from "./dialog"; + +describe("Dialog FocusScope integration", () => { + it("mounts an open dialog without a ref update loop", () => { + render( + + + Focus scope check + Dialog content is mounted. + + , + ); + + expect(screen.getByText("Focus scope check")).toBeInTheDocument(); + }); +}); diff --git a/baron-sso/adminfront/src/components/ui/dialog.tsx b/baron-sso/adminfront/src/components/ui/dialog.tsx new file mode 100644 index 0000000..b1a70ec --- /dev/null +++ b/baron-sso/adminfront/src/components/ui/dialog.tsx @@ -0,0 +1,287 @@ +import { X } from "lucide-react"; +import * as React from "react"; +import { createPortal } from "react-dom"; + +import { cn } from "../../lib/utils"; + +type DialogContextValue = { + open: boolean; + setOpen: (open: boolean) => void; +}; + +const DialogContext = React.createContext(null); + +function useDialogContext(componentName: string) { + const context = React.useContext(DialogContext); + if (!context) { + throw new Error(`${componentName} must be used within Dialog`); + } + return context; +} + +function composeEventHandlers( + theirs: ((event: E) => void) | undefined, + ours: (event: E) => void, +) { + return (event: E) => { + theirs?.(event); + if (!event.defaultPrevented) { + ours(event); + } + }; +} + +type DialogProps = { + open?: boolean; + defaultOpen?: boolean; + onOpenChange?: (open: boolean) => void; + children?: React.ReactNode; +}; + +function Dialog({ + open, + defaultOpen = false, + onOpenChange, + children, +}: DialogProps) { + const [internalOpen, setInternalOpen] = React.useState(defaultOpen); + const isControlled = open !== undefined; + const currentOpen = isControlled ? open : internalOpen; + const setOpen = React.useCallback( + (nextOpen: boolean) => { + if (!isControlled) { + setInternalOpen(nextOpen); + } + onOpenChange?.(nextOpen); + }, + [isControlled, onOpenChange], + ); + + const value = React.useMemo( + () => ({ open: currentOpen, setOpen }), + [currentOpen, setOpen], + ); + + return ( + {children} + ); +} + +type DialogTriggerProps = React.ButtonHTMLAttributes & { + asChild?: boolean; +}; + +const DialogTrigger = React.forwardRef( + ({ asChild = false, children, onClick, ...props }, ref) => { + const { setOpen } = useDialogContext("DialogTrigger"); + const handleOpen = (event: React.MouseEvent) => { + onClick?.(event); + if (!event.defaultPrevented) { + setOpen(true); + } + }; + + if (asChild && React.isValidElement(children)) { + const child = children as React.ReactElement<{ + onClick?: React.MouseEventHandler; + }>; + return React.cloneElement(child, { + ...props, + onClick: composeEventHandlers( + child.props.onClick as React.MouseEventHandler, + () => setOpen(true), + ), + }); + } + + return ( + + ); + }, +); +DialogTrigger.displayName = "DialogTrigger"; + +const DialogPortal = ({ children }: { children?: React.ReactNode }) => { + if (typeof document === "undefined") { + return null; + } + return createPortal(children, document.body); +}; +DialogPortal.displayName = "DialogPortal"; + +const DialogClose = React.forwardRef( + ({ asChild = false, children, onClick, ...props }, ref) => { + const { setOpen } = useDialogContext("DialogClose"); + const handleClose = (event: React.MouseEvent) => { + onClick?.(event); + if (!event.defaultPrevented) { + setOpen(false); + } + }; + + if (asChild && React.isValidElement(children)) { + const child = children as React.ReactElement<{ + onClick?: React.MouseEventHandler; + }>; + return React.cloneElement(child, { + ...props, + onClick: composeEventHandlers( + child.props.onClick as React.MouseEventHandler, + () => setOpen(false), + ), + }); + } + + return ( + + ); + }, +); +DialogClose.displayName = "DialogClose"; + +const DialogOverlay = React.forwardRef< + HTMLButtonElement, + React.ButtonHTMLAttributes +>(({ className, onMouseDown, ...props }, ref) => { + const { setOpen } = useDialogContext("DialogOverlay"); + return ( + + ); + }, +); +Switch.displayName = "Switch"; + +export { Switch }; diff --git a/baron-sso/adminfront/src/components/ui/table.tsx b/baron-sso/adminfront/src/components/ui/table.tsx new file mode 100644 index 0000000..33663b2 --- /dev/null +++ b/baron-sso/adminfront/src/components/ui/table.tsx @@ -0,0 +1,102 @@ +import * as React from "react"; +import { + commonTableBodyClass, + commonTableCaptionClass, + commonTableCellClass, + commonTableClass, + commonTableFooterClass, + commonTableHeadClass, + commonTableHeaderClass, + commonTableRowClass, + commonTableWrapperClass, +} from "../../../../common/ui/table"; +import { cn } from "../../lib/utils"; + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)); +Table.displayName = "Table"; + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableHeader.displayName = "TableHeader"; + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableBody.displayName = "TableBody"; + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableFooter.displayName = "TableFooter"; + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableRow.displayName = "TableRow"; + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableHead.displayName = "TableHead"; + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableCell.displayName = "TableCell"; + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableCaption.displayName = "TableCaption"; + +export { + Table, + TableBody, + TableCaption, + TableCell, + TableFooter, + TableHead, + TableHeader, + TableRow, +}; diff --git a/baron-sso/adminfront/src/components/ui/tabs.tsx b/baron-sso/adminfront/src/components/ui/tabs.tsx new file mode 100644 index 0000000..1564658 --- /dev/null +++ b/baron-sso/adminfront/src/components/ui/tabs.tsx @@ -0,0 +1,87 @@ +import * as React from "react"; +import { cn } from "../../lib/utils"; + +const TabsContext = React.createContext<{ + value?: string; + onValueChange?: (value: string) => void; +}>({}); + +const Tabs = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & { + value?: string; + onValueChange?: (value: string) => void; + } +>(({ className, value, onValueChange, ...props }, ref) => { + return ( + +
+ + ); +}); +Tabs.displayName = "Tabs"; + +const TabsList = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TabsList.displayName = "TabsList"; + +const TabsTrigger = React.forwardRef< + HTMLButtonElement, + React.ButtonHTMLAttributes & { value: string } +>(({ className, value, ...props }, ref) => { + const { value: activeValue, onValueChange } = React.useContext(TabsContext); + const isSelected = activeValue === value; + + return ( +