From cbb804e67173df93f3604db76547503c0832ba44 Mon Sep 17 00:00:00 2001 From: Nityanand Rai <107465508+Nityanand13@users.noreply.github.com> Date: Tue, 31 Jan 2023 15:02:00 +0530 Subject: [PATCH 01/18] [MI-2502]:Added custom type for webhook post in order to comment, edit and close issues (#13) * [MI-2502]:Added custom type for webhook post in order to comment, edit and close issues * [MI-2502]: Added EOF * [MI-2502]: Fixed lint errors * [MI-2502]: Review fixes done 1. Improved code quality 2. Made few contants * [MI-2502]: Review fixes done 1. Changed few files to .tsx 2. Improved code quality * [MI-2502]: Review fixes done 1. Improved code quality 2. Made a constant * [MI-2502]: Review fixes done 1. Improved code readability * [MI-2502]: Review fixes done 1. Made a package dor constants 2. Made a package for struct 3. Improved code redability * [MI-2502]: Review fixes done 1. Improved code quality 2. Fixed the issue that I was facing while creating issue or attaching a comment to the issue. 3. Changed few file names * [MI-2502]: Review fixes done 1. Changed the names of few functions 2. Improved code quality * [MI-2502]: Review fixes done 1.Improved code readability * [MI-2502]: Review fixes done 1. Improved code readability * [MI-2502]: Review done 1. Improved code quality * [MI-2502]: Review fixes done 1. Improved code quality --- go.mod | 2 +- go.sum | 6 +- server/client/client.go | 2 +- server/constants/constants.go | 46 ++ server/plugin/api.go | 689 +++++++++++------- server/plugin/api_test.go | 6 +- server/plugin/cluster.go | 2 +- server/plugin/command.go | 40 +- server/plugin/flows.go | 2 +- server/plugin/mm_34646_token_refresh.go | 11 +- server/plugin/permalinks.go | 2 +- server/plugin/permalinks_test.go | 2 +- server/plugin/plugin.go | 55 +- server/plugin/subscriptions.go | 2 +- server/plugin/template_test.go | 2 +- server/plugin/utils.go | 141 ++++ server/plugin/webhook.go | 114 +-- server/serializer/error.go | 11 + server/serializer/issue.go | 52 ++ server/serializer/notification.go | 8 + server/serializer/pr.go | 12 + server/serializer/repo.go | 8 + server/serializer/user.go | 62 ++ webapp/src/action_types/index.js | 8 +- webapp/src/actions/index.js | 129 +++- webapp/src/client/client.js | 20 + webapp/src/components/github_issue/index.tsx | 118 +++ .../attach_comment_to_issue.jsx | 48 +- .../modals/attach_comment_to_issue/index.js | 6 +- .../modals/close_reopen_issue/index.tsx | 181 +++++ .../components/modals/create_issue/index.js | 30 - .../create_update_issue.jsx} | 131 +++- .../modals/create_update_issue/index.js | 31 + .../post_menu_action/create_issue/index.js | 4 +- .../sidebar_buttons/sidebar_buttons.jsx | 48 +- .../components/sidebar_right/github_items.tsx | 102 ++- webapp/src/index.js | 15 +- webapp/src/reducers/index.js | 56 +- webapp/src/selectors.js | 13 + webapp/src/websocket/index.js | 26 +- 40 files changed, 1691 insertions(+), 552 deletions(-) create mode 100644 server/constants/constants.go create mode 100644 server/serializer/error.go create mode 100644 server/serializer/issue.go create mode 100644 server/serializer/notification.go create mode 100644 server/serializer/pr.go create mode 100644 server/serializer/repo.go create mode 100644 server/serializer/user.go create mode 100644 webapp/src/components/github_issue/index.tsx create mode 100644 webapp/src/components/modals/close_reopen_issue/index.tsx delete mode 100644 webapp/src/components/modals/create_issue/index.js rename webapp/src/components/modals/{create_issue/create_issue.jsx => create_update_issue/create_update_issue.jsx} (63%) create mode 100644 webapp/src/components/modals/create_update_issue/index.js diff --git a/go.mod b/go.mod index 148a14880..156a66cac 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.16 require ( github.com/Masterminds/sprig/v3 v3.2.2 - github.com/google/go-github/v41 v41.0.0 + github.com/google/go-github/v48 v48.2.0 github.com/gorilla/mux v1.8.0 github.com/mattermost/mattermost-plugin-api v0.0.27 github.com/mattermost/mattermost-server/v6 v6.5.2 diff --git a/go.sum b/go.sum index b69878ae2..76bea24aa 100644 --- a/go.sum +++ b/go.sum @@ -635,13 +635,16 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github/v35 v35.2.0/go.mod h1:s0515YVTI+IMrDoy9Y4pHt9ShGpzHvHO8rZ7L7acgvs= github.com/google/go-github/v41 v41.0.0 h1:HseJrM2JFf2vfiZJ8anY2hqBjdfY1Vlj/K27ueww4gg= github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg= +github.com/google/go-github/v48 v48.2.0 h1:68puzySE6WqUY9KWmpOsDEQfDZsso98rT6pZcz9HqcE= +github.com/google/go-github/v48 v48.2.0/go.mod h1:dDlehKBDo850ZPvCTK0sEqTCVWcrGl2LcDiajkYi89Y= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= @@ -1941,7 +1944,6 @@ golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= diff --git a/server/client/client.go b/server/client/client.go index 4320c6542..e79e046fa 100644 --- a/server/client/client.go +++ b/server/client/client.go @@ -7,7 +7,7 @@ import ( "net/url" "strings" - "github.com/google/go-github/v41/github" + "github.com/google/go-github/v48/github" "github.com/pkg/errors" "golang.org/x/oauth2" diff --git a/server/constants/constants.go b/server/constants/constants.go new file mode 100644 index 000000000..6e832b9be --- /dev/null +++ b/server/constants/constants.go @@ -0,0 +1,46 @@ +package constants + +import "time" + +const ( + APIErrorIDNotConnected = "not_connected" + // TokenTTL is the OAuth token expiry duration in seconds + TokenTTL = 600 + + RequestTimeout = 30 * time.Second + OauthCompleteTimeout = 2 * time.Minute + HeaderMattermostUserID = "Mattermost-User-ID" + OwnerQueryParam = "owner" + RepoQueryParam = "repo" + NumberQueryParam = "number" + PostIdQueryParam = "postId" + + IssueStatus = "status" + AssigneesForProps = "assignees" + LabelsForProps = "labels" + DescriptionForProps = "description" + TitleForProps = "title" + IssueNumberForProps = "issue_number" + IssueUrlForProps = "issue_url" + RepoOwnerForProps = "repo_owner" + RepoNameForProps = "repo_name" + + Close = "Close" + Reopen = "Reopen" + + IssueCompleted = "completed" + IssueNotPlanned = "not_planned" + IssueClose = "closed" + IssueOpen = "open" + + //Actions of webhook events + ActionOpened = "opened" + ActionClosed = "closed" + ActionReopened = "reopened" + ActionSubmitted = "submitted" + ActionLabeled = "labeled" + ActionAssigned = "assigned" + ActionCreated = "created" + ActionDeleted = "deleted" + ActionEdited = "edited" +) diff --git a/server/plugin/api.go b/server/plugin/api.go index 3ef3eda4f..4a73520c7 100644 --- a/server/plugin/api.go +++ b/server/plugin/api.go @@ -11,67 +11,24 @@ import ( "sync" "time" - "github.com/google/go-github/v41/github" + "github.com/google/go-github/v48/github" "github.com/gorilla/mux" "github.com/pkg/errors" "golang.org/x/oauth2" "github.com/mattermost/mattermost-plugin-api/experimental/bot/logger" "github.com/mattermost/mattermost-plugin-api/experimental/flow" + "github.com/mattermost/mattermost-plugin-github/server/constants" + "github.com/mattermost/mattermost-plugin-github/server/serializer" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/plugin" ) -const ( - apiErrorIDNotConnected = "not_connected" - // TokenTTL is the OAuth token expiry duration in seconds - TokenTTL = 10 * 60 - - requestTimeout = 30 * time.Second - oauthCompleteTimeout = 2 * time.Minute -) - -type OAuthState struct { - UserID string `json:"user_id"` - Token string `json:"token"` - PrivateAllowed bool `json:"private_allowed"` -} - -type APIErrorResponse struct { - ID string `json:"id"` - Message string `json:"message"` - StatusCode int `json:"status_code"` -} - -func (e *APIErrorResponse) Error() string { - return e.Message -} - -type PRDetails struct { - URL string `json:"url"` - Number int `json:"number"` - Status string `json:"status"` - Mergeable bool `json:"mergeable"` - RequestedReviewers []*string `json:"requestedReviewers"` - Reviews []*github.PullRequestReview `json:"reviews"` -} - -type Context struct { - Ctx context.Context - UserID string - Log logger.Logger -} - -// HTTPHandlerFuncWithContext is http.HandleFunc but with a Context attached -type HTTPHandlerFuncWithContext func(c *Context, w http.ResponseWriter, r *http.Request) - -type UserContext struct { - Context - GHInfo *GitHubUserInfo -} - // HTTPHandlerFuncWithUserContext is http.HandleFunc but with a UserContext attached -type HTTPHandlerFuncWithUserContext func(c *UserContext, w http.ResponseWriter, r *http.Request) +type HTTPHandlerFuncWithUserContext func(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) + +// HTTPHandlerFuncWithContext is http.HandleFunc but with a .ontext attached +type HTTPHandlerFuncWithContext func(c *serializer.Context, w http.ResponseWriter, r *http.Request) // ResponseType indicates type of response returned by api type ResponseType string @@ -90,15 +47,14 @@ func (p *Plugin) writeJSON(w http.ResponseWriter, v interface{}) { w.WriteHeader(http.StatusInternalServerError) return } - _, err = w.Write(b) - if err != nil { + if _, err = w.Write(b); err != nil { p.API.LogWarn("Failed to write JSON response", "error", err.Error()) w.WriteHeader(http.StatusInternalServerError) return } } -func (p *Plugin) writeAPIError(w http.ResponseWriter, apiErr *APIErrorResponse) { +func (p *Plugin) writeAPIError(w http.ResponseWriter, apiErr *serializer.APIErrorResponse) { b, err := json.Marshal(apiErr) if err != nil { p.API.LogWarn("Failed to marshal API error", "error", err.Error()) @@ -108,8 +64,7 @@ func (p *Plugin) writeAPIError(w http.ResponseWriter, apiErr *APIErrorResponse) w.WriteHeader(apiErr.StatusCode) - _, err = w.Write(b) - if err != nil { + if _, err = w.Write(b); err != nil { p.API.LogWarn("Failed to write JSON response", "error", err.Error()) w.WriteHeader(http.StatusInternalServerError) return @@ -139,6 +94,11 @@ func (p *Plugin) initializeAPI() { apiRouter.HandleFunc("/searchissues", p.checkAuth(p.attachUserContext(p.searchIssues), ResponseTypePlain)).Methods(http.MethodGet) apiRouter.HandleFunc("/yourassignments", p.checkAuth(p.attachUserContext(p.getYourAssignments), ResponseTypePlain)).Methods(http.MethodGet) apiRouter.HandleFunc("/createissue", p.checkAuth(p.attachUserContext(p.createIssue), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc("/closeorreopenissue", p.checkAuth(p.attachUserContext(p.closeOrReopenIssue), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc("/updateissue", p.checkAuth(p.attachUserContext(p.updateIssue), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc("/editissuemodal", p.checkAuth(p.attachUserContext(p.openIssueEditModal), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc("/closereopenissuemodal", p.checkAuth(p.attachUserContext(p.openCloseOrReopenIssueModal), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc("/attachcommentissuemodal", p.checkAuth(p.attachUserContext(p.openAttachCommentIssueModal), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc("/createissuecomment", p.checkAuth(p.attachUserContext(p.createIssueComment), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc("/mentions", p.checkAuth(p.attachUserContext(p.getMentions), ResponseTypePlain)).Methods(http.MethodGet) apiRouter.HandleFunc("/unreads", p.checkAuth(p.attachUserContext(p.getUnreads), ResponseTypePlain)).Methods(http.MethodGet) @@ -184,11 +144,11 @@ func (p *Plugin) checkConfigured(next http.Handler) http.Handler { func (p *Plugin) checkAuth(handler http.HandlerFunc, responseType ResponseType) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - userID := r.Header.Get("Mattermost-User-ID") + userID := r.Header.Get(constants.HeaderMattermostUserID) if userID == "" { switch responseType { case ResponseTypeJSON: - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Not authorized.", StatusCode: http.StatusUnauthorized}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Not authorized.", StatusCode: http.StatusUnauthorized}) case ResponseTypePlain: http.Error(w, "Not authorized", http.StatusUnauthorized) default: @@ -201,16 +161,16 @@ func (p *Plugin) checkAuth(handler http.HandlerFunc, responseType ResponseType) } } -func (p *Plugin) createContext(_ http.ResponseWriter, r *http.Request) (*Context, context.CancelFunc) { - userID := r.Header.Get("Mattermost-User-ID") +func (p *Plugin) createContext(_ http.ResponseWriter, r *http.Request) (*serializer.Context, context.CancelFunc) { + userID := r.Header.Get(constants.HeaderMattermostUserID) logger := logger.New(p.API).With(logger.LogContext{ "userid": userID, }) - ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + ctx, cancel := context.WithTimeout(context.Background(), constants.RequestTimeout) - context := &Context{ + context := &serializer.Context{ Ctx: ctx, UserID: userID, Log: logger, @@ -243,7 +203,7 @@ func (p *Plugin) attachUserContext(handler HTTPHandlerFuncWithUserContext) http. "github username": info.GitHubUsername, }) - userContext := &UserContext{ + userContext := &serializer.UserContext{ Context: *context, GHInfo: info, } @@ -271,7 +231,7 @@ func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Req p.router.ServeHTTP(w, r) } -func (p *Plugin) connectUserToGitHub(c *Context, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) connectUserToGitHub(c *serializer.Context, w http.ResponseWriter, r *http.Request) { privateAllowed := false pValBool, _ := strconv.ParseBool(r.URL.Query().Get("private")) if pValBool { @@ -280,7 +240,7 @@ func (p *Plugin) connectUserToGitHub(c *Context, w http.ResponseWriter, r *http. conf := p.getOAuthConfig(privateAllowed) - state := OAuthState{ + state := serializer.OAuthState{ UserID: c.UserID, Token: model.NewId()[:15], PrivateAllowed: privateAllowed, @@ -292,7 +252,7 @@ func (p *Plugin) connectUserToGitHub(c *Context, w http.ResponseWriter, r *http. return } - appErr := p.API.KVSetWithExpiry(githubOauthKey+state.Token, stateBytes, TokenTTL) + appErr := p.API.KVSetWithExpiry(fmt.Sprintf("%s%s", githubOauthKey, state.Token), stateBytes, constants.TokenTTL) if appErr != nil { http.Error(w, "error setting stored state", http.StatusBadRequest) return @@ -332,7 +292,7 @@ func (p *Plugin) connectUserToGitHub(c *Context, w http.ResponseWriter, r *http. http.Redirect(w, r, url, http.StatusFound) } -func (p *Plugin) completeConnectUserToGitHub(c *Context, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) completeConnectUserToGitHub(c *serializer.Context, w http.ResponseWriter, r *http.Request) { var rErr error defer func() { p.oauthBroker.publishOAuthComplete(c.UserID, rErr, false) @@ -347,7 +307,7 @@ func (p *Plugin) completeConnectUserToGitHub(c *Context, w http.ResponseWriter, stateToken := r.URL.Query().Get("state") - storedState, appErr := p.API.KVGet(githubOauthKey + stateToken) + storedState, appErr := p.API.KVGet(fmt.Sprintf("%s%s", githubOauthKey, stateToken)) if appErr != nil { c.Log.Warnf("Failed to get state token", "error", appErr.Error()) @@ -355,7 +315,7 @@ func (p *Plugin) completeConnectUserToGitHub(c *Context, w http.ResponseWriter, http.Error(w, rErr.Error(), http.StatusBadRequest) return } - var state OAuthState + var state serializer.OAuthState if err := json.Unmarshal(storedState, &state); err != nil { rErr = errors.Wrap(err, "json unmarshal failed") @@ -363,10 +323,8 @@ func (p *Plugin) completeConnectUserToGitHub(c *Context, w http.ResponseWriter, return } - appErr = p.API.KVDelete(githubOauthKey + stateToken) - if appErr != nil { + if appErr = p.API.KVDelete(fmt.Sprintf("%s%s", githubOauthKey, stateToken)); appErr != nil { c.Log.WithError(appErr).Warnf("Failed to delete state token") - rErr = errors.Wrap(appErr, "error deleting stored state") http.Error(w, rErr.Error(), http.StatusBadRequest) return @@ -386,7 +344,7 @@ func (p *Plugin) completeConnectUserToGitHub(c *Context, w http.ResponseWriter, conf := p.getOAuthConfig(state.PrivateAllowed) - ctx, cancel := context.WithTimeout(context.Background(), oauthCompleteTimeout) + ctx, cancel := context.WithTimeout(context.Background(), constants.OauthCompleteTimeout) defer cancel() tok, err := conf.Exchange(ctx, code) @@ -408,12 +366,12 @@ func (p *Plugin) completeConnectUserToGitHub(c *Context, w http.ResponseWriter, return } - userInfo := &GitHubUserInfo{ + userInfo := &serializer.GitHubUserInfo{ UserID: state.UserID, Token: tok, GitHubUsername: gitUser.GetLogin(), LastToDoPostAt: model.GetMillis(), - Settings: &UserSettings{ + Settings: &serializer.UserSettings{ SidebarButtons: settingButtonsTeam, DailyReminder: true, Notifications: true, @@ -442,8 +400,7 @@ func (p *Plugin) completeConnectUserToGitHub(c *Context, w http.ResponseWriter, } if stepName == stepOAuthConnect { - err = flow.Go(stepWebhookQuestion) - if err != nil { + if err = flow.Go(stepWebhookQuestion); err != nil { c.Log.WithError(err).Warnf("Failed go to next step") } } else { @@ -508,35 +465,30 @@ func (p *Plugin) completeConnectUserToGitHub(c *Context, w http.ResponseWriter, ` w.Header().Set("Content-Type", "text/html") - _, err = w.Write([]byte(html)) - if err != nil { + if _, err = w.Write([]byte(html)); err != nil { c.Log.WithError(err).Warnf("Failed to write HTML response") w.WriteHeader(http.StatusInternalServerError) return } } -func (p *Plugin) getGitHubUser(c *Context, w http.ResponseWriter, r *http.Request) { - type GitHubUserRequest struct { - UserID string `json:"user_id"` - } - - req := &GitHubUserRequest{} +func (p *Plugin) getGitHubUser(c *serializer.Context, w http.ResponseWriter, r *http.Request) { + req := &serializer.GitHubUserRequest{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { c.Log.WithError(err).Warnf("Error decoding GitHubUserRequest from JSON body") - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a JSON object.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) return } if req.UserID == "" { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a JSON object with a non-blank user_id field.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a JSON object with a non-blank user_id field.", StatusCode: http.StatusBadRequest}) return } userInfo, apiErr := p.getGitHubUserInfo(req.UserID) if apiErr != nil { - if apiErr.ID == apiErrorIDNotConnected { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "User is not connected to a GitHub account.", StatusCode: http.StatusNotFound}) + if apiErr.ID == constants.APIErrorIDNotConnected { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "User is not connected to a GitHub account.", StatusCode: http.StatusNotFound}) } else { p.writeAPIError(w, apiErr) } @@ -544,32 +496,17 @@ func (p *Plugin) getGitHubUser(c *Context, w http.ResponseWriter, r *http.Reques } if userInfo == nil { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "User is not connected to a GitHub account.", StatusCode: http.StatusNotFound}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "User is not connected to a GitHub account.", StatusCode: http.StatusNotFound}) return } - type GitHubUserResponse struct { - Username string `json:"username"` - } - - resp := &GitHubUserResponse{Username: userInfo.GitHubUsername} + resp := &serializer.GitHubUserResponse{Username: userInfo.GitHubUsername} p.writeJSON(w, resp) } -func (p *Plugin) getConnected(c *Context, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getConnected(c *serializer.Context, w http.ResponseWriter, r *http.Request) { config := p.getConfiguration() - - type ConnectedResponse struct { - Connected bool `json:"connected"` - GitHubUsername string `json:"github_username"` - GitHubClientID string `json:"github_client_id"` - EnterpriseBaseURL string `json:"enterprise_base_url,omitempty"` - Organization string `json:"organization"` - UserSettings *UserSettings `json:"user_settings"` - ClientConfiguration map[string]interface{} `json:"configuration"` - } - - resp := &ConnectedResponse{ + resp := &serializer.ConnectedResponse{ Connected: false, EnterpriseBaseURL: config.EnterpriseBaseURL, Organization: config.GitHubOrg, @@ -616,7 +553,7 @@ func (p *Plugin) getConnected(c *Context, w http.ResponseWriter, r *http.Request } } - privateRepoStoreKey := info.UserID + githubPrivateRepoKey + privateRepoStoreKey := fmt.Sprintf("%s%s", info.UserID, githubPrivateRepoKey) if config.EnablePrivateRepo && !info.AllowedPrivateRepos { val, err := p.API.KVGet(privateRepoStoreKey) if err != nil { @@ -632,8 +569,7 @@ func (p *Plugin) getConnected(c *Context, w http.ResponseWriter, r *http.Request } else { p.CreateBotDMPost(info.UserID, fmt.Sprintf(message, "`/github connect private`."), "") } - err := p.API.KVSet(privateRepoStoreKey, []byte("1")) - if err != nil { + if err := p.API.KVSet(privateRepoStoreKey, []byte("1")); err != nil { c.Log.WithError(err).Warnf("Unable to set private repo key value") } } @@ -642,7 +578,7 @@ func (p *Plugin) getConnected(c *Context, w http.ResponseWriter, r *http.Request p.writeJSON(w, resp) } -func (p *Plugin) getMentions(c *UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getMentions(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { config := p.getConfiguration() githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) @@ -658,7 +594,7 @@ func (p *Plugin) getMentions(c *UserContext, w http.ResponseWriter, r *http.Requ p.writeJSON(w, result.Issues) } -func (p *Plugin) getUnreads(c *UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getUnreads(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) notifications, _, err := githubClient.Activity.ListNotifications(c.Ctx, &github.NotificationListOptions{}) @@ -667,13 +603,7 @@ func (p *Plugin) getUnreads(c *UserContext, w http.ResponseWriter, r *http.Reque return } - type filteredNotification struct { - github.Notification - - HTMLUrl string `json:"html_url"` - } - - filteredNotifications := []*filteredNotification{} + filteredNotifications := []*serializer.FilteredNotification{} for _, n := range notifications { if n.GetReason() == notificationReasonSubscribed { continue @@ -691,7 +621,7 @@ func (p *Plugin) getUnreads(c *UserContext, w http.ResponseWriter, r *http.Reque subjectURL = n.GetSubject().GetLatestCommentURL() } - filteredNotifications = append(filteredNotifications, &filteredNotification{ + filteredNotifications = append(filteredNotifications, &serializer.FilteredNotification{ Notification: *n, HTMLUrl: fixGithubNotificationSubjectURL(subjectURL, issueNum), }) @@ -700,7 +630,7 @@ func (p *Plugin) getUnreads(c *UserContext, w http.ResponseWriter, r *http.Reque p.writeJSON(w, filteredNotifications) } -func (p *Plugin) getReviews(c *UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getReviews(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { config := p.getConfiguration() githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) @@ -716,7 +646,7 @@ func (p *Plugin) getReviews(c *UserContext, w http.ResponseWriter, r *http.Reque p.writeJSON(w, result.Issues) } -func (p *Plugin) getYourPrs(c *UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getYourPrs(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { config := p.getConfiguration() githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) @@ -732,17 +662,17 @@ func (p *Plugin) getYourPrs(c *UserContext, w http.ResponseWriter, r *http.Reque p.writeJSON(w, result.Issues) } -func (p *Plugin) getPrsDetails(c *UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getPrsDetails(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) - var prList []*PRDetails + var prList []*serializer.PRDetails if err := json.NewDecoder(r.Body).Decode(&prList); err != nil { c.Log.WithError(err).Warnf("Error decoding PRDetails JSON body") - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a JSON object.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) return } - prDetails := make([]*PRDetails, len(prList)) + prDetails := make([]*serializer.PRDetails, len(prList)) var wg sync.WaitGroup for i, pr := range prList { i := i @@ -760,7 +690,7 @@ func (p *Plugin) getPrsDetails(c *UserContext, w http.ResponseWriter, r *http.Re p.writeJSON(w, prDetails) } -func (p *Plugin) fetchPRDetails(c *UserContext, client *github.Client, prURL string, prNumber int) *PRDetails { +func (p *Plugin) fetchPRDetails(c *serializer.UserContext, client *github.Client, prURL string, prNumber int) *serializer.PRDetails { var status string var mergeable bool // Initialize to a non-nil slice to simplify JSON handling semantics @@ -807,7 +737,7 @@ func (p *Plugin) fetchPRDetails(c *UserContext, client *github.Client, prURL str }() wg.Wait() - return &PRDetails{ + return &serializer.PRDetails{ URL: prURL, Number: prNumber, Status: status, @@ -817,7 +747,7 @@ func (p *Plugin) fetchPRDetails(c *UserContext, client *github.Client, prURL str } } -func fetchReviews(c *UserContext, client *github.Client, repoOwner string, repoName string, number int) ([]*github.PullRequestReview, error) { +func fetchReviews(c *serializer.UserContext, client *github.Client, repoOwner string, repoName string, number int) ([]*github.PullRequestReview, error) { reviewsList, _, err := client.PullRequests.ListReviews(c.Ctx, repoOwner, repoName, number, nil) if err != nil { @@ -832,7 +762,7 @@ func getRepoOwnerAndNameFromURL(url string) (string, string) { return splitted[len(splitted)-2], splitted[len(splitted)-1] } -func (p *Plugin) searchIssues(c *UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) searchIssues(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { config := p.getConfiguration() githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) @@ -873,44 +803,36 @@ func getFailReason(code int, repo string, username string) string { return cause } -func (p *Plugin) createIssueComment(c *UserContext, w http.ResponseWriter, r *http.Request) { - type CreateIssueCommentRequest struct { - PostID string `json:"post_id"` - Owner string `json:"owner"` - Repo string `json:"repo"` - Number int `json:"number"` - Comment string `json:"comment"` - } - - req := &CreateIssueCommentRequest{} +func (p *Plugin) createIssueComment(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { + req := &serializer.CreateIssueCommentRequest{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { c.Log.WithError(err).Warnf("Error decoding CreateIssueCommentRequest JSON body") - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a JSON object.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) return } if req.PostID == "" { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid post id", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid post id", StatusCode: http.StatusBadRequest}) return } if req.Owner == "" { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid repo owner.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid repository owner.", StatusCode: http.StatusBadRequest}) return } if req.Repo == "" { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid repo.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid repository.", StatusCode: http.StatusBadRequest}) return } if req.Number == 0 { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid issue number.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid issue number.", StatusCode: http.StatusBadRequest}) return } if req.Comment == "" { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid non empty comment.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid non empty comment.", StatusCode: http.StatusBadRequest}) return } @@ -918,17 +840,17 @@ func (p *Plugin) createIssueComment(c *UserContext, w http.ResponseWriter, r *ht post, appErr := p.API.GetPost(req.PostID) if appErr != nil { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load post " + req.PostID, StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", req.PostID), StatusCode: http.StatusInternalServerError}) return } if post == nil { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load post " + req.PostID + ": not found", StatusCode: http.StatusNotFound}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", req.PostID), StatusCode: http.StatusNotFound}) return } commentUsername, err := p.getUsername(post.UserId) if err != nil { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) return } @@ -936,18 +858,20 @@ func (p *Plugin) createIssueComment(c *UserContext, w http.ResponseWriter, r *ht permalink := p.getPermaLink(req.PostID) permalinkMessage := fmt.Sprintf("*@%s attached a* [message](%s) *from %s*\n\n", currentUsername, permalink, commentUsername) - req.Comment = permalinkMessage + req.Comment + if req.ShowAttachedMessage { + req.Comment = fmt.Sprintf("%s%s", permalinkMessage, req.Comment) + } comment := &github.IssueComment{ Body: &req.Comment, } result, rawResponse, err := githubClient.Issues.CreateComment(c.Ctx, req.Owner, req.Repo, req.Number, comment) if err != nil { - statusCode := 500 + statusCode := http.StatusInternalServerError if rawResponse != nil { statusCode = rawResponse.StatusCode } - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to create an issue comment: " + getFailReason(statusCode, req.Repo, currentUsername), StatusCode: statusCode}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create an issue comment: %s", getFailReason(statusCode, req.Repo, currentUsername)), StatusCode: statusCode}) return } @@ -965,16 +889,15 @@ func (p *Plugin) createIssueComment(c *UserContext, w http.ResponseWriter, r *ht UserId: c.UserID, } - _, appErr = p.API.CreatePost(reply) - if appErr != nil { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to create notification post " + req.PostID, StatusCode: http.StatusInternalServerError}) + if _, appErr = p.API.CreatePost(reply); appErr != nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create the notification post %s", req.PostID), StatusCode: http.StatusInternalServerError}) return } p.writeJSON(w, result) } -func (p *Plugin) getYourAssignments(c *UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getYourAssignments(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { config := p.getConfiguration() githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) @@ -990,14 +913,14 @@ func (p *Plugin) getYourAssignments(c *UserContext, w http.ResponseWriter, r *ht p.writeJSON(w, result.Issues) } -func (p *Plugin) postToDo(c *UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) postToDo(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) username := c.GHInfo.GitHubUsername text, err := p.GetToDo(c.Ctx, username, githubClient) if err != nil { c.Log.WithError(err).Warnf("Failed to get Todos") - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Encountered an error getting the to do items.", StatusCode: http.StatusUnauthorized}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Encountered an error getting the to do items.", StatusCode: http.StatusUnauthorized}) return } @@ -1010,8 +933,8 @@ func (p *Plugin) postToDo(c *UserContext, w http.ResponseWriter, r *http.Request p.writeJSON(w, resp) } -func (p *Plugin) updateSettings(c *UserContext, w http.ResponseWriter, r *http.Request) { - var settings *UserSettings +func (p *Plugin) updateSettings(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { + var settings *serializer.UserSettings if err := json.NewDecoder(r.Body).Decode(&settings); err != nil { c.Log.WithError(err).Warnf("Error decoding settings from JSON body") http.Error(w, "Invalid request body", http.StatusBadRequest) @@ -1035,29 +958,181 @@ func (p *Plugin) updateSettings(c *UserContext, w http.ResponseWriter, r *http.R p.writeJSON(w, info.Settings) } -func (p *Plugin) getIssueByNumber(c *UserContext, w http.ResponseWriter, r *http.Request) { - owner := r.FormValue("owner") - repo := r.FormValue("repo") - number := r.FormValue("number") - numberInt, err := strconv.Atoi(number) +func (p *Plugin) openAttachCommentIssueModal(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { + req := &serializer.OpenCreateCommentOrEditIssueModalRequestBody{} + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + c.Log.WithError(err).Warnf("Error decoding the JSON body") + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) + return + } + + userID := r.Header.Get(constants.HeaderMattermostUserID) + post, appErr := p.API.GetPost(req.PostID) + if appErr != nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", req.PostID), StatusCode: http.StatusInternalServerError}) + return + } + if post == nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", req.PostID), StatusCode: http.StatusNotFound}) + return + } + + p.API.PublishWebSocketEvent( + wsEventAttachCommentToIssue, + map[string]interface{}{ + "postId": post.Id, + "owner": req.RepoOwner, + "repo": req.RepoName, + "number": req.IssueNumber, + }, + &model.WebsocketBroadcast{UserId: userID}, + ) +} + +func (p *Plugin) openCloseOrReopenIssueModal(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { + req := &serializer.OpenCreateCommentOrEditIssueModalRequestBody{} + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + c.Log.WithError(err).Warnf("Error decoding the JSON body") + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) + return + } + + userID := r.Header.Get(constants.HeaderMattermostUserID) + + post, appErr := p.API.GetPost(req.PostID) + if appErr != nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", req.PostID), StatusCode: http.StatusInternalServerError}) + return + } + if post == nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", req.PostID), StatusCode: http.StatusNotFound}) + return + } + + p.API.PublishWebSocketEvent( + wsEventCloseOrReopenIssue, + map[string]interface{}{ + "channel_id": post.ChannelId, + "owner": req.RepoOwner, + "repo": req.RepoName, + "number": req.IssueNumber, + "status": req.Status, + "postId": req.PostID, + }, + &model.WebsocketBroadcast{UserId: userID}, + ) +} + +func (p *Plugin) openIssueEditModal(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { + req := &serializer.OpenCreateCommentOrEditIssueModalRequestBody{} + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + c.Log.WithError(err).Warnf("Error decoding the JSON body") + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) + return + } + + githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + issue, _, err := githubClient.Issues.Get(c.Ctx, req.RepoOwner, req.RepoName, req.IssueNumber) + if err != nil { + // If the issue is not found, it probably belongs to a private repo. + // Return an empty response in that case. + var gerr *github.ErrorResponse + if errors.As(err, &gerr) && gerr.Response.StatusCode == http.StatusNotFound { + c.Log.WithError(err).With(logger.LogContext{ + "owner": req.RepoOwner, + "repo": req.RepoName, + "number": req.IssueNumber, + }).Debugf("Issue not found") + p.writeJSON(w, nil) + return + } + + c.Log.WithError(err).With(logger.LogContext{ + "owner": req.RepoOwner, + "repo": req.RepoName, + "number": req.IssueNumber, + }).Debugf("Could not get the issue") + p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Could not get the issue", StatusCode: http.StatusInternalServerError}) + return + } + + description := "" + if issue.Body != nil { + *issue.Body = mdCommentRegex.ReplaceAllString(issue.GetBody(), "") + description = *issue.Body + } + + assignees := make([]string, len(issue.Assignees)) + for index, user := range issue.Assignees { + assignees[index] = user.GetLogin() + } + + labels := make([]string, len(issue.Labels)) + for index, label := range issue.Labels { + labels[index] = label.GetName() + } + + milestoneTitle := "" + var milestoneNumber int + if issue.Milestone != nil && issue.Milestone.Title != nil { + milestoneTitle = *issue.Milestone.Title + milestoneNumber = *issue.Milestone.Number + } + + userID := r.Header.Get(constants.HeaderMattermostUserID) + post, appErr := p.API.GetPost(req.PostID) + if appErr != nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", req.PostID), StatusCode: http.StatusInternalServerError}) + return + } + if post == nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", req.PostID), StatusCode: http.StatusNotFound}) + return + } + + p.API.PublishWebSocketEvent( + wsEventCreateOrUpdateIssue, + map[string]interface{}{ + "title": *issue.Title, + "channel_id": post.ChannelId, + "postId": req.PostID, + "milestone_title": milestoneTitle, + "milestone_number": milestoneNumber, + "assignees": assignees, + "labels": labels, + "description": description, + "repo_full_name": fmt.Sprintf("%s/%s", req.RepoOwner, req.RepoName), + "issue_number": *issue.Number, + }, + &model.WebsocketBroadcast{UserId: userID}, + ) + + p.writeJSON(w, issue) +} + +func (p *Plugin) getIssueByNumber(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { + owner := r.FormValue(constants.OwnerQueryParam) + repo := r.FormValue(constants.RepoQueryParam) + number := r.FormValue(constants.NumberQueryParam) + issueNumber, err := strconv.Atoi(number) if err != nil { - p.writeAPIError(w, &APIErrorResponse{Message: "Invalid param 'number'.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Invalid param 'number'.", StatusCode: http.StatusBadRequest}) return } githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) - result, _, err := githubClient.Issues.Get(c.Ctx, owner, repo, numberInt) + result, _, err := githubClient.Issues.Get(c.Ctx, owner, repo, issueNumber) if err != nil { - // If the issue is not found, it's probably behind a private repo. - // Return an empty repose in this case. + // If the issue is not found, it probably belongs to a private repo. + // Return an empty response in that case. var gerr *github.ErrorResponse if errors.As(err, &gerr) && gerr.Response.StatusCode == http.StatusNotFound { c.Log.WithError(err).With(logger.LogContext{ "owner": owner, "repo": repo, - "number": numberInt, - }).Debugf("Issue not found") + "number": issueNumber, + }).Debugf("Issue not found") p.writeJSON(w, nil) return } @@ -1065,40 +1140,41 @@ func (p *Plugin) getIssueByNumber(c *UserContext, w http.ResponseWriter, r *http c.Log.WithError(err).With(logger.LogContext{ "owner": owner, "repo": repo, - "number": numberInt, - }).Debugf("Could not get issue") - p.writeAPIError(w, &APIErrorResponse{Message: "Could not get issue", StatusCode: http.StatusInternalServerError}) + "number": issueNumber, + }).Debugf("Could not get the issue") + p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Could not get the issue", StatusCode: http.StatusInternalServerError}) return } + if result.Body != nil { *result.Body = mdCommentRegex.ReplaceAllString(result.GetBody(), "") } p.writeJSON(w, result) } -func (p *Plugin) getPrByNumber(c *UserContext, w http.ResponseWriter, r *http.Request) { - owner := r.FormValue("owner") - repo := r.FormValue("repo") - number := r.FormValue("number") +func (p *Plugin) getPrByNumber(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { + owner := r.FormValue(constants.OwnerQueryParam) + repo := r.FormValue(constants.RepoQueryParam) + number := r.FormValue(constants.NumberQueryParam) - numberInt, err := strconv.Atoi(number) + prNumber, err := strconv.Atoi(number) if err != nil { - p.writeAPIError(w, &APIErrorResponse{Message: "Invalid param 'number'.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Invalid param 'number'.", StatusCode: http.StatusBadRequest}) return } githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) - result, _, err := githubClient.PullRequests.Get(c.Ctx, owner, repo, numberInt) + result, _, err := githubClient.PullRequests.Get(c.Ctx, owner, repo, prNumber) if err != nil { // If the pull request is not found, it's probably behind a private repo. - // Return an empty repose in this case. + // Return an empty response in that case. var gerr *github.ErrorResponse if errors.As(err, &gerr) && gerr.Response.StatusCode == http.StatusNotFound { c.Log.With(logger.LogContext{ "owner": owner, "repo": repo, - "number": numberInt, + "number": prNumber, }).Debugf("Pull request not found") p.writeJSON(w, nil) @@ -1108,9 +1184,9 @@ func (p *Plugin) getPrByNumber(c *UserContext, w http.ResponseWriter, r *http.Re c.Log.WithError(err).With(logger.LogContext{ "owner": owner, "repo": repo, - "number": numberInt, + "number": prNumber, }).Debugf("Could not get pull request") - p.writeAPIError(w, &APIErrorResponse{Message: "Could not get pull request", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Could not get pull request", StatusCode: http.StatusInternalServerError}) return } if result.Body != nil { @@ -1119,10 +1195,10 @@ func (p *Plugin) getPrByNumber(c *UserContext, w http.ResponseWriter, r *http.Re p.writeJSON(w, result) } -func (p *Plugin) getLabels(c *UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getLabels(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { owner, repo, err := parseRepo(r.URL.Query().Get("repo")) if err != nil { - p.writeAPIError(w, &APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &serializer.APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) return } @@ -1134,7 +1210,7 @@ func (p *Plugin) getLabels(c *UserContext, w http.ResponseWriter, r *http.Reques labels, resp, err := githubClient.Issues.ListLabels(c.Ctx, owner, repo, &opt) if err != nil { c.Log.WithError(err).Warnf("Failed to list labels") - p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch labels", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Failed to fetch labels", StatusCode: http.StatusInternalServerError}) return } allLabels = append(allLabels, labels...) @@ -1147,10 +1223,10 @@ func (p *Plugin) getLabels(c *UserContext, w http.ResponseWriter, r *http.Reques p.writeJSON(w, allLabels) } -func (p *Plugin) getAssignees(c *UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getAssignees(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { owner, repo, err := parseRepo(r.URL.Query().Get("repo")) if err != nil { - p.writeAPIError(w, &APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &serializer.APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) return } @@ -1162,7 +1238,7 @@ func (p *Plugin) getAssignees(c *UserContext, w http.ResponseWriter, r *http.Req assignees, resp, err := githubClient.Issues.ListAssignees(c.Ctx, owner, repo, &opt) if err != nil { c.Log.WithError(err).Warnf("Failed to list assignees") - p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch assignees", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Failed to fetch assignees", StatusCode: http.StatusInternalServerError}) return } allAssignees = append(allAssignees, assignees...) @@ -1175,10 +1251,10 @@ func (p *Plugin) getAssignees(c *UserContext, w http.ResponseWriter, r *http.Req p.writeJSON(w, allAssignees) } -func (p *Plugin) getMilestones(c *UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getMilestones(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { owner, repo, err := parseRepo(r.URL.Query().Get("repo")) if err != nil { - p.writeAPIError(w, &APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &serializer.APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) return } @@ -1190,7 +1266,7 @@ func (p *Plugin) getMilestones(c *UserContext, w http.ResponseWriter, r *http.Re milestones, resp, err := githubClient.Issues.ListMilestones(c.Ctx, owner, repo, &github.MilestoneListOptions{ListOptions: opt}) if err != nil { c.Log.WithError(err).Warnf("Failed to list milestones") - p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch milestones", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Failed to fetch milestones", StatusCode: http.StatusInternalServerError}) return } allMilestones = append(allMilestones, milestones...) @@ -1203,7 +1279,7 @@ func (p *Plugin) getMilestones(c *UserContext, w http.ResponseWriter, r *http.Re p.writeJSON(w, allMilestones) } -func (p *Plugin) getRepositories(c *UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getRepositories(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) org := p.getConfiguration().GitHubOrg @@ -1216,7 +1292,7 @@ func (p *Plugin) getRepositories(c *UserContext, w http.ResponseWriter, r *http. repos, resp, err := githubClient.Repositories.List(c.Ctx, "", &github.RepositoryListOptions{ListOptions: opt}) if err != nil { c.Log.WithError(err).Warnf("Failed to list repositories") - p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError}) return } allRepos = append(allRepos, repos...) @@ -1230,7 +1306,7 @@ func (p *Plugin) getRepositories(c *UserContext, w http.ResponseWriter, r *http. repos, resp, err := githubClient.Repositories.ListByOrg(c.Ctx, org, &github.RepositoryListByOrgOptions{Sort: "full_name", ListOptions: opt}) if err != nil { c.Log.WithError(err).Warnf("Failed to list repositories by org") - p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError}) return } allRepos = append(allRepos, repos...) @@ -1240,15 +1316,7 @@ func (p *Plugin) getRepositories(c *UserContext, w http.ResponseWriter, r *http. opt.Page = resp.NextPage } } - - // Only send down fields to client that are needed - type RepositoryResponse struct { - Name string `json:"name,omitempty"` - FullName string `json:"full_name,omitempty"` - Permissions map[string]bool `json:"permissions,omitempty"` - } - - resp := make([]RepositoryResponse, len(allRepos)) + resp := make([]serializer.RepositoryResponse, len(allRepos)) for i, r := range allRepos { resp[i].Name = r.GetName() resp[i].FullName = r.GetFullName() @@ -1258,38 +1326,168 @@ func (p *Plugin) getRepositories(c *UserContext, w http.ResponseWriter, r *http. p.writeJSON(w, resp) } -func (p *Plugin) createIssue(c *UserContext, w http.ResponseWriter, r *http.Request) { - type IssueRequest struct { - Title string `json:"title"` - Body string `json:"body"` - Repo string `json:"repo"` - PostID string `json:"post_id"` - ChannelID string `json:"channel_id"` - Labels []string `json:"labels"` - Assignees []string `json:"assignees"` - Milestone int `json:"milestone"` +func (p *Plugin) updateIssue(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { + // get data for the issue from the request body and fill UpdateIssueRequest to update the issue + issue := &serializer.UpdateIssueRequest{} + if err := json.NewDecoder(r.Body).Decode(&issue); err != nil { + c.Log.WithError(err).Warnf("Error decoding the JSON body") + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) + return + } + + if !p.validateIssueRequestForUpdation(issue, w) { + return } - // get data for the issue from the request body and fill IssueRequest object - issue := &IssueRequest{} + var post *model.Post + permalink := "" + if issue.PostID != "" { + var appErr *model.AppError + post, appErr = p.API.GetPost(issue.PostID) + if appErr != nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) + return + } + if post == nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", issue.PostID), StatusCode: http.StatusNotFound}) + return + } + permalink = p.getPermaLink(issue.PostID) + } + + githubIssue := &github.IssueRequest{ + Title: &issue.Title, + Body: &issue.Body, + Labels: &issue.Labels, + Assignees: &issue.Assignees, + } + + // submitting the request with an invalid milestone ID results in a 422 error + // we should make sure it's not zero here because the webapp client might have left this field empty + if issue.Milestone > 0 { + githubIssue.Milestone = &issue.Milestone + } + + currentUser, appErr := p.API.GetUser(c.UserID) + if appErr != nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "failed to load current user", StatusCode: http.StatusInternalServerError}) + return + } + + splittedRepo := strings.Split(issue.Repo, "/") + if len(splittedRepo) < 2 { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid repository", StatusCode: http.StatusBadRequest}) + } + + owner, repoName := splittedRepo[0], splittedRepo[1] + githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + + result, resp, err := githubClient.Issues.Edit(c.Ctx, owner, repoName, issue.IssueNumber, githubIssue) + if err != nil { + if resp != nil && resp.Response.StatusCode == http.StatusGone { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Issues are disabled on this repository.", StatusCode: http.StatusMethodNotAllowed}) + return + } + + c.Log.WithError(err).Warnf("Failed to update the issue") + p.writeAPIError(w, &serializer.APIErrorResponse{ + ID: "", + Message: fmt.Sprintf("failed to update the issue: %s", getFailReason(resp.StatusCode, + issue.Repo, + currentUser.Username, + )), + StatusCode: resp.StatusCode, + }) + return + } + + rootID := issue.PostID + channelID := issue.ChannelID + message := fmt.Sprintf("Updated GitHub issue [#%v](%v)", result.GetNumber(), result.GetHTMLURL()) + if post != nil { + if post.RootId != "" { + rootID = post.RootId + } + channelID = post.ChannelId + message += fmt.Sprintf(" from a [message](%s)", permalink) + } + + reply := &model.Post{ + Message: message, + ChannelId: channelID, + RootId: rootID, + UserId: c.UserID, + } + + if post != nil { + _, appErr = p.API.CreatePost(reply) + } else { + _ = p.API.SendEphemeralPost(c.UserID, reply) + } + if appErr != nil { + c.Log.WithError(appErr).Warnf("failed to create the notification post") + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create the notification post, postID: %s, channelID: %s", issue.PostID, channelID), StatusCode: http.StatusInternalServerError}) + return + } + + p.updatePost(post, issue, w) + p.writeJSON(w, result) +} + +func (p *Plugin) closeOrReopenIssue(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { + req := &serializer.CommentAndCloseRequest{} + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + c.Log.WithError(err).Warnf("Error decoding the JSON body") + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) + return + } + + post, appErr := p.API.GetPost(req.PostID) + if appErr != nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", req.PostID), StatusCode: http.StatusInternalServerError}) + return + } + if post == nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", req.PostID), StatusCode: http.StatusNotFound}) + return + } + + if _, err := p.getUsername(post.UserId); err != nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) + return + } + if req.IssueComment != "" { + p.CreateCommentToIssue(c, w, req.IssueComment, req.Owner, req.Repository, post, req.Number) + } + + if req.Status == constants.Close { + p.CloseOrReopenIssue(c, w, constants.IssueClose, req.StatusReason, req.Owner, req.Repository, post, req.Number) + } else { + p.CloseOrReopenIssue(c, w, constants.IssueOpen, req.StatusReason, req.Owner, req.Repository, post, req.Number) + } +} + +func (p *Plugin) createIssue(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { + // get data for the issue from the request body and fill CreateIssueRequest object to create the issue + issue := &serializer.CreateIssueRequest{} if err := json.NewDecoder(r.Body).Decode(&issue); err != nil { - c.Log.WithError(err).Warnf("Error decoding JSON body") - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a JSON object.", StatusCode: http.StatusBadRequest}) + c.Log.WithError(err).Warnf("Error decoding the JSON body") + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) return } if issue.Title == "" { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid issue title.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid issue title.", StatusCode: http.StatusBadRequest}) return } if issue.Repo == "" { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid repo name.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid repository name.", StatusCode: http.StatusBadRequest}) return } if issue.PostID == "" && issue.ChannelID == "" { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide either a postID or a channelID", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide either a postID or a channelID", StatusCode: http.StatusBadRequest}) return } @@ -1300,17 +1498,17 @@ func (p *Plugin) createIssue(c *UserContext, w http.ResponseWriter, r *http.Requ var appErr *model.AppError post, appErr = p.API.GetPost(issue.PostID) if appErr != nil { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load post " + issue.PostID, StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) return } if post == nil { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load post " + issue.PostID + ": not found", StatusCode: http.StatusNotFound}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", issue.PostID), StatusCode: http.StatusNotFound}) return } username, err := p.getUsername(post.UserId) if err != nil { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) return } @@ -1319,7 +1517,7 @@ func (p *Plugin) createIssue(c *UserContext, w http.ResponseWriter, r *http.Requ mmMessage = fmt.Sprintf("_Issue created from a [Mattermost message](%v) *by %s*._", permalink, username) } - ghIssue := &github.IssueRequest{ + githubIssue := &github.IssueRequest{ Title: &issue.Title, Body: &issue.Body, Labels: &issue.Labels, @@ -1327,44 +1525,39 @@ func (p *Plugin) createIssue(c *UserContext, w http.ResponseWriter, r *http.Requ } // submitting the request with an invalid milestone ID results in a 422 error - // we make sure it's not zero here, because the webapp client might have left this field empty + // we should make sure it's not zero here because the webapp client might have left this field empty if issue.Milestone > 0 { - ghIssue.Milestone = &issue.Milestone + githubIssue.Milestone = &issue.Milestone } - if ghIssue.GetBody() != "" && mmMessage != "" { + if githubIssue.GetBody() != "" && mmMessage != "" { mmMessage = "\n\n" + mmMessage } - *ghIssue.Body = ghIssue.GetBody() + mmMessage + *githubIssue.Body = fmt.Sprintf("%s%s", githubIssue.GetBody(), mmMessage) currentUser, appErr := p.API.GetUser(c.UserID) if appErr != nil { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load current user", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "failed to load current user", StatusCode: http.StatusInternalServerError}) return } splittedRepo := strings.Split(issue.Repo, "/") - owner := splittedRepo[0] - repoName := splittedRepo[1] + owner, repoName := splittedRepo[0], splittedRepo[1] githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) - result, resp, err := githubClient.Issues.Create(c.Ctx, owner, repoName, ghIssue) + result, resp, err := githubClient.Issues.Create(c.Ctx, owner, repoName, githubIssue) if err != nil { if resp != nil && resp.Response.StatusCode == http.StatusGone { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Issues are disabled on this repository.", StatusCode: http.StatusMethodNotAllowed}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Issues are disabled on this repository.", StatusCode: http.StatusMethodNotAllowed}) return } c.Log.WithError(err).Warnf("Failed to create issue") - p.writeAPIError(w, - &APIErrorResponse{ - ID: "", - Message: "failed to create issue: " + getFailReason(resp.StatusCode, - issue.Repo, - currentUser.Username, - ), - StatusCode: resp.StatusCode, - }) + p.writeAPIError(w, &serializer.APIErrorResponse{ + ID: "", + Message: fmt.Sprintf("failed to create issue: %s", getFailReason(resp.StatusCode, issue.Repo, currentUser.Username)), + StatusCode: resp.StatusCode, + }) return } @@ -1389,11 +1582,11 @@ func (p *Plugin) createIssue(c *UserContext, w http.ResponseWriter, r *http.Requ if post != nil { _, appErr = p.API.CreatePost(reply) } else { - p.API.SendEphemeralPost(c.UserID, reply) + _ = p.API.SendEphemeralPost(c.UserID, reply) } if appErr != nil { - c.Log.WithError(appErr).Warnf("failed to create notification post") - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to create notification post, postID: " + issue.PostID + ", channelID: " + channelID, StatusCode: http.StatusInternalServerError}) + c.Log.WithError(appErr).Warnf("failed to create the notification post") + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create the notification post, postID: %s, channelID: %s", issue.PostID, channelID), StatusCode: http.StatusInternalServerError}) return } diff --git a/server/plugin/api_test.go b/server/plugin/api_test.go index 8aaa5bd89..0db23f92a 100644 --- a/server/plugin/api_test.go +++ b/server/plugin/api_test.go @@ -11,6 +11,8 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/mattermost/mattermost-plugin-github/server/constants" + "github.com/mattermost/mattermost-plugin-github/server/serializer" "github.com/mattermost/mattermost-plugin-github/server/testutils" ) @@ -79,7 +81,7 @@ func TestPlugin_ServeHTTP(t *testing.T) { expectedResponse: testutils.ExpectedResponse{ StatusCode: http.StatusUnauthorized, ResponseType: testutils.ContentTypeJSON, - Body: APIErrorResponse{ID: "", Message: "Not authorized.", StatusCode: http.StatusUnauthorized}, + Body: serializer.APIErrorResponse{ID: "", Message: "Not authorized.", StatusCode: http.StatusUnauthorized}, }, userID: "", }, "unauthorized test http": { @@ -115,7 +117,7 @@ func TestPlugin_ServeHTTP(t *testing.T) { p.SetAPI(&plugintest.API{}) req := test.httpTest.CreateHTTPRequest(test.request) - req.Header.Add("Mattermost-User-ID", test.userID) + req.Header.Add(constants.HeaderMattermostUserID, test.userID) rr := httptest.NewRecorder() p.ServeHTTP(&plugin.Context{}, rr, req) test.httpTest.CompareHTTPResponse(rr, test.expectedResponse) diff --git a/server/plugin/cluster.go b/server/plugin/cluster.go index 87b992c40..f78325647 100644 --- a/server/plugin/cluster.go +++ b/server/plugin/cluster.go @@ -3,7 +3,7 @@ package plugin import ( "encoding/json" - "github.com/google/go-github/v41/github" + "github.com/google/go-github/v48/github" "github.com/mattermost/mattermost-server/v6/model" ) diff --git a/server/plugin/command.go b/server/plugin/command.go index af1412697..e0e38de75 100644 --- a/server/plugin/command.go +++ b/server/plugin/command.go @@ -7,6 +7,8 @@ import ( "unicode" "github.com/mattermost/mattermost-plugin-api/experimental/command" + "github.com/mattermost/mattermost-plugin-github/server/constants" + "github.com/mattermost/mattermost-plugin-github/server/serializer" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/plugin" "github.com/pkg/errors" @@ -93,7 +95,7 @@ func (p *Plugin) postCommandResponse(args *model.CommandArgs, text string) { _ = p.API.SendEphemeralPost(args.UserId, post) } -func (p *Plugin) getMutedUsernames(userInfo *GitHubUserInfo) []string { +func (p *Plugin) getMutedUsernames(userInfo *serializer.GitHubUserInfo) []string { mutedUsernameBytes, err := p.API.KVGet(userInfo.UserID + "-muted-users") if err != nil { return nil @@ -107,7 +109,7 @@ func (p *Plugin) getMutedUsernames(userInfo *GitHubUserInfo) []string { return mutedUsers } -func (p *Plugin) handleMuteList(args *model.CommandArgs, userInfo *GitHubUserInfo) string { +func (p *Plugin) handleMuteList(args *model.CommandArgs, userInfo *serializer.GitHubUserInfo) string { mutedUsernames := p.getMutedUsernames(userInfo) var mutedUsers string for _, user := range mutedUsernames { @@ -128,7 +130,7 @@ func contains(s []string, e string) bool { return false } -func (p *Plugin) handleMuteAdd(args *model.CommandArgs, username string, userInfo *GitHubUserInfo) string { +func (p *Plugin) handleMuteAdd(args *model.CommandArgs, username string, userInfo *serializer.GitHubUserInfo) string { mutedUsernames := p.getMutedUsernames(userInfo) if contains(mutedUsernames, username) { return username + " is already muted" @@ -151,7 +153,7 @@ func (p *Plugin) handleMuteAdd(args *model.CommandArgs, username string, userInf return fmt.Sprintf("`%v`", username) + " is now muted. You'll no longer receive notifications for comments in your PRs and issues." } -func (p *Plugin) handleUnmute(args *model.CommandArgs, username string, userInfo *GitHubUserInfo) string { +func (p *Plugin) handleUnmute(args *model.CommandArgs, username string, userInfo *serializer.GitHubUserInfo) string { mutedUsernames := p.getMutedUsernames(userInfo) userToMute := []string{username} newMutedList := arrayDifference(mutedUsernames, userToMute) @@ -161,14 +163,14 @@ func (p *Plugin) handleUnmute(args *model.CommandArgs, username string, userInfo return fmt.Sprintf("`%v`", username) + " is no longer muted" } -func (p *Plugin) handleUnmuteAll(args *model.CommandArgs, userInfo *GitHubUserInfo) string { +func (p *Plugin) handleUnmuteAll(args *model.CommandArgs, userInfo *serializer.GitHubUserInfo) string { if err := p.API.KVSet(userInfo.UserID+"-muted-users", []byte("")); err != nil { return "Error occurred unmuting users" } return "Unmuted all users" } -func (p *Plugin) handleMuteCommand(_ *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string { +func (p *Plugin) handleMuteCommand(_ *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *serializer.GitHubUserInfo) string { if len(parameters) == 0 { return "Invalid mute command. Available commands are 'list', 'add' and 'delete'." } @@ -210,7 +212,7 @@ func arrayDifference(a, b []string) []string { return diff } -func (p *Plugin) handleSubscribe(c *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string { +func (p *Plugin) handleSubscribe(c *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *serializer.GitHubUserInfo) string { switch { case len(parameters) == 0: return "Please specify a repository or 'list' command." @@ -221,7 +223,7 @@ func (p *Plugin) handleSubscribe(c *plugin.Context, args *model.CommandArgs, par } } -func (p *Plugin) handleSubscriptions(c *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string { +func (p *Plugin) handleSubscriptions(c *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *serializer.GitHubUserInfo) string { if len(parameters) == 0 { return "Invalid subscribe command. Available commands are 'list', 'add' and 'delete'." } @@ -241,7 +243,7 @@ func (p *Plugin) handleSubscriptions(c *plugin.Context, args *model.CommandArgs, } } -func (p *Plugin) handleSubscriptionsList(_ *plugin.Context, args *model.CommandArgs, parameters []string, _ *GitHubUserInfo) string { +func (p *Plugin) handleSubscriptionsList(_ *plugin.Context, args *model.CommandArgs, parameters []string, _ *serializer.GitHubUserInfo) string { txt := "" subs, err := p.GetSubscriptionsByChannel(args.ChannelId) if err != nil { @@ -265,7 +267,7 @@ func (p *Plugin) handleSubscriptionsList(_ *plugin.Context, args *model.CommandA return txt } -func (p *Plugin) handleSubscribesAdd(_ *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string { +func (p *Plugin) handleSubscribesAdd(_ *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *serializer.GitHubUserInfo) string { if len(parameters) == 0 { return "Please specify a repository." } @@ -345,7 +347,7 @@ func (p *Plugin) handleSubscribesAdd(_ *plugin.Context, args *model.CommandArgs, return msg } -func (p *Plugin) handleUnsubscribe(_ *plugin.Context, args *model.CommandArgs, parameters []string, _ *GitHubUserInfo) string { +func (p *Plugin) handleUnsubscribe(_ *plugin.Context, args *model.CommandArgs, parameters []string, _ *serializer.GitHubUserInfo) string { if len(parameters) == 0 { return "Please specify a repository." } @@ -360,12 +362,12 @@ func (p *Plugin) handleUnsubscribe(_ *plugin.Context, args *model.CommandArgs, p return fmt.Sprintf("Successfully unsubscribed from %s.", repo) } -func (p *Plugin) handleDisconnect(_ *plugin.Context, args *model.CommandArgs, _ []string, _ *GitHubUserInfo) string { +func (p *Plugin) handleDisconnect(_ *plugin.Context, args *model.CommandArgs, _ []string, _ *serializer.GitHubUserInfo) string { p.disconnectGitHubAccount(args.UserId) return "Disconnected your GitHub account." } -func (p *Plugin) handleTodo(_ *plugin.Context, _ *model.CommandArgs, _ []string, userInfo *GitHubUserInfo) string { +func (p *Plugin) handleTodo(_ *plugin.Context, _ *model.CommandArgs, _ []string, userInfo *serializer.GitHubUserInfo) string { githubClient := p.githubConnectUser(context.Background(), userInfo) text, err := p.GetToDo(context.Background(), userInfo.GitHubUsername, githubClient) @@ -377,7 +379,7 @@ func (p *Plugin) handleTodo(_ *plugin.Context, _ *model.CommandArgs, _ []string, return text } -func (p *Plugin) handleMe(_ *plugin.Context, _ *model.CommandArgs, _ []string, userInfo *GitHubUserInfo) string { +func (p *Plugin) handleMe(_ *plugin.Context, _ *model.CommandArgs, _ []string, userInfo *serializer.GitHubUserInfo) string { githubClient := p.githubConnectUser(context.Background(), userInfo) gitUser, _, err := githubClient.Users.Get(context.Background(), "") if err != nil { @@ -388,7 +390,7 @@ func (p *Plugin) handleMe(_ *plugin.Context, _ *model.CommandArgs, _ []string, u return text } -func (p *Plugin) handleHelp(_ *plugin.Context, _ *model.CommandArgs, _ []string, _ *GitHubUserInfo) string { +func (p *Plugin) handleHelp(_ *plugin.Context, _ *model.CommandArgs, _ []string, _ *serializer.GitHubUserInfo) string { message, err := renderTemplate("helpText", p.getConfiguration()) if err != nil { p.API.LogWarn("Failed to render help template", "error", err.Error()) @@ -398,7 +400,7 @@ func (p *Plugin) handleHelp(_ *plugin.Context, _ *model.CommandArgs, _ []string, return "###### Mattermost GitHub Plugin - Slash Command Help\n" + message } -func (p *Plugin) handleSettings(_ *plugin.Context, _ *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string { +func (p *Plugin) handleSettings(_ *plugin.Context, _ *model.CommandArgs, parameters []string, userInfo *serializer.GitHubUserInfo) string { if len(parameters) < 2 { return "Please specify both a setting and value. Use `/github help` for more usage information." } @@ -463,7 +465,7 @@ func (p *Plugin) handleSettings(_ *plugin.Context, _ *model.CommandArgs, paramet return "Settings updated." } -func (p *Plugin) handleIssue(_ *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string { +func (p *Plugin) handleIssue(_ *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *serializer.GitHubUserInfo) string { if len(parameters) == 0 { return "Invalid issue command. Available command is 'create'." } @@ -517,7 +519,7 @@ func (p *Plugin) handleSetup(c *plugin.Context, args *model.CommandArgs, paramet return "" } -type CommandHandleFunc func(c *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string +type CommandHandleFunc func(c *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *serializer.GitHubUserInfo) string func (p *Plugin) isAuthorizedSysAdmin(userID string) (bool, error) { user, appErr := p.API.GetUser(userID) @@ -603,7 +605,7 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo info, apiErr := p.getGitHubUserInfo(args.UserId) if apiErr != nil { text := "Unknown error." - if apiErr.ID == apiErrorIDNotConnected { + if apiErr.ID == constants.APIErrorIDNotConnected { text = "You must connect your account to GitHub first. Either click on the GitHub logo in the bottom left of the screen or enter `/github connect`." } p.postCommandResponse(args, text) diff --git a/server/plugin/flows.go b/server/plugin/flows.go index d5c74549d..c8e459a6f 100644 --- a/server/plugin/flows.go +++ b/server/plugin/flows.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/google/go-github/v41/github" + "github.com/google/go-github/v48/github" "github.com/gorilla/mux" pluginapi "github.com/mattermost/mattermost-plugin-api" "github.com/mattermost/mattermost-plugin-api/experimental/flow" diff --git a/server/plugin/mm_34646_token_refresh.go b/server/plugin/mm_34646_token_refresh.go index 62c7ccb72..fd9c63c1b 100644 --- a/server/plugin/mm_34646_token_refresh.go +++ b/server/plugin/mm_34646_token_refresh.go @@ -5,10 +5,11 @@ import ( "encoding/json" "time" - "github.com/google/go-github/v41/github" + "github.com/google/go-github/v48/github" "github.com/pkg/errors" "github.com/mattermost/mattermost-plugin-api/cluster" + "github.com/mattermost/mattermost-plugin-github/server/serializer" ) const pageSize = 100 @@ -49,7 +50,7 @@ func (p *Plugin) forceResetAllMM34646() error { appErr.Error()) continue } - tryInfo := GitHubUserInfo{} + tryInfo := serializer.GitHubUserInfo{} err := json.Unmarshal(data, &tryInfo) if err != nil { // too noisy to report @@ -65,7 +66,7 @@ func (p *Plugin) forceResetAllMM34646() error { info, errResp := p.getGitHubUserInfo(tryInfo.UserID) if errResp != nil { - p.API.LogError("failed to retrieve GitHubUserInfo", "key", key, "user_id", tryInfo.UserID, + p.API.LogError("failed to retrieve serializer.GitHubUserInfo", "key", key, "user_id", tryInfo.UserID, "error", errResp.Error()) continue } @@ -89,7 +90,7 @@ func (p *Plugin) forceResetAllMM34646() error { return nil } -func (p *Plugin) forceResetUserTokenMM34646(ctx context.Context, config *Configuration, info *GitHubUserInfo) (string, error) { +func (p *Plugin) forceResetUserTokenMM34646(ctx context.Context, config *Configuration, info *serializer.GitHubUserInfo) (string, error) { if info.MM34646ResetTokenDone { return info.Token.AccessToken, nil } @@ -111,7 +112,7 @@ func (p *Plugin) forceResetUserTokenMM34646(ctx context.Context, config *Configu info.MM34646ResetTokenDone = true err = p.storeGitHubUserInfo(info) if err != nil { - return "", errors.Wrap(err, "failed to store updated GitHubUserInfo") + return "", errors.Wrap(err, "failed to store updated serializer.GitHubUserInfo") } p.API.LogDebug("Updated user access token for MM-34646", "user_id", info.UserID) diff --git a/server/plugin/permalinks.go b/server/plugin/permalinks.go index 22d1bd6a8..4f1c27353 100644 --- a/server/plugin/permalinks.go +++ b/server/plugin/permalinks.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/google/go-github/v41/github" + "github.com/google/go-github/v48/github" ) // maxPermalinkReplacements sets the maximum limit to the number of diff --git a/server/plugin/permalinks_test.go b/server/plugin/permalinks_test.go index cbc48cdd0..82d4d3b27 100644 --- a/server/plugin/permalinks_test.go +++ b/server/plugin/permalinks_test.go @@ -7,7 +7,7 @@ import ( "net/url" "testing" - "github.com/google/go-github/v41/github" + "github.com/google/go-github/v48/github" "github.com/mattermost/mattermost-server/v6/plugin/plugintest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" diff --git a/server/plugin/plugin.go b/server/plugin/plugin.go index 5dc367947..ac8a7baca 100644 --- a/server/plugin/plugin.go +++ b/server/plugin/plugin.go @@ -12,11 +12,13 @@ import ( "strings" "sync" - "github.com/google/go-github/v41/github" + "github.com/google/go-github/v48/github" "github.com/gorilla/mux" pluginapi "github.com/mattermost/mattermost-plugin-api" "github.com/mattermost/mattermost-plugin-api/experimental/bot/poster" "github.com/mattermost/mattermost-plugin-api/experimental/telemetry" + "github.com/mattermost/mattermost-plugin-github/server/constants" + "github.com/mattermost/mattermost-plugin-github/server/serializer" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/plugin" "github.com/pkg/errors" @@ -37,9 +39,11 @@ const ( wsEventConnect = "connect" wsEventDisconnect = "disconnect" // WSEventConfigUpdate is the WebSocket event to update the configurations on webapp. - WSEventConfigUpdate = "config_update" - wsEventRefresh = "refresh" - wsEventCreateIssue = "createIssue" + WSEventConfigUpdate = "config_update" + wsEventRefresh = "refresh" + wsEventCreateOrUpdateIssue = "createOrUpdateIssue" + wsEventCloseOrReopenIssue = "closeOrReopenIssue" + wsEventAttachCommentToIssue = "attachCommentToIssue" WSEventRefresh = "refresh" @@ -123,7 +127,7 @@ func (p *Plugin) GetGitHubClient(ctx context.Context, userID string) (*github.Cl return p.githubConnectUser(ctx, userInfo), nil } -func (p *Plugin) githubConnectUser(ctx context.Context, info *GitHubUserInfo) *github.Client { +func (p *Plugin) githubConnectUser(ctx context.Context, info *serializer.GitHubUserInfo) *github.Client { tok := *info.Token return p.githubConnectToken(tok) } @@ -301,7 +305,7 @@ func (p *Plugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*mode msg := post.Message info, appErr := p.getGitHubUserInfo(post.UserId) if appErr != nil { - if appErr.ID != apiErrorIDNotConnected { + if appErr.ID != constants.APIErrorIDNotConnected { p.API.LogError("Error in getting user info", "error", appErr.Message) } return nil, "" @@ -371,26 +375,7 @@ func (p *Plugin) getOAuthConfigForChimeraApp(scopes []string) *oauth2.Config { } } -type GitHubUserInfo struct { - UserID string - Token *oauth2.Token - GitHubUsername string - LastToDoPostAt int64 - Settings *UserSettings - AllowedPrivateRepos bool - - // MM34646ResetTokenDone is set for a user whose token has been reset for MM-34646. - MM34646ResetTokenDone bool -} - -type UserSettings struct { - SidebarButtons string `json:"sidebar_buttons"` - DailyReminder bool `json:"daily_reminder"` - DailyReminderOnChange bool `json:"daily_reminder_on_change"` - Notifications bool `json:"notifications"` -} - -func (p *Plugin) storeGitHubUserInfo(info *GitHubUserInfo) error { +func (p *Plugin) storeGitHubUserInfo(info *serializer.GitHubUserInfo) error { config := p.getConfiguration() encryptedToken, err := encrypt([]byte(config.EncryptionKey), info.Token.AccessToken) @@ -412,24 +397,24 @@ func (p *Plugin) storeGitHubUserInfo(info *GitHubUserInfo) error { return nil } -func (p *Plugin) getGitHubUserInfo(userID string) (*GitHubUserInfo, *APIErrorResponse) { +func (p *Plugin) getGitHubUserInfo(userID string) (*serializer.GitHubUserInfo, *serializer.APIErrorResponse) { config := p.getConfiguration() - var userInfo GitHubUserInfo + var userInfo serializer.GitHubUserInfo infoBytes, appErr := p.API.KVGet(userID + githubTokenKey) if appErr != nil || infoBytes == nil { - return nil, &APIErrorResponse{ID: apiErrorIDNotConnected, Message: "Must connect user account to GitHub first.", StatusCode: http.StatusBadRequest} + return nil, &serializer.APIErrorResponse{ID: constants.APIErrorIDNotConnected, Message: "Must connect user account to GitHub first.", StatusCode: http.StatusBadRequest} } if err := json.Unmarshal(infoBytes, &userInfo); err != nil { - return nil, &APIErrorResponse{ID: "", Message: "Unable to parse token.", StatusCode: http.StatusInternalServerError} + return nil, &serializer.APIErrorResponse{ID: "", Message: "Unable to parse token.", StatusCode: http.StatusInternalServerError} } unencryptedToken, err := decrypt([]byte(config.EncryptionKey), userInfo.Token.AccessToken) if err != nil { p.API.LogWarn("Failed to decrypt access token", "error", err.Error()) - return nil, &APIErrorResponse{ID: "", Message: "Unable to decrypt access token.", StatusCode: http.StatusInternalServerError} + return nil, &serializer.APIErrorResponse{ID: "", Message: "Unable to decrypt access token.", StatusCode: http.StatusInternalServerError} } userInfo.Token.AccessToken = unencryptedToken @@ -496,7 +481,7 @@ func (p *Plugin) disconnectGitHubAccount(userID string) { func (p *Plugin) openIssueCreateModal(userID string, channelID string, title string) { p.API.PublishWebSocketEvent( - wsEventCreateIssue, + wsEventCreateOrUpdateIssue, map[string]interface{}{ "title": title, "channel_id": channelID, @@ -556,7 +541,7 @@ func (p *Plugin) GetDailySummaryText(userID string) (string, error) { return string(summaryByte), nil } -func (p *Plugin) PostToDo(info *GitHubUserInfo, userID string) error { +func (p *Plugin) PostToDo(info *serializer.GitHubUserInfo, userID string) error { ctx := context.Background() text, err := p.GetToDo(ctx, info.GitHubUsername, p.githubConnectUser(ctx, info)) if err != nil { @@ -691,7 +676,7 @@ func (p *Plugin) GetToDo(ctx context.Context, username string, githubClient *git return text, nil } -func (p *Plugin) HasUnreads(info *GitHubUserInfo) bool { +func (p *Plugin) HasUnreads(info *serializer.GitHubUserInfo) bool { username := info.GitHubUsername ctx := context.Background() githubClient := p.githubConnectUser(ctx, info) @@ -796,7 +781,7 @@ func (p *Plugin) sendRefreshEvent(userID string) { func (p *Plugin) getUsername(mmUserID string) (string, error) { info, apiEr := p.getGitHubUserInfo(mmUserID) if apiEr != nil { - if apiEr.ID != apiErrorIDNotConnected { + if apiEr.ID != constants.APIErrorIDNotConnected { return "", apiEr } diff --git a/server/plugin/subscriptions.go b/server/plugin/subscriptions.go index d5067fe65..517fe488c 100644 --- a/server/plugin/subscriptions.go +++ b/server/plugin/subscriptions.go @@ -9,7 +9,7 @@ import ( "strconv" "strings" - "github.com/google/go-github/v41/github" + "github.com/google/go-github/v48/github" "github.com/pkg/errors" ) diff --git a/server/plugin/template_test.go b/server/plugin/template_test.go index 6b31a508d..b52f00c77 100644 --- a/server/plugin/template_test.go +++ b/server/plugin/template_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/google/go-github/v41/github" + "github.com/google/go-github/v48/github" "github.com/stretchr/testify/require" ) diff --git a/server/plugin/utils.go b/server/plugin/utils.go index 91ccc94c2..a7628cb4b 100644 --- a/server/plugin/utils.go +++ b/server/plugin/utils.go @@ -9,12 +9,17 @@ import ( "encoding/base64" "fmt" "io" + "net/http" "net/url" "path" "strconv" "strings" "unicode" + "github.com/google/go-github/v48/github" + "github.com/mattermost/mattermost-plugin-github/server/constants" + "github.com/mattermost/mattermost-plugin-github/server/serializer" + "github.com/mattermost/mattermost-server/v6/model" "github.com/pkg/errors" ) @@ -351,3 +356,139 @@ func isValidURL(rawURL string) error { return nil } + +func (p *Plugin) validateIssueRequestForUpdation(issue *serializer.UpdateIssueRequest, w http.ResponseWriter) bool { + if issue.Title == "" { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid issue title.", StatusCode: http.StatusBadRequest}) + return false + } + if issue.PostID == "" && issue.ChannelID == "" { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide either a postID or a channelID", StatusCode: http.StatusBadRequest}) + return false + } + + return true +} + +func (p *Plugin) updatePost(post *model.Post, issue *serializer.UpdateIssueRequest, w http.ResponseWriter) { + post, appErr := p.API.GetPost(issue.PostID) + if appErr != nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) + return + } + if post == nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", issue.PostID), StatusCode: http.StatusNotFound}) + return + } + + post.Props[constants.AssigneesForProps] = issue.Assignees + post.Props[constants.LabelsForProps] = issue.Labels + post.Props[constants.DescriptionForProps] = issue.Body + post.Props[constants.TitleForProps] = issue.Title + if _, appErr = p.API.UpdatePost(post); appErr != nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to update the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) + } +} + +func (p *Plugin) CreateCommentToIssue(c *serializer.UserContext, w http.ResponseWriter, comment, owner, repo string, post *model.Post, issueNumber int) { + currentUsername := c.GHInfo.GitHubUsername + permalink := p.getPermaLink(post.Id) + issueComment := &github.IssueComment{ + Body: &comment, + } + githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + + result, rawResponse, err := githubClient.Issues.CreateComment(c.Ctx, owner, repo, issueNumber, issueComment) + if err != nil { + statusCode := http.StatusInternalServerError + if rawResponse != nil { + statusCode = rawResponse.StatusCode + } + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create an issue comment: %s", getFailReason(statusCode, repo, currentUsername)), StatusCode: statusCode}) + return + } + + rootID := post.Id + if post.RootId != "" { + // the original post was a reply + rootID = post.RootId + } + + permalinkReplyMessage := fmt.Sprintf("[Comment](%v) attached to GitHub issue [#%v](%v)", permalink, issueNumber, result.GetHTMLURL()) + reply := &model.Post{ + Message: permalinkReplyMessage, + ChannelId: post.ChannelId, + RootId: rootID, + UserId: c.UserID, + } + + if _, appErr := p.API.CreatePost(reply); appErr != nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create the notification post %s", post.Id), StatusCode: http.StatusInternalServerError}) + return + } +} + +func (p *Plugin) CloseOrReopenIssue(c *serializer.UserContext, w http.ResponseWriter, status, statusReason, owner, repo string, post *model.Post, issueNumber int) { + currentUsername := c.GHInfo.GitHubUsername + githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) + githubIssue := &github.IssueRequest{ + State: &(status), + StateReason: &(statusReason), + } + + issue, resp, err := githubClient.Issues.Edit(c.Ctx, owner, repo, issueNumber, githubIssue) + if err != nil { + if resp != nil && resp.Response.StatusCode == http.StatusGone { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Issues are disabled on this repository.", StatusCode: http.StatusMethodNotAllowed}) + return + } + + c.Log.WithError(err).Warnf("Failed to update the issue") + p.writeAPIError(w, &serializer.APIErrorResponse{ + ID: "", + Message: fmt.Sprintf("failed to update the issue: %s", getFailReason(resp.StatusCode, + repo, + currentUsername, + )), + StatusCode: resp.StatusCode, + }) + return + } + + var permalinkReplyMessage string + switch statusReason { + case constants.IssueCompleted: + permalinkReplyMessage = fmt.Sprintf("Issue closed as completed [#%v](%v)", issueNumber, issue.GetHTMLURL()) + case constants.IssueNotPlanned: + permalinkReplyMessage = fmt.Sprintf("Issue closed as not planned [#%v](%v)", issueNumber, issue.GetHTMLURL()) + default: + permalinkReplyMessage = fmt.Sprintf("Issue reopend [#%v](%v)", issueNumber, issue.GetHTMLURL()) + } + + rootID := post.Id + if post.RootId != "" { + // the original post was a reply + rootID = post.RootId + } + + reply := &model.Post{ + Message: permalinkReplyMessage, + ChannelId: post.ChannelId, + RootId: rootID, + UserId: c.UserID, + } + + if _, appErr := p.API.CreatePost(reply); appErr != nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create the notification post %s", post.Id), StatusCode: http.StatusInternalServerError}) + return + } + if status == constants.IssueClose { + post.Props[constants.IssueStatus] = constants.Reopen + } else { + post.Props[constants.IssueStatus] = constants.Close + } + if _, appErr := p.API.UpdatePost(post); appErr != nil { + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to update the post %s", post.Id), StatusCode: http.StatusInternalServerError}) + } + p.writeJSON(w, issue) +} diff --git a/server/plugin/webhook.go b/server/plugin/webhook.go index 39d967c82..ed9196556 100644 --- a/server/plugin/webhook.go +++ b/server/plugin/webhook.go @@ -6,30 +6,19 @@ import ( "crypto/sha1" //nolint:gosec // GitHub webhooks are signed using sha1 https://developer.github.com/webhooks/. "encoding/hex" "encoding/json" + "fmt" "io" "net/http" "strings" "sync" "time" - "github.com/google/go-github/v41/github" + "github.com/google/go-github/v48/github" + "github.com/mattermost/mattermost-plugin-github/server/constants" "github.com/mattermost/mattermost-server/v6/model" "github.com/microcosm-cc/bluemonday" ) -const ( - actionOpened = "opened" - actionClosed = "closed" - actionReopened = "reopened" - actionSubmitted = "submitted" - actionLabeled = "labeled" - actionAssigned = "assigned" - - actionCreated = "created" - actionDeleted = "deleted" - actionEdited = "edited" -) - // RenderConfig holds various configuration options to be used in a template // for redering an event. type RenderConfig struct { @@ -343,15 +332,15 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { } action := event.GetAction() - if action != actionOpened && action != actionLabeled && action != actionClosed { + if action != constants.ActionOpened && action != constants.ActionLabeled && action != constants.ActionClosed { return } pr := event.GetPullRequest() eventLabel := event.GetLabel().GetName() labels := make([]string, len(pr.Labels)) - for i, v := range pr.Labels { - labels[i] = v.GetName() + for index, label := range pr.Labels { + labels[index] = label.GetName() } closedPRMessage, err := renderTemplate("closedPR", event) @@ -370,7 +359,7 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { continue } - if sub.PullsMerged() && action != actionClosed { + if sub.PullsMerged() && action != constants.ActionClosed { continue } @@ -391,7 +380,7 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { continue } - if action == actionLabeled { + if action == constants.ActionLabeled { if label != "" && label == eventLabel { pullRequestLabelledMessage, err := renderTemplate("pullRequestLabelled", event) if err != nil { @@ -405,7 +394,7 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { } } - if action == actionOpened { + if action == constants.ActionOpened { newPRMessage, err := renderTemplate("newPR", GetEventWithRenderConfig(event, sub)) if err != nil { p.API.LogWarn("Failed to render template", "error", err.Error()) @@ -415,7 +404,7 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { post.Message = p.sanitizeDescription(newPRMessage) } - if action == actionClosed { + if action == constants.ActionClosed { post.Message = closedPRMessage } @@ -434,7 +423,7 @@ func (p *Plugin) sanitizeDescription(description string) string { func (p *Plugin) handlePRDescriptionMentionNotification(event *github.PullRequestEvent) { action := event.GetAction() - if action != actionOpened { + if action != constants.ActionOpened { return } @@ -496,7 +485,7 @@ func (p *Plugin) postIssueEvent(event *github.IssuesEvent) { // This condition is made to check if the message doesn't get automatically labeled to prevent duplicated issue messages timeDiff := time.Until(issue.GetCreatedAt()) * -1 - if action == actionLabeled && timeDiff.Seconds() < 4.00 { + if action == constants.ActionLabeled && timeDiff.Seconds() < 4.00 { return } @@ -507,16 +496,16 @@ func (p *Plugin) postIssueEvent(event *github.IssuesEvent) { issueTemplate := "" switch action { - case actionOpened: + case constants.ActionOpened: issueTemplate = "newIssue" - case actionClosed: + case constants.ActionClosed: issueTemplate = "closedIssue" - case actionReopened: + case constants.ActionReopened: issueTemplate = "reopenedIssue" - case actionLabeled: + case constants.ActionLabeled: issueTemplate = "issueLabelled" default: @@ -525,8 +514,8 @@ func (p *Plugin) postIssueEvent(event *github.IssuesEvent) { eventLabel := event.GetLabel().GetName() labels := make([]string, len(issue.Labels)) - for i, v := range issue.Labels { - labels[i] = v.GetName() + for index, label := range issue.Labels { + labels[index] = label.GetName() } for _, sub := range subscribedChannels { @@ -534,7 +523,7 @@ func (p *Plugin) postIssueEvent(event *github.IssuesEvent) { continue } - if sub.IssueCreations() && action != actionOpened { + if sub.IssueCreations() && action != constants.ActionOpened { continue } @@ -549,12 +538,35 @@ func (p *Plugin) postIssueEvent(event *github.IssuesEvent) { } renderedMessage = p.sanitizeDescription(renderedMessage) + assignees := make([]string, len(issue.Assignees)) + for index, user := range issue.Assignees { + assignees[index] = user.GetLogin() + } + description := "" + if issue.Body != nil { + description = *issue.Body + } + post := &model.Post{ UserId: p.BotUserID, - Type: "custom_git_issue", Message: renderedMessage, } + if action == constants.ActionOpened { + post.Type = "custom_git_issue" + post.Props = map[string]interface{}{ + constants.TitleForProps: *issue.Title, + constants.IssueUrlForProps: *issue.HTMLURL, + constants.IssueNumberForProps: *issue.Number, + constants.DescriptionForProps: description, + constants.AssigneesForProps: assignees, + constants.LabelsForProps: labels, + constants.RepoOwnerForProps: *repo.Owner.Login, + constants.RepoNameForProps: *repo.Name, + constants.IssueStatus: constants.Close, + } + } + label := sub.Label() contained := false @@ -568,7 +580,7 @@ func (p *Plugin) postIssueEvent(event *github.IssuesEvent) { continue } - if action == actionLabeled { + if action == constants.ActionLabeled { if label == "" || label != eventLabel { continue } @@ -716,7 +728,7 @@ func (p *Plugin) postIssueCommentEvent(event *github.IssueCommentEvent) { return } - if event.GetAction() != actionCreated { + if event.GetAction() != constants.ActionCreated { return } @@ -732,8 +744,8 @@ func (p *Plugin) postIssueCommentEvent(event *github.IssueCommentEvent) { } labels := make([]string, len(event.GetIssue().Labels)) - for i, v := range event.GetIssue().Labels { - labels[i] = v.GetName() + for index, label := range event.GetIssue().Labels { + labels[index] = label.GetName() } for _, sub := range subs { @@ -758,7 +770,7 @@ func (p *Plugin) postIssueCommentEvent(event *github.IssueCommentEvent) { continue } - if event.GetAction() == actionCreated { + if event.GetAction() == constants.ActionCreated { post.Message = message } @@ -771,7 +783,7 @@ func (p *Plugin) postIssueCommentEvent(event *github.IssueCommentEvent) { } func (p *Plugin) senderMutedByReceiver(userID string, sender string) bool { - mutedUsernameBytes, _ := p.API.KVGet(userID + "-muted-users") + mutedUsernameBytes, _ := p.API.KVGet(fmt.Sprintf("%s-muted-users", userID)) mutedUsernames := string(mutedUsernameBytes) return strings.Contains(mutedUsernames, sender) } @@ -785,7 +797,7 @@ func (p *Plugin) postPullRequestReviewEvent(event *github.PullRequestReviewEvent } action := event.GetAction() - if action != actionSubmitted { + if action != constants.ActionSubmitted { return } @@ -811,8 +823,8 @@ func (p *Plugin) postPullRequestReviewEvent(event *github.PullRequestReviewEvent } labels := make([]string, len(event.GetPullRequest().Labels)) - for i, v := range event.GetPullRequest().Labels { - labels[i] = v.GetName() + for index, label := range event.GetPullRequest().Labels { + labels[index] = label.GetName() } for _, sub := range subs { @@ -865,8 +877,8 @@ func (p *Plugin) postPullRequestReviewCommentEvent(event *github.PullRequestRevi } labels := make([]string, len(event.GetPullRequest().Labels)) - for i, v := range event.GetPullRequest().Labels { - labels[i] = v.GetName() + for index, label := range event.GetPullRequest().Labels { + labels[index] = label.GetName() } for _, sub := range subs { @@ -900,7 +912,7 @@ func (p *Plugin) postPullRequestReviewCommentEvent(event *github.PullRequestRevi func (p *Plugin) handleCommentMentionNotification(event *github.IssueCommentEvent) { action := event.GetAction() - if action == actionEdited || action == actionDeleted { + if action == constants.ActionEdited || action == constants.ActionDeleted { return } @@ -966,7 +978,7 @@ func (p *Plugin) handleCommentAuthorNotification(event *github.IssueCommentEvent } action := event.GetAction() - if action == actionEdited || action == actionDeleted { + if action == constants.ActionEdited || action == constants.ActionDeleted { return } @@ -1088,7 +1100,7 @@ func (p *Plugin) handlePullRequestNotification(event *github.PullRequestEvent) { if isPrivate && !p.permissionToRepo(requestedUserID, repoName) { requestedUserID = "" } - case actionClosed: + case constants.ActionClosed: if author == sender { return } @@ -1096,7 +1108,7 @@ func (p *Plugin) handlePullRequestNotification(event *github.PullRequestEvent) { if isPrivate && !p.permissionToRepo(authorUserID, repoName) { authorUserID = "" } - case actionReopened: + case constants.ActionReopened: if author == sender { return } @@ -1104,7 +1116,7 @@ func (p *Plugin) handlePullRequestNotification(event *github.PullRequestEvent) { if isPrivate && !p.permissionToRepo(authorUserID, repoName) { authorUserID = "" } - case actionAssigned: + case constants.ActionAssigned: assignee := event.GetPullRequest().GetAssignee().GetLogin() if assignee == sender { return @@ -1146,17 +1158,17 @@ func (p *Plugin) handleIssueNotification(event *github.IssuesEvent) { assigneeUserID := "" switch event.GetAction() { - case actionClosed: + case constants.ActionClosed: authorUserID = p.getGitHubToUserIDMapping(author) if isPrivate && !p.permissionToRepo(authorUserID, repoName) { authorUserID = "" } - case actionReopened: + case constants.ActionReopened: authorUserID = p.getGitHubToUserIDMapping(author) if isPrivate && !p.permissionToRepo(authorUserID, repoName) { authorUserID = "" } - case actionAssigned: + case constants.ActionAssigned: assignee := event.GetAssignee().GetLogin() if assignee == sender { return @@ -1197,7 +1209,7 @@ func (p *Plugin) handlePullRequestReviewNotification(event *github.PullRequestRe return } - if event.GetAction() != actionSubmitted { + if event.GetAction() != constants.ActionSubmitted { return } diff --git a/server/serializer/error.go b/server/serializer/error.go new file mode 100644 index 000000000..1e2974f51 --- /dev/null +++ b/server/serializer/error.go @@ -0,0 +1,11 @@ +package serializer + +type APIErrorResponse struct { + ID string `json:"id"` + Message string `json:"message"` + StatusCode int `json:"status_code"` +} + +func (e *APIErrorResponse) Error() string { + return e.Message +} diff --git a/server/serializer/issue.go b/server/serializer/issue.go new file mode 100644 index 000000000..e21432423 --- /dev/null +++ b/server/serializer/issue.go @@ -0,0 +1,52 @@ +package serializer + +type CreateIssueRequest struct { + Title string `json:"title"` + Body string `json:"body"` + Repo string `json:"repo"` + PostID string `json:"post_id"` + ChannelID string `json:"channel_id"` + Labels []string `json:"labels"` + Assignees []string `json:"assignees"` + Milestone int `json:"milestone"` +} + +type CreateIssueCommentRequest struct { + PostID string `json:"post_id"` + Owner string `json:"owner"` + Repo string `json:"repo"` + Number int `json:"number"` + Comment string `json:"comment"` + ShowAttachedMessage bool `json:"show_attached_message"` +} + +type UpdateIssueRequest struct { + Title string `json:"title"` + Body string `json:"body"` + Repo string `json:"repo"` + PostID string `json:"post_id"` + ChannelID string `json:"channel_id"` + Labels []string `json:"labels"` + Assignees []string `json:"assignees"` + Milestone int `json:"milestone"` + IssueNumber int `json:"issue_number"` +} + +type CommentAndCloseRequest struct { + ChannelID string `json:"channel_id"` + IssueComment string `json:"issue_comment"` + StatusReason string `json:"status_reason"` + Number int `json:"number"` + Owner string `json:"owner"` + Repository string `json:"repo"` + Status string `json:"status"` + PostID string `json:"postId"` +} + +type OpenCreateCommentOrEditIssueModalRequestBody struct { + RepoOwner string `json:"repo_owner"` + RepoName string `json:"repo_name"` + IssueNumber int `json:"issue_number"` + PostID string `json:"postId"` + Status string `json:"status"` +} diff --git a/server/serializer/notification.go b/server/serializer/notification.go new file mode 100644 index 000000000..236e6da56 --- /dev/null +++ b/server/serializer/notification.go @@ -0,0 +1,8 @@ +package serializer + +import "github.com/google/go-github/v48/github" + +type FilteredNotification struct { + github.Notification + HTMLUrl string `json:"html_url"` +} diff --git a/server/serializer/pr.go b/server/serializer/pr.go new file mode 100644 index 000000000..01f9f0c37 --- /dev/null +++ b/server/serializer/pr.go @@ -0,0 +1,12 @@ +package serializer + +import "github.com/google/go-github/v48/github" + +type PRDetails struct { + URL string `json:"url"` + Number int `json:"number"` + Status string `json:"status"` + Mergeable bool `json:"mergeable"` + RequestedReviewers []*string `json:"requestedReviewers"` + Reviews []*github.PullRequestReview `json:"reviews"` +} diff --git a/server/serializer/repo.go b/server/serializer/repo.go new file mode 100644 index 000000000..a7d3eaf7e --- /dev/null +++ b/server/serializer/repo.go @@ -0,0 +1,8 @@ +package serializer + +// Only send down fields to the client that are needed +type RepositoryResponse struct { + Name string `json:"name,omitempty"` + FullName string `json:"full_name,omitempty"` + Permissions map[string]bool `json:"permissions,omitempty"` +} diff --git a/server/serializer/user.go b/server/serializer/user.go new file mode 100644 index 000000000..9c1342e4a --- /dev/null +++ b/server/serializer/user.go @@ -0,0 +1,62 @@ +package serializer + +import ( + "context" + + "github.com/mattermost/mattermost-plugin-api/experimental/bot/logger" + "golang.org/x/oauth2" +) + +type Context struct { + Ctx context.Context + UserID string + Log logger.Logger +} + +type GitHubUserRequest struct { + UserID string `json:"user_id"` +} + +type GitHubUserResponse struct { + Username string `json:"username"` +} + +type ConnectedResponse struct { + Connected bool `json:"connected"` + GitHubUsername string `json:"github_username"` + GitHubClientID string `json:"github_client_id"` + EnterpriseBaseURL string `json:"enterprise_base_url,omitempty"` + Organization string `json:"organization"` + UserSettings *UserSettings `json:"user_settings"` + ClientConfiguration map[string]interface{} `json:"configuration"` +} + +type UserSettings struct { + SidebarButtons string `json:"sidebar_buttons"` + DailyReminder bool `json:"daily_reminder"` + DailyReminderOnChange bool `json:"daily_reminder_on_change"` + Notifications bool `json:"notifications"` +} + +type GitHubUserInfo struct { + UserID string + Token *oauth2.Token + GitHubUsername string + LastToDoPostAt int64 + Settings *UserSettings + AllowedPrivateRepos bool + + // MM34646ResetTokenDone is set for a user whose token has been reset for MM-34646. + MM34646ResetTokenDone bool +} + +type UserContext struct { + Context + GHInfo *GitHubUserInfo +} + +type OAuthState struct { + UserID string `json:"user_id"` + Token string `json:"token"` + PrivateAllowed bool `json:"private_allowed"` +} diff --git a/webapp/src/action_types/index.js b/webapp/src/action_types/index.js index 88da0997b..f46990850 100644 --- a/webapp/src/action_types/index.js +++ b/webapp/src/action_types/index.js @@ -17,9 +17,11 @@ export default { RECEIVED_GITHUB_USER: pluginId + '_received_github_user', RECEIVED_SHOW_RHS_ACTION: pluginId + '_received_rhs_action', UPDATE_RHS_STATE: pluginId + '_update_rhs_state', - CLOSE_CREATE_ISSUE_MODAL: pluginId + '_close_create_modal', - OPEN_CREATE_ISSUE_MODAL: pluginId + '_open_create_modal', - OPEN_CREATE_ISSUE_MODAL_WITHOUT_POST: pluginId + '_open_create_modal_without_post', + CLOSE_CREATE_OR_UPDATE_ISSUE_MODAL: pluginId + '_close_create_or_update_issue_modal', + CLOSE_CLOSE_OR_REOPEN_ISSUE_MODAL: pluginId + '_close_close_or_reopen_issue_modal', + OPEN_CREATE_ISSUE_MODAL_WITH_POST: pluginId + '_open_create_issue_modal_with_post', + OPEN_CLOSE_OR_REOPEN_ISSUE_MODAL: pluginId + '_open_close_or_reopen_issue_modal', + OPEN_CREATE_OR_UPDATE_ISSUE_MODAL: pluginId + '_open_create_or_update_issue_modal', CLOSE_ATTACH_COMMENT_TO_ISSUE_MODAL: pluginId + '_close_attach_modal', OPEN_ATTACH_COMMENT_TO_ISSUE_MODAL: pluginId + '_open_attach_modal', RECEIVED_ATTACH_COMMENT_RESULT: pluginId + '_received_attach_comment', diff --git a/webapp/src/actions/index.js b/webapp/src/actions/index.js index 1788ae959..17935a1e9 100644 --- a/webapp/src/actions/index.js +++ b/webapp/src/actions/index.js @@ -211,6 +211,60 @@ export function getMilestoneOptions(repo) { }; } +export function attachCommentIssueModal(payload) { + return async (dispatch, getState) => { + let data; + try { + data = await Client.attachCommentIssueModal(payload); + } catch (error) { + return {error}; + } + + const connected = await checkAndHandleNotConnected(data)(dispatch, getState); + if (!connected) { + return {error: data}; + } + + return {data}; + }; +} + +export function editIssueModal(payload) { + return async (dispatch, getState) => { + let data; + try { + data = await Client.editIssueModal(payload); + } catch (error) { + return {error}; + } + + const connected = await checkAndHandleNotConnected(data)(dispatch, getState); + if (!connected) { + return {error: data}; + } + + return {data}; + }; +} + +export function closeOrReopenIssueModal(payload) { + return async (dispatch, getState) => { + let data; + try { + data = await Client.closeOrReopenIssueModal(payload); + } catch (error) { + return {error}; + } + + const connected = await checkAndHandleNotConnected(data)(dispatch, getState); + if (!connected) { + return {error: data}; + } + + return {data}; + }; +} + export function getYourAssignments() { return async (dispatch, getState) => { let data; @@ -339,28 +393,51 @@ export function updateRhsState(rhsState) { }; } -export function openCreateIssueModal(postId) { +export function openCreateIssueModalWithPost(postId) { return { - type: ActionTypes.OPEN_CREATE_ISSUE_MODAL, + type: ActionTypes.OPEN_CREATE_ISSUE_MODAL_WITH_POST, data: { postId, }, }; } -export function openCreateIssueModalWithoutPost(title, channelId) { +export function openCreateOrUpdateIssueModal(messageData) { return { - type: ActionTypes.OPEN_CREATE_ISSUE_MODAL_WITHOUT_POST, + type: ActionTypes.OPEN_CREATE_OR_UPDATE_ISSUE_MODAL, data: { - title, - channelId, + messageData, }, }; } -export function closeCreateIssueModal() { +export function openCloseOrReopenIssueModal(messageData) { return { - type: ActionTypes.CLOSE_CREATE_ISSUE_MODAL, + type: ActionTypes.OPEN_CLOSE_OR_REOPEN_ISSUE_MODAL, + data: { + messageData, + }, + }; +} + +export function openCreateCommentOnIssueModal(messageData) { + return { + type: ActionTypes.OPEN_ATTACH_COMMENT_TO_ISSUE_MODAL, + data: { + messageData, + }, + }; +} + +export function closeCreateOrUpdateIssueModal() { + return { + type: ActionTypes.CLOSE_CREATE_OR_UPDATE_ISSUE_MODAL, + }; +} + +export function closeCloseOrReOpenIssueModal() { + return { + type: ActionTypes.CLOSE_CLOSE_OR_REOPEN_ISSUE_MODAL, }; } @@ -382,6 +459,42 @@ export function createIssue(payload) { }; } +export function closeOrReopenIssue(payload) { + return async (dispatch) => { + let data; + try { + data = await Client.closeOrReopenIssue(payload); + } catch (error) { + return {error}; + } + + const connected = await dispatch(checkAndHandleNotConnected(data)); + if (!connected) { + return {error: data}; + } + + return {data}; + }; +} + +export function updateIssue(payload) { + return async (dispatch) => { + let data; + try { + data = await Client.updateIssue(payload); + } catch (error) { + return {error}; + } + + const connected = await dispatch(checkAndHandleNotConnected(data)); + if (!connected) { + return {error: data}; + } + + return {data}; + }; +} + export function openAttachCommentToIssueModal(postId) { return { type: ActionTypes.OPEN_ATTACH_COMMENT_TO_ISSUE_MODAL, diff --git a/webapp/src/client/client.js b/webapp/src/client/client.js index 25d6f3f9c..5ebcdb510 100644 --- a/webapp/src/client/client.js +++ b/webapp/src/client/client.js @@ -7,6 +7,18 @@ import {ClientError} from 'mattermost-redux/client/client4'; import {id as pluginId} from '../manifest'; export default class Client { + editIssueModal = async (payload) => { + return this.doPost(`${this.url}/editissuemodal`, payload); + } + + closeOrReopenIssueModal = async (payload) => { + return this.doPost(`${this.url}/closereopenissuemodal`, payload); + } + + attachCommentIssueModal = async (payload) => { + return this.doPost(`${this.url}/attachcommentissuemodal`, payload); + } + setServerRoute(url) { this.url = url + `/plugins/${pluginId}/api/v1`; } @@ -63,6 +75,14 @@ export default class Client { return this.doPost(`${this.url}/createissue`, payload); } + closeOrReopenIssue = async (payload) => { + return this.doPost(`${this.url}/closeorreopenissue`, payload); + } + + updateIssue = async (payload) => { + return this.doPost(`${this.url}/updateissue`, payload); + } + searchIssues = async (searchTerm) => { return this.doGet(`${this.url}/searchissues?term=${searchTerm}`); } diff --git a/webapp/src/components/github_issue/index.tsx b/webapp/src/components/github_issue/index.tsx new file mode 100644 index 000000000..9edeee267 --- /dev/null +++ b/webapp/src/components/github_issue/index.tsx @@ -0,0 +1,118 @@ +import * as React from 'react'; +import {makeStyleFromTheme} from 'mattermost-redux/utils/theme_utils'; +import {Theme} from 'mattermost-redux/types/preferences'; +import {Post} from 'mattermost-redux/types/posts'; +import {useDispatch} from 'react-redux'; + +import {attachCommentIssueModal, editIssueModal, closeOrReopenIssueModal} from '../../actions'; + +type GithubIssueProps = { + theme: Theme, + post: Post, +} + +const GithubIssue = ({theme, post}: GithubIssueProps) => { + const style = getStyle(theme); + const postProps = post.props || {}; + let assignees; + let labels; + const dispatch = useDispatch(); + + const issue = { + repo_owner: postProps.repo_owner, + repo_name: postProps.repo_name, + issue_number: postProps.issue_number, + postId: post.id, + status: postProps.status, + }; + + const content = ( +
+ + + +
+ ); + + if (postProps.assignees?.length) { + assignees = ( +
+ {'Assignees'} +
+ {postProps.assignees.map((assignee: string, index: number) => ( + {(index ? ', ' : '') + assignee} + ))} +
+
+ ); + } + + if (postProps.labels?.length) { + labels = ( +
+ {'Labels'} +
+ {postProps.labels.map((label: string, index: number) => ( + {(index ? ', ' : '') + label} + ))} +
+
+ ); + } + + return ( +
+
+ + {'#' + postProps.issue_number + ' ' + postProps.title} + +
+

{postProps.description}

+ {assignees} + {labels} + {content} +
+ ); +}; + +const getStyle = makeStyleFromTheme((theme) => ({ + button: { + fontFamily: 'Open Sans', + fontSize: '12px', + fontWeight: 'bold', + letterSpacing: '1px', + lineHeight: '19px', + margin: '12px 12px 8px 0px', + borderRadius: '4px', + color: theme.buttonColor, + }, + close_or_reopen_button: { + backgroundColor: theme.errorTextColor, + }, + other_buttons: { + backgroundColor: theme.buttonBg, + }, + assignees_and_labels: { + display: 'inline-block', + verticalAlign: 'top', + width: '30%', + }, +})); + +export default GithubIssue; diff --git a/webapp/src/components/modals/attach_comment_to_issue/attach_comment_to_issue.jsx b/webapp/src/components/modals/attach_comment_to_issue/attach_comment_to_issue.jsx index 703adc28f..f959f884a 100644 --- a/webapp/src/components/modals/attach_comment_to_issue/attach_comment_to_issue.jsx +++ b/webapp/src/components/modals/attach_comment_to_issue/attach_comment_to_issue.jsx @@ -12,6 +12,7 @@ import GithubIssueSelector from 'components/github_issue_selector'; import {getErrorMessage} from 'utils/user_utils'; const initialState = { + comment: '', submitting: false, issueValue: null, textSearchTerms: '', @@ -25,6 +26,7 @@ export default class AttachIssueModal extends PureComponent { post: PropTypes.object, theme: PropTypes.object.isRequired, visible: PropTypes.bool.isRequired, + messageData: PropTypes.object, }; constructor(props) { @@ -38,6 +40,25 @@ export default class AttachIssueModal extends PureComponent { } if (!this.state.issueValue) { + const {owner, repo, number} = this.props.messageData ?? {}; + const issue = { + owner, + repo, + number, + comment: this.state.comment, + post_id: this.props.post.id, + show_attached_message: false, + }; + this.setState({submitting: true}); + + this.props.create(issue).then((created) => { + if (created.error) { + const errMessage = getErrorMessage(created.error.message); + this.setState({error: errMessage, submitting: false}); + return; + } + this.handleClose(e); + }); return; } @@ -53,6 +74,7 @@ export default class AttachIssueModal extends PureComponent { number, comment: this.props.post.message, post_id: this.props.post.id, + show_attached_message: true, }; this.setState({submitting: true}); @@ -68,6 +90,8 @@ export default class AttachIssueModal extends PureComponent { }); }; + handleIssueCommentChange = (comment) => this.setState({comment}); + handleClose = (e) => { if (e && e.preventDefault) { e.preventDefault(); @@ -83,15 +107,25 @@ export default class AttachIssueModal extends PureComponent { }; render() { - const {visible, theme} = this.props; - const {error, submitting} = this.state; + const {error, submitting, comment, issueValue} = this.state; + const {visible, theme, messageData, post} = this.props; const style = getStyle(theme); - if (!visible) { return null; } - const component = ( + const {number} = messageData ?? {}; + const modalTitle = number ? 'Create a comment to GitHub Issue' : 'Attach Message to GitHub Issue'; + const component = number ? ( +
+ +
+ ) : (
@@ -123,7 +157,7 @@ export default class AttachIssueModal extends PureComponent { > - {'Attach Message to GitHub Issue'} + {modalTitle}
{ - const postId = state[`plugins-${pluginId}`].attachCommentToIssueModalForPostId; - const post = getPost(state, postId); + const {postId, messageData} = state[`plugins-${pluginId}`].attachCommentToIssueModalForPostId; + const currentPostId = postId || messageData?.postId; + const post = currentPostId ? getPost(state, currentPostId) : null; return { visible: state[`plugins-${pluginId}`].attachCommentToIssueModalVisible, post, + messageData, }; }; diff --git a/webapp/src/components/modals/close_reopen_issue/index.tsx b/webapp/src/components/modals/close_reopen_issue/index.tsx new file mode 100644 index 000000000..5e793a19a --- /dev/null +++ b/webapp/src/components/modals/close_reopen_issue/index.tsx @@ -0,0 +1,181 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useState} from 'react'; +import {Modal} from 'react-bootstrap'; + +import {Theme} from 'mattermost-redux/types/preferences'; + +import {useDispatch, useSelector} from 'react-redux'; + +import {closeCloseOrReOpenIssueModal, closeOrReopenIssue} from '../../../actions'; + +import {getCloseOrReopenIssueModalData} from '../../../selectors'; + +import FormButton from '../../form_button'; +import Input from '../../input'; + +const CloseOrReopenIssueModal = ({theme}: {theme: Theme}) => { + const dispatch = useDispatch(); + const closeOrReopenIssueModalData = useSelector(getCloseOrReopenIssueModalData); + const {messageData, visible} = closeOrReopenIssueModalData; + const [statusReason, setStatusReason] = useState('completed'); + const [submitting, setSubmitting] = useState(false); + const [comment, setComment] = useState(''); + if (!visible) { + return null; + } + + const handleCloseOrReopenIssue = async (e: React.SyntheticEvent) => { + if (e && e.preventDefault) { + e.preventDefault(); + } + + const currentStatus = messageData?.status === 'Close' ? statusReason : 'reopened'; + const issue = { + channel_id: messageData.channel_id, + issue_comment: comment, + status_reason: currentStatus, + repo: messageData.repo, + number: messageData.number, + owner: messageData.owner, + status: messageData.status, + postId: messageData.postId, + }; + + setSubmitting(true); + await dispatch(closeOrReopenIssue(issue)); + setSubmitting(false); + handleClose(e); + }; + + const handleClose = (e: React.SyntheticEvent) => { + if (e && e.preventDefault) { + e.preventDefault(); + } + dispatch(closeCloseOrReOpenIssueModal()); + }; + + const handleStatusChange = (e: React.ChangeEvent) => setStatusReason(e.target.value); + + const handleIssueCommentChange = (updatedComment: string) => setComment(updatedComment); + + const style = getStyle(theme); + const modalTitle = messageData.status + ' Issue'; + const savingMessage = messageData.status === 'Close' ? 'Closing' : 'Reopening'; + const status = messageData.status + ' Issue'; + const submitError = null; + + const component = (messageData.status === 'Close') ? ( +
+ +
+ + +
+ + +
+
+ ) : ( +
+ +
+ ); + + return ( + + + + {modalTitle} + + + + + {component} + + + {submitError} + + + {status} + + + + + ); +}; + +const getStyle = (theme: Theme) => ({ + modal: { + padding: '2em 2em 3em', + color: theme.centerChannelColor, + backgroundColor: theme.centerChannelBg, + }, + descriptionArea: { + height: 'auto', + width: '100%', + color: '#000', + }, + radioButtons: { + margin: '0.4em 0.6em', + }, +}); + +export default CloseOrReopenIssueModal; diff --git a/webapp/src/components/modals/create_issue/index.js b/webapp/src/components/modals/create_issue/index.js deleted file mode 100644 index cc4eef263..000000000 --- a/webapp/src/components/modals/create_issue/index.js +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {connect} from 'react-redux'; -import {bindActionCreators} from 'redux'; -import {getPost} from 'mattermost-redux/selectors/entities/posts'; - -import {id as pluginId} from 'manifest'; -import {closeCreateIssueModal, createIssue} from 'actions'; - -import CreateIssueModal from './create_issue'; - -const mapStateToProps = (state) => { - const {postId, title, channelId} = state[`plugins-${pluginId}`].createIssueModal; - const post = (postId) ? getPost(state, postId) : null; - - return { - visible: state[`plugins-${pluginId}`].isCreateIssueModalVisible, - post, - title, - channelId, - }; -}; - -const mapDispatchToProps = (dispatch) => bindActionCreators({ - close: closeCreateIssueModal, - create: createIssue, -}, dispatch); - -export default connect(mapStateToProps, mapDispatchToProps)(CreateIssueModal); diff --git a/webapp/src/components/modals/create_issue/create_issue.jsx b/webapp/src/components/modals/create_update_issue/create_update_issue.jsx similarity index 63% rename from webapp/src/components/modals/create_issue/create_issue.jsx rename to webapp/src/components/modals/create_update_issue/create_update_issue.jsx index 1f8d565f1..b341a5fd5 100644 --- a/webapp/src/components/modals/create_issue/create_issue.jsx +++ b/webapp/src/components/modals/create_update_issue/create_update_issue.jsx @@ -29,15 +29,15 @@ const initialState = { issueTitleValid: true, }; -export default class CreateIssueModal extends PureComponent { +export default class CreateOrUpdateIssueModal extends PureComponent { static propTypes = { + update: PropTypes.func.isRequired, close: PropTypes.func.isRequired, create: PropTypes.func.isRequired, post: PropTypes.object, - title: PropTypes.string, - channelId: PropTypes.string, theme: PropTypes.object.isRequired, visible: PropTypes.bool.isRequired, + messageData: PropTypes.object, }; constructor(props) { @@ -46,17 +46,34 @@ export default class CreateIssueModal extends PureComponent { this.validator = new Validator(); } + /* eslint-disable react/no-did-update-set-state*/ componentDidUpdate(prevProps) { - if (this.props.post && !prevProps.post) { - this.setState({issueDescription: this.props.post.message}); //eslint-disable-line react/no-did-update-set-state - } else if (this.props.channelId && (this.props.channelId !== prevProps.channelId || this.props.title !== prevProps.title)) { - const title = this.props.title.substring(0, MAX_TITLE_LENGTH); - this.setState({issueTitle: title}); // eslint-disable-line react/no-did-update-set-state + if (this.props.post && !this.props.messageData) { + this.setState({issueDescription: this.props.post.message}); + } + + const {channel_id, title, description, assignees, labels, milestone_title, milestone_number, repo_full_name} = this.props.messageData ?? {}; + if (channel_id && (channel_id !== prevProps.messageData?.channel_id || title !== prevProps.messageData?.title || description !== prevProps.messageData?.description || assignees !== prevProps.messageData?.assignees || labels !== prevProps.messageData?.labels || milestone_title !== prevProps.messageData?.milestone_title || milestone_number !== prevProps.messageData?.milestone_number)) { + if (assignees) { + this.setState({assignees}); + } + if (labels) { + this.setState({labels}); + } + this.setState({milestone: { + value: milestone_number, + label: milestone_title, + }, + issueDescription: description, + repo: repo_full_name, + issueTitle: title.substring(0, MAX_TITLE_LENGTH)}); } } + /* eslint-enable */ - // handle issue creation after form is populated - handleCreate = async (e) => { + // handle issue creation or updation after form is populated + handleCreateOrUpdate = async (e) => { + const {channel_id, issue_number, repo_full_name} = this.props.messageData ?? {}; if (e && e.preventDefault) { e.preventDefault(); } @@ -80,20 +97,36 @@ export default class CreateIssueModal extends PureComponent { assignees: this.state.assignees, milestone: this.state.milestone && this.state.milestone.value, post_id: postId, - channel_id: this.props.channelId, + channel_id, + issue_number, }; + if (!issue.repo) { + issue.repo = this.state.repo; + } this.setState({submitting: true}); - - const created = await this.props.create(issue); - if (created.error) { - const errMessage = getErrorMessage(created.error.message); - this.setState({ - error: errMessage, - showErrors: true, - submitting: false, - }); - return; + if (repo_full_name) { + const updated = await this.props.update(issue); + if (updated?.error) { + const errMessage = getErrorMessage(updated.error.message); + this.setState({ + error: errMessage, + showErrors: true, + submitting: false, + }); + return; + } + } else { + const created = await this.props.create(issue); + if (created.error) { + const errMessage = getErrorMessage(created.error.message); + this.setState({ + error: errMessage, + showErrors: true, + submitting: false, + }); + return; + } } this.handleClose(e); }; @@ -122,24 +155,25 @@ export default class CreateIssueModal extends PureComponent { return null; } + const repoName = this.state.repo.name ?? this.state.repo; return ( <> {requiredMsg} @@ -176,11 +212,40 @@ export default class CreateIssueModal extends PureComponent { ); } - const component = ( + const component = repo_full_name ? ( +
+ + + + {issueTitleValidationError} + {this.renderIssueAttributeSelectors()} + + +
+ ) : (
{issueTitleValidationError} @@ -204,7 +269,7 @@ export default class CreateIssueModal extends PureComponent {
@@ -221,12 +286,12 @@ export default class CreateIssueModal extends PureComponent { > - {'Create GitHub Issue'} + {modalTitle}
{ + const {postId, messageData} = state[`plugins-${pluginId}`].createOrUpdateIssueModal; + const currentPostId = postId || messageData?.postId; + const post = currentPostId ? getPost(state, currentPostId) : null; + + return { + visible: state[`plugins-${pluginId}`].isCreateOrUpdateIssueModalVisible, + post, + messageData, + }; +}; + +const mapDispatchToProps = (dispatch) => bindActionCreators({ + close: closeCreateOrUpdateIssueModal, + create: createIssue, + update: updateIssue, +}, dispatch); + +export default connect(mapStateToProps, mapDispatchToProps)(CreateOrUpdateIssueModal); diff --git a/webapp/src/components/post_menu_action/create_issue/index.js b/webapp/src/components/post_menu_action/create_issue/index.js index 238ded3bd..39c6fea7c 100644 --- a/webapp/src/components/post_menu_action/create_issue/index.js +++ b/webapp/src/components/post_menu_action/create_issue/index.js @@ -7,7 +7,7 @@ import {getPost} from 'mattermost-redux/selectors/entities/posts'; import {isSystemMessage} from 'mattermost-redux/utils/post_utils'; import {id as pluginId} from 'manifest'; -import {openCreateIssueModal} from 'actions'; +import {openCreateIssueModalWithPost} from 'actions'; import CreateIssuePostMenuAction from './create_issue'; @@ -21,7 +21,7 @@ const mapStateToProps = (state, ownProps) => { }; const mapDispatchToProps = (dispatch) => bindActionCreators({ - open: openCreateIssueModal, + open: openCreateIssueModalWithPost, }, dispatch); export default connect(mapStateToProps, mapDispatchToProps)(CreateIssuePostMenuAction); diff --git a/webapp/src/components/sidebar_buttons/sidebar_buttons.jsx b/webapp/src/components/sidebar_buttons/sidebar_buttons.jsx index 9b4c8ed9b..2b07630be 100644 --- a/webapp/src/components/sidebar_buttons/sidebar_buttons.jsx +++ b/webapp/src/components/sidebar_buttons/sidebar_buttons.jsx @@ -208,28 +208,26 @@ export default class SidebarButtons extends React.PureComponent { } } -const getStyle = makeStyleFromTheme((theme) => { - return { - buttonTeam: { - color: changeOpacity(theme.sidebarText, 0.6), - display: 'block', - marginBottom: '10px', - width: '100%', - }, - buttonHeader: { - color: changeOpacity(theme.sidebarText, 0.6), - textAlign: 'center', - cursor: 'pointer', - }, - containerHeader: { - marginTop: '10px', - marginBottom: '5px', - display: 'flex', - alignItems: 'center', - justifyContent: 'space-around', - padding: '0 10px', - }, - containerTeam: { - }, - }; -}); +const getStyle = makeStyleFromTheme((theme) => ({ + buttonTeam: { + color: changeOpacity(theme.sidebarText, 0.6), + display: 'block', + marginBottom: '10px', + width: '100%', + }, + buttonHeader: { + color: changeOpacity(theme.sidebarText, 0.6), + textAlign: 'center', + cursor: 'pointer', + }, + containerHeader: { + marginTop: '10px', + marginBottom: '5px', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-around', + padding: '0 10px', + }, + containerTeam: { + }, +})); diff --git a/webapp/src/components/sidebar_right/github_items.tsx b/webapp/src/components/sidebar_right/github_items.tsx index 0fe8de697..418d621a1 100644 --- a/webapp/src/components/sidebar_right/github_items.tsx +++ b/webapp/src/components/sidebar_right/github_items.tsx @@ -255,58 +255,56 @@ function GithubItems(props: GithubItemsProps) { }) :
{'You have no active items'}
; } -const getStyle = makeStyleFromTheme((theme) => { - return { - container: { - padding: '15px', - borderTop: `1px solid ${changeOpacity(theme.centerChannelColor, 0.2)}`, - }, - itemTitle: { - color: theme.centerChannelColor, - lineHeight: 1.7, - fontWeight: 'bold', - }, - subtitle: { - margin: '5px 0 0 0', - fontSize: '13px', - }, - subtitleSecondLine: { - fontSize: '13px', - }, - icon: { - top: 3, - position: 'relative', - left: 3, - height: 18, - display: 'inline-flex', - alignItems: 'center', - marginRight: '6px', - }, - iconSucess: { - color: theme.onlineIndicator, - }, - iconPending: { - color: theme.awayIndicator, - }, - iconFailed: { - color: theme.dndIndicator, - }, - iconChangesRequested: { - color: theme.dndIndicator, - }, - conflictIcon: { - color: theme.dndIndicator, - }, - milestoneIcon: { - top: 3, - position: 'relative', - height: 18, - display: 'inline-flex', - alignItems: 'center', - color: theme.centerChannelColor, - }, - }; -}); +const getStyle = makeStyleFromTheme((theme) => ({ + container: { + padding: '15px', + borderTop: `1px solid ${changeOpacity(theme.centerChannelColor, 0.2)}`, + }, + itemTitle: { + color: theme.centerChannelColor, + lineHeight: 1.7, + fontWeight: 'bold', + }, + subtitle: { + marginTop: '5px', + fontSize: '13px', + }, + subtitleSecondLine: { + fontSize: '13px', + }, + icon: { + top: 3, + position: 'relative', + left: 3, + height: 18, + display: 'inline-flex', + alignItems: 'center', + marginRight: '6px', + }, + iconSucess: { + color: theme.onlineIndicator, + }, + iconPending: { + color: theme.awayIndicator, + }, + iconFailed: { + color: theme.dndIndicator, + }, + iconChangesRequested: { + color: theme.dndIndicator, + }, + conflictIcon: { + color: theme.dndIndicator, + }, + milestoneIcon: { + top: 3, + position: 'relative', + height: 18, + display: 'inline-flex', + alignItems: 'center', + color: theme.centerChannelColor, + }, +})); function getGithubLabels(labels: Label[]) { return labels.map((label) => { diff --git a/webapp/src/index.js b/webapp/src/index.js index 8709de6fe..0a7a18a42 100644 --- a/webapp/src/index.js +++ b/webapp/src/index.js @@ -3,17 +3,19 @@ import AttachCommentToIssuePostMenuAction from 'components/post_menu_actions/attach_comment_to_issue'; import AttachCommentToIssueModal from 'components/modals/attach_comment_to_issue'; -import CreateIssueModal from './components/modals/create_issue'; +import CreateOrUpdateIssueModal from './components/modals/create_update_issue'; +import CloseOrReopenIssueModal from './components/modals/close_reopen_issue'; import CreateIssuePostMenuAction from './components/post_menu_action/create_issue'; import SidebarHeader from './components/sidebar_header'; import TeamSidebar from './components/team_sidebar'; import UserAttribute from './components/user_attribute'; import SidebarRight from './components/sidebar_right'; import LinkTooltip from './components/link_tooltip'; +import GithubIssue from './components/github_issue'; import Reducer from './reducers'; import Client from './client'; import {getConnected, setShowRHSAction} from './actions'; -import {handleConnect, handleDisconnect, handleConfigurationUpdate, handleOpenCreateIssueModal, handleReconnect, handleRefresh} from './websocket'; +import {handleConnect, handleDisconnect, handleConfigurationUpdate, handleOpenCreateOrUpdateIssueModal, handleOpenCreateCommentOnIssueModal, handleOpenCloseOrReopenIssueModal, handleReconnect, handleRefresh} from './websocket'; import {getServerRoute} from './selectors'; import {id as pluginId} from './manifest'; @@ -31,7 +33,8 @@ class PluginClass { registry.registerLeftSidebarHeaderComponent(SidebarHeader); registry.registerBottomTeamSidebarComponent(TeamSidebar); registry.registerPopoverUserAttributesComponent(UserAttribute); - registry.registerRootComponent(CreateIssueModal); + registry.registerRootComponent(CreateOrUpdateIssueModal); + registry.registerRootComponent(CloseOrReopenIssueModal); registry.registerPostDropdownMenuComponent(CreateIssuePostMenuAction); registry.registerRootComponent(AttachCommentToIssueModal); registry.registerPostDropdownMenuComponent(AttachCommentToIssuePostMenuAction); @@ -44,7 +47,11 @@ class PluginClass { registry.registerWebSocketEventHandler(`custom_${pluginId}_disconnect`, handleDisconnect(store)); registry.registerWebSocketEventHandler(`custom_${pluginId}_config_update`, handleConfigurationUpdate(store)); registry.registerWebSocketEventHandler(`custom_${pluginId}_refresh`, handleRefresh(store)); - registry.registerWebSocketEventHandler(`custom_${pluginId}_createIssue`, handleOpenCreateIssueModal(store)); + registry.registerWebSocketEventHandler(`custom_${pluginId}_createOrUpdateIssue`, handleOpenCreateOrUpdateIssueModal(store)); + registry.registerWebSocketEventHandler(`custom_${pluginId}_attachCommentToIssue`, handleOpenCreateCommentOnIssueModal(store)); + registry.registerWebSocketEventHandler(`custom_${pluginId}_closeOrReopenIssue`, handleOpenCloseOrReopenIssueModal(store)); + registry.registerPostTypeComponent('custom_git_issue', GithubIssue); + registry.registerReconnectHandler(handleReconnect(store)); activityFunc = () => { diff --git a/webapp/src/reducers/index.js b/webapp/src/reducers/index.js index 0ebcf5192..35154c3c9 100644 --- a/webapp/src/reducers/index.js +++ b/webapp/src/reducers/index.js @@ -179,12 +179,23 @@ function rhsState(state = null, action) { } } -const isCreateIssueModalVisible = (state = false, action) => { +const isCreateOrUpdateIssueModalVisible = (state = false, action) => { switch (action.type) { - case ActionTypes.OPEN_CREATE_ISSUE_MODAL: - case ActionTypes.OPEN_CREATE_ISSUE_MODAL_WITHOUT_POST: + case ActionTypes.OPEN_CREATE_ISSUE_MODAL_WITH_POST: + case ActionTypes.OPEN_CREATE_OR_UPDATE_ISSUE_MODAL: return true; - case ActionTypes.CLOSE_CREATE_ISSUE_MODAL: + case ActionTypes.CLOSE_CREATE_OR_UPDATE_ISSUE_MODAL: + return false; + default: + return state; + } +}; + +const isCloseOrReopenIssueModalVisible = (state = false, action) => { + switch (action.type) { + case ActionTypes.OPEN_CLOSE_OR_REOPEN_ISSUE_MODAL: + return true; + case ActionTypes.CLOSE_CLOSE_OR_REOPEN_ISSUE_MODAL: return false; default: return state; @@ -202,17 +213,30 @@ const attachCommentToIssueModalVisible = (state = false, action) => { } }; -const createIssueModal = (state = '', action) => { +const createOrUpdateIssueModal = (state = '', action) => { switch (action.type) { - case ActionTypes.OPEN_CREATE_ISSUE_MODAL: - case ActionTypes.OPEN_CREATE_ISSUE_MODAL_WITHOUT_POST: + case ActionTypes.OPEN_CREATE_ISSUE_MODAL_WITH_POST: + case ActionTypes.OPEN_CREATE_OR_UPDATE_ISSUE_MODAL: return { ...state, postId: action.data.postId, - title: action.data.title, - channelId: action.data.channelId, + messageData: action.data.messageData, + }; + case ActionTypes.CLOSE_CREATE_OR_UPDATE_ISSUE_MODAL: + return {}; + default: + return state; + } +}; + +const closeOrReopenIssueModal = (state = '', action) => { + switch (action.type) { + case ActionTypes.OPEN_CLOSE_OR_REOPEN_ISSUE_MODAL: + return { + ...state, + messageData: action.data.messageData, }; - case ActionTypes.CLOSE_CREATE_ISSUE_MODAL: + case ActionTypes.CLOSE_CLOSE_OR_REOPEN_ISSUE_MODAL: return {}; default: return state; @@ -222,7 +246,11 @@ const createIssueModal = (state = '', action) => { const attachCommentToIssueModalForPostId = (state = '', action) => { switch (action.type) { case ActionTypes.OPEN_ATTACH_COMMENT_TO_ISSUE_MODAL: - return action.data.postId; + return { + ...state, + postId: action.data.postId, + messageData: action.data.messageData, + }; case ActionTypes.CLOSE_ATTACH_COMMENT_TO_ISSUE_MODAL: return ''; default: @@ -249,8 +277,10 @@ export default combineReducers({ githubUsers, rhsPluginAction, rhsState, - isCreateIssueModalVisible, - createIssueModal, + isCreateOrUpdateIssueModalVisible, + isCloseOrReopenIssueModalVisible, + createOrUpdateIssueModal, + closeOrReopenIssueModal, attachCommentToIssueModalVisible, attachCommentToIssueModalForPostId, }); diff --git a/webapp/src/selectors.js b/webapp/src/selectors.js index a8d3f26de..35a39fce3 100644 --- a/webapp/src/selectors.js +++ b/webapp/src/selectors.js @@ -1,3 +1,5 @@ +import {createSelector} from 'reselect'; + import {getConfig} from 'mattermost-redux/selectors/entities/general'; import {id as pluginId} from './manifest'; @@ -19,4 +21,15 @@ export const getServerRoute = (state) => { return basePath; }; +export const getCloseOrReopenIssueModalData = createSelector( + getPluginState, + (pluginState) => { + const {messageData} = pluginState.closeOrReopenIssueModal; + return { + visible: pluginState.isCloseOrReopenIssueModalVisible, + messageData, + }; + }, +); + export const configuration = (state) => getPluginState(state).configuration; diff --git a/webapp/src/websocket/index.js b/webapp/src/websocket/index.js index 15021af57..88690dc55 100644 --- a/webapp/src/websocket/index.js +++ b/webapp/src/websocket/index.js @@ -9,7 +9,9 @@ import { getUnreads, getYourAssignments, getYourPrs, - openCreateIssueModalWithoutPost, + openCreateOrUpdateIssueModal, + openCloseOrReopenIssueModal, + openCreateCommentOnIssueModal, } from '../actions'; import {id as pluginId} from '../manifest'; @@ -85,11 +87,29 @@ export function handleRefresh(store) { }; } -export function handleOpenCreateIssueModal(store) { +export function handleOpenCreateOrUpdateIssueModal(store) { return (msg) => { if (!msg.data) { return; } - store.dispatch(openCreateIssueModalWithoutPost(msg.data.title, msg.data.channel_id)); + store.dispatch(openCreateOrUpdateIssueModal(msg.data)); + }; +} + +export function handleOpenCloseOrReopenIssueModal(store) { + return (msg) => { + if (!msg.data) { + return; + } + store.dispatch(openCloseOrReopenIssueModal(msg.data)); + }; +} + +export function handleOpenCreateCommentOnIssueModal(store) { + return (msg) => { + if (!msg.data) { + return; + } + store.dispatch(openCreateCommentOnIssueModal(msg.data)); }; } From 70ae8851e0d13e8a1114eccb0b71a5af48b59fbd Mon Sep 17 00:00:00 2001 From: Nityanand Rai <107465508+Nityanand13@users.noreply.github.com> Date: Thu, 9 Feb 2023 17:42:03 +0530 Subject: [PATCH 02/18] [MI-2736]: Done the review fixes of a github PR #636 (#20) * [MI-2736]: Review fixes done 1. Improved code readability * [MI-2736]: Review fixes done 1. Fixed linting errors * [MI-2736]: Review fixes done 1. Fixed linting error --- go.sum | 2 -- server/constants/constants.go | 6 +++--- server/plugin/api.go | 9 +++++---- server/plugin/command.go | 3 ++- server/plugin/mm_34646_token_refresh.go | 1 + server/plugin/plugin.go | 5 +++-- server/plugin/utils.go | 5 +++-- server/plugin/webhook.go | 5 +++-- webapp/src/client/client.js | 2 +- 9 files changed, 21 insertions(+), 17 deletions(-) diff --git a/go.sum b/go.sum index 76bea24aa..372493be0 100644 --- a/go.sum +++ b/go.sum @@ -641,8 +641,6 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github/v35 v35.2.0/go.mod h1:s0515YVTI+IMrDoy9Y4pHt9ShGpzHvHO8rZ7L7acgvs= -github.com/google/go-github/v41 v41.0.0 h1:HseJrM2JFf2vfiZJ8anY2hqBjdfY1Vlj/K27ueww4gg= -github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg= github.com/google/go-github/v48 v48.2.0 h1:68puzySE6WqUY9KWmpOsDEQfDZsso98rT6pZcz9HqcE= github.com/google/go-github/v48 v48.2.0/go.mod h1:dDlehKBDo850ZPvCTK0sEqTCVWcrGl2LcDiajkYi89Y= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= diff --git a/server/constants/constants.go b/server/constants/constants.go index 6e832b9be..c2e9ab504 100644 --- a/server/constants/constants.go +++ b/server/constants/constants.go @@ -13,7 +13,7 @@ const ( OwnerQueryParam = "owner" RepoQueryParam = "repo" NumberQueryParam = "number" - PostIdQueryParam = "postId" + PostIDQueryParam = "postId" IssueStatus = "status" AssigneesForProps = "assignees" @@ -21,7 +21,7 @@ const ( DescriptionForProps = "description" TitleForProps = "title" IssueNumberForProps = "issue_number" - IssueUrlForProps = "issue_url" + IssueURLForProps = "issue_url" RepoOwnerForProps = "repo_owner" RepoNameForProps = "repo_name" @@ -33,7 +33,7 @@ const ( IssueClose = "closed" IssueOpen = "open" - //Actions of webhook events + // Actions of webhook events ActionOpened = "opened" ActionClosed = "closed" ActionReopened = "reopened" diff --git a/server/plugin/api.go b/server/plugin/api.go index 913e81e16..4ecbe06b7 100644 --- a/server/plugin/api.go +++ b/server/plugin/api.go @@ -16,10 +16,11 @@ import ( "github.com/pkg/errors" "golang.org/x/oauth2" - "github.com/mattermost/mattermost-plugin-api/experimental/bot/logger" - "github.com/mattermost/mattermost-plugin-api/experimental/flow" "github.com/mattermost/mattermost-plugin-github/server/constants" "github.com/mattermost/mattermost-plugin-github/server/serializer" + + "github.com/mattermost/mattermost-plugin-api/experimental/bot/logger" + "github.com/mattermost/mattermost-plugin-api/experimental/flow" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/plugin" ) @@ -97,7 +98,7 @@ func (p *Plugin) initializeAPI() { apiRouter.HandleFunc("/closeorreopenissue", p.checkAuth(p.attachUserContext(p.closeOrReopenIssue), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc("/updateissue", p.checkAuth(p.attachUserContext(p.updateIssue), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc("/editissuemodal", p.checkAuth(p.attachUserContext(p.openIssueEditModal), ResponseTypePlain)).Methods(http.MethodPost) - apiRouter.HandleFunc("/closereopenissuemodal", p.checkAuth(p.attachUserContext(p.openCloseOrReopenIssueModal), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc("/close_reopen_issue_modal", p.checkAuth(p.attachUserContext(p.openCloseOrReopenIssueModal), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc("/attachcommentissuemodal", p.checkAuth(p.attachUserContext(p.openAttachCommentIssueModal), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc("/createissuecomment", p.checkAuth(p.attachUserContext(p.createIssueComment), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc("/mentions", p.checkAuth(p.attachUserContext(p.getMentions), ResponseTypePlain)).Methods(http.MethodGet) @@ -1430,7 +1431,7 @@ func (p *Plugin) updateIssue(c *serializer.UserContext, w http.ResponseWriter, r return } - p.updatePost(post, issue, w) + p.updatePost(issue, w) p.writeJSON(w, result) } diff --git a/server/plugin/command.go b/server/plugin/command.go index e0e38de75..f95e925de 100644 --- a/server/plugin/command.go +++ b/server/plugin/command.go @@ -6,9 +6,10 @@ import ( "strings" "unicode" - "github.com/mattermost/mattermost-plugin-api/experimental/command" "github.com/mattermost/mattermost-plugin-github/server/constants" "github.com/mattermost/mattermost-plugin-github/server/serializer" + + "github.com/mattermost/mattermost-plugin-api/experimental/command" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/plugin" "github.com/pkg/errors" diff --git a/server/plugin/mm_34646_token_refresh.go b/server/plugin/mm_34646_token_refresh.go index ed931e710..adec22097 100644 --- a/server/plugin/mm_34646_token_refresh.go +++ b/server/plugin/mm_34646_token_refresh.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "github.com/mattermost/mattermost-plugin-api/cluster" + "github.com/mattermost/mattermost-plugin-github/server/serializer" ) diff --git a/server/plugin/plugin.go b/server/plugin/plugin.go index b059c5a7a..0414be207 100644 --- a/server/plugin/plugin.go +++ b/server/plugin/plugin.go @@ -17,13 +17,14 @@ import ( pluginapi "github.com/mattermost/mattermost-plugin-api" "github.com/mattermost/mattermost-plugin-api/experimental/bot/poster" "github.com/mattermost/mattermost-plugin-api/experimental/telemetry" - "github.com/mattermost/mattermost-plugin-github/server/constants" - "github.com/mattermost/mattermost-plugin-github/server/serializer" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/plugin" "github.com/pkg/errors" "golang.org/x/oauth2" + "github.com/mattermost/mattermost-plugin-github/server/constants" + "github.com/mattermost/mattermost-plugin-github/server/serializer" + root "github.com/mattermost/mattermost-plugin-github" ) diff --git a/server/plugin/utils.go b/server/plugin/utils.go index a7628cb4b..8b5817827 100644 --- a/server/plugin/utils.go +++ b/server/plugin/utils.go @@ -16,9 +16,10 @@ import ( "strings" "unicode" - "github.com/google/go-github/v48/github" "github.com/mattermost/mattermost-plugin-github/server/constants" "github.com/mattermost/mattermost-plugin-github/server/serializer" + + "github.com/google/go-github/v48/github" "github.com/mattermost/mattermost-server/v6/model" "github.com/pkg/errors" ) @@ -370,7 +371,7 @@ func (p *Plugin) validateIssueRequestForUpdation(issue *serializer.UpdateIssueRe return true } -func (p *Plugin) updatePost(post *model.Post, issue *serializer.UpdateIssueRequest, w http.ResponseWriter) { +func (p *Plugin) updatePost(issue *serializer.UpdateIssueRequest, w http.ResponseWriter) { post, appErr := p.API.GetPost(issue.PostID) if appErr != nil { p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) diff --git a/server/plugin/webhook.go b/server/plugin/webhook.go index ed9196556..c183d348f 100644 --- a/server/plugin/webhook.go +++ b/server/plugin/webhook.go @@ -13,8 +13,9 @@ import ( "sync" "time" - "github.com/google/go-github/v48/github" "github.com/mattermost/mattermost-plugin-github/server/constants" + + "github.com/google/go-github/v48/github" "github.com/mattermost/mattermost-server/v6/model" "github.com/microcosm-cc/bluemonday" ) @@ -556,7 +557,7 @@ func (p *Plugin) postIssueEvent(event *github.IssuesEvent) { post.Type = "custom_git_issue" post.Props = map[string]interface{}{ constants.TitleForProps: *issue.Title, - constants.IssueUrlForProps: *issue.HTMLURL, + constants.IssueURLForProps: *issue.HTMLURL, constants.IssueNumberForProps: *issue.Number, constants.DescriptionForProps: description, constants.AssigneesForProps: assignees, diff --git a/webapp/src/client/client.js b/webapp/src/client/client.js index 5ebcdb510..fb331f53c 100644 --- a/webapp/src/client/client.js +++ b/webapp/src/client/client.js @@ -12,7 +12,7 @@ export default class Client { } closeOrReopenIssueModal = async (payload) => { - return this.doPost(`${this.url}/closereopenissuemodal`, payload); + return this.doPost(`${this.url}/close_reopen_issue_modal`, payload); } attachCommentIssueModal = async (payload) => { From 3cecd5dc72cc73f0fca2c9267b69a42b081b98d3 Mon Sep 17 00:00:00 2001 From: Nityanand Rai <107465508+Nityanand13@users.noreply.github.com> Date: Thu, 16 Feb 2023 11:08:03 +0530 Subject: [PATCH 03/18] [MI-2736]: Done the review fixes of a github PR #636 (#21) * [MI-2736]: Review fixes done 1. Improved code readability * [MI-2736]: Review fixes done 1. Fixed linting errors * [MI-2736]: Review fixes done 1. Fixed linting error * [MI-2736]: Review fixes done 1. Improved code readability * [MI-2736]: Review fixes done 1. Changed the case of few endpoints to snake case --- server/plugin/api.go | 20 +++++++++---------- webapp/src/client/client.js | 20 +++++++++---------- .../create_update_issue.jsx | 13 ++++++------ 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/server/plugin/api.go b/server/plugin/api.go index 4ecbe06b7..babcfdfb8 100644 --- a/server/plugin/api.go +++ b/server/plugin/api.go @@ -90,17 +90,17 @@ func (p *Plugin) initializeAPI() { apiRouter.HandleFunc("/user", p.checkAuth(p.attachContext(p.getGitHubUser), ResponseTypeJSON)).Methods(http.MethodPost) apiRouter.HandleFunc("/todo", p.checkAuth(p.attachUserContext(p.postToDo), ResponseTypeJSON)).Methods(http.MethodPost) apiRouter.HandleFunc("/reviews", p.checkAuth(p.attachUserContext(p.getReviews), ResponseTypePlain)).Methods(http.MethodGet) - apiRouter.HandleFunc("/yourprs", p.checkAuth(p.attachUserContext(p.getYourPrs), ResponseTypePlain)).Methods(http.MethodGet) - apiRouter.HandleFunc("/prsdetails", p.checkAuth(p.attachUserContext(p.getPrsDetails), ResponseTypePlain)).Methods(http.MethodPost) - apiRouter.HandleFunc("/searchissues", p.checkAuth(p.attachUserContext(p.searchIssues), ResponseTypePlain)).Methods(http.MethodGet) - apiRouter.HandleFunc("/yourassignments", p.checkAuth(p.attachUserContext(p.getYourAssignments), ResponseTypePlain)).Methods(http.MethodGet) - apiRouter.HandleFunc("/createissue", p.checkAuth(p.attachUserContext(p.createIssue), ResponseTypePlain)).Methods(http.MethodPost) - apiRouter.HandleFunc("/closeorreopenissue", p.checkAuth(p.attachUserContext(p.closeOrReopenIssue), ResponseTypePlain)).Methods(http.MethodPost) - apiRouter.HandleFunc("/updateissue", p.checkAuth(p.attachUserContext(p.updateIssue), ResponseTypePlain)).Methods(http.MethodPost) - apiRouter.HandleFunc("/editissuemodal", p.checkAuth(p.attachUserContext(p.openIssueEditModal), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc("/your_prs", p.checkAuth(p.attachUserContext(p.getYourPrs), ResponseTypePlain)).Methods(http.MethodGet) + apiRouter.HandleFunc("/prs_details", p.checkAuth(p.attachUserContext(p.getPrsDetails), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc("/search_issues", p.checkAuth(p.attachUserContext(p.searchIssues), ResponseTypePlain)).Methods(http.MethodGet) + apiRouter.HandleFunc("/your_assignments", p.checkAuth(p.attachUserContext(p.getYourAssignments), ResponseTypePlain)).Methods(http.MethodGet) + apiRouter.HandleFunc("/create_issue", p.checkAuth(p.attachUserContext(p.createIssue), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc("/close_or_reopen_issue", p.checkAuth(p.attachUserContext(p.closeOrReopenIssue), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc("/update_issue", p.checkAuth(p.attachUserContext(p.updateIssue), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc("/edit_issue_modal", p.checkAuth(p.attachUserContext(p.openIssueEditModal), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc("/close_reopen_issue_modal", p.checkAuth(p.attachUserContext(p.openCloseOrReopenIssueModal), ResponseTypePlain)).Methods(http.MethodPost) - apiRouter.HandleFunc("/attachcommentissuemodal", p.checkAuth(p.attachUserContext(p.openAttachCommentIssueModal), ResponseTypePlain)).Methods(http.MethodPost) - apiRouter.HandleFunc("/createissuecomment", p.checkAuth(p.attachUserContext(p.createIssueComment), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc("/attach_comment_issue_modal", p.checkAuth(p.attachUserContext(p.openAttachCommentIssueModal), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc("/create_issue_comment", p.checkAuth(p.attachUserContext(p.createIssueComment), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc("/mentions", p.checkAuth(p.attachUserContext(p.getMentions), ResponseTypePlain)).Methods(http.MethodGet) apiRouter.HandleFunc("/unreads", p.checkAuth(p.attachUserContext(p.getUnreads), ResponseTypePlain)).Methods(http.MethodGet) apiRouter.HandleFunc("/labels", p.checkAuth(p.attachUserContext(p.getLabels), ResponseTypePlain)).Methods(http.MethodGet) diff --git a/webapp/src/client/client.js b/webapp/src/client/client.js index fb331f53c..9b26d88e6 100644 --- a/webapp/src/client/client.js +++ b/webapp/src/client/client.js @@ -8,7 +8,7 @@ import {id as pluginId} from '../manifest'; export default class Client { editIssueModal = async (payload) => { - return this.doPost(`${this.url}/editissuemodal`, payload); + return this.doPost(`${this.url}/edit_issue_modal`, payload); } closeOrReopenIssueModal = async (payload) => { @@ -16,7 +16,7 @@ export default class Client { } attachCommentIssueModal = async (payload) => { - return this.doPost(`${this.url}/attachcommentissuemodal`, payload); + return this.doPost(`${this.url}/attach_comment_issue_modal`, payload); } setServerRoute(url) { @@ -32,15 +32,15 @@ export default class Client { } getYourPrs = async () => { - return this.doGet(`${this.url}/yourprs`); + return this.doGet(`${this.url}/your_prs`); } getPrsDetails = async (prList) => { - return this.doPost(`${this.url}/prsdetails`, prList); + return this.doPost(`${this.url}/prs_details`, prList); } getYourAssignments = async () => { - return this.doGet(`${this.url}/yourassignments`); + return this.doGet(`${this.url}/your_assignments`); } getMentions = async () => { @@ -72,23 +72,23 @@ export default class Client { } createIssue = async (payload) => { - return this.doPost(`${this.url}/createissue`, payload); + return this.doPost(`${this.url}/create_issue`, payload); } closeOrReopenIssue = async (payload) => { - return this.doPost(`${this.url}/closeorreopenissue`, payload); + return this.doPost(`${this.url}/close_or_reopen_issue`, payload); } updateIssue = async (payload) => { - return this.doPost(`${this.url}/updateissue`, payload); + return this.doPost(`${this.url}/update_issue`, payload); } searchIssues = async (searchTerm) => { - return this.doGet(`${this.url}/searchissues?term=${searchTerm}`); + return this.doGet(`${this.url}/search_issues?term=${searchTerm}`); } attachCommentToIssue = async (payload) => { - return this.doPost(`${this.url}/createissuecomment`, payload); + return this.doPost(`${this.url}/create_issue_comment`, payload); } getIssue = async (owner, repo, issueNumber) => { diff --git a/webapp/src/components/modals/create_update_issue/create_update_issue.jsx b/webapp/src/components/modals/create_update_issue/create_update_issue.jsx index b341a5fd5..2a8bbab83 100644 --- a/webapp/src/components/modals/create_update_issue/create_update_issue.jsx +++ b/webapp/src/components/modals/create_update_issue/create_update_issue.jsx @@ -64,8 +64,10 @@ export default class CreateOrUpdateIssueModal extends PureComponent { value: milestone_number, label: milestone_title, }, + repo: { + name: repo_full_name, + }, issueDescription: description, - repo: repo_full_name, issueTitle: title.substring(0, MAX_TITLE_LENGTH)}); } } @@ -151,29 +153,28 @@ export default class CreateOrUpdateIssueModal extends PureComponent { handleIssueDescriptionChange = (issueDescription) => this.setState({issueDescription}); renderIssueAttributeSelectors = () => { - if (!this.state.repo || (this.state.repo.permissions && !this.state.repo.permissions.push)) { + if (!this.state.repo || !this.state.repo.name || (this.state.repo.permissions && !this.state.repo.permissions.push)) { return null; } - const repoName = this.state.repo.name ?? this.state.repo; return ( <> Date: Mon, 13 Mar 2023 17:42:24 +0530 Subject: [PATCH 04/18] [MI-2814] Done the review fixes of github PR #636 (#22) * [MI-2736]: Review fixes done 1. Improved code readability * [MI-2736]: Review fixes done 1. Fixed linting errors * [MI-2736]: Review fixes done 1. Fixed linting error * [MI-2736]: Review fixes done 1. Improved code readability * [MI-2736]: Review fixes done 1. Changed the case of few endpoints to snake case * [MI-2814]: Review fixes done of github PR #636 * [MI-2814]: Review fixes done 1. Made comment field editable when trying to add a comment from message action. * [MI-2814]: Review fixes done 1. Improved code readability * [MI-2814]: Review fixes done 1. Used GET method on getting the issue info 2. Improved code readability * [MI-2814]: Improved linting error --- server/plugin/api.go | 132 +++++------------- server/plugin/plugin.go | 10 +- server/serializer/issue.go | 8 -- webapp/src/actions/index.js | 40 +----- webapp/src/client/client.js | 12 +- webapp/src/components/github_issue/index.tsx | 9 +- .../attach_comment_to_issue.jsx | 29 ++-- .../modals/close_reopen_issue/index.tsx | 6 +- .../create_update_issue.jsx | 59 +++++--- .../modals/create_update_issue/index.js | 3 +- webapp/src/index.js | 6 +- webapp/src/websocket/index.js | 20 --- 12 files changed, 107 insertions(+), 227 deletions(-) diff --git a/server/plugin/api.go b/server/plugin/api.go index babcfdfb8..fd4381c13 100644 --- a/server/plugin/api.go +++ b/server/plugin/api.go @@ -97,9 +97,7 @@ func (p *Plugin) initializeAPI() { apiRouter.HandleFunc("/create_issue", p.checkAuth(p.attachUserContext(p.createIssue), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc("/close_or_reopen_issue", p.checkAuth(p.attachUserContext(p.closeOrReopenIssue), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc("/update_issue", p.checkAuth(p.attachUserContext(p.updateIssue), ResponseTypePlain)).Methods(http.MethodPost) - apiRouter.HandleFunc("/edit_issue_modal", p.checkAuth(p.attachUserContext(p.openIssueEditModal), ResponseTypePlain)).Methods(http.MethodPost) - apiRouter.HandleFunc("/close_reopen_issue_modal", p.checkAuth(p.attachUserContext(p.openCloseOrReopenIssueModal), ResponseTypePlain)).Methods(http.MethodPost) - apiRouter.HandleFunc("/attach_comment_issue_modal", p.checkAuth(p.attachUserContext(p.openAttachCommentIssueModal), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc("/issue_info", p.checkAuth(p.attachUserContext(p.getIssueInfo), ResponseTypePlain)).Methods(http.MethodGet) apiRouter.HandleFunc("/create_issue_comment", p.checkAuth(p.attachUserContext(p.createIssueComment), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc("/mentions", p.checkAuth(p.attachUserContext(p.getMentions), ResponseTypePlain)).Methods(http.MethodGet) apiRouter.HandleFunc("/unreads", p.checkAuth(p.attachUserContext(p.getUnreads), ResponseTypePlain)).Methods(http.MethodGet) @@ -959,99 +957,38 @@ func (p *Plugin) updateSettings(c *serializer.UserContext, w http.ResponseWriter p.writeJSON(w, info.Settings) } -func (p *Plugin) openAttachCommentIssueModal(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { - req := &serializer.OpenCreateCommentOrEditIssueModalRequestBody{} - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - c.Log.WithError(err).Warnf("Error decoding the JSON body") - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) - return - } - - userID := r.Header.Get(constants.HeaderMattermostUserID) - post, appErr := p.API.GetPost(req.PostID) - if appErr != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", req.PostID), StatusCode: http.StatusInternalServerError}) - return - } - if post == nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", req.PostID), StatusCode: http.StatusNotFound}) - return - } - - p.API.PublishWebSocketEvent( - wsEventAttachCommentToIssue, - map[string]interface{}{ - "postId": post.Id, - "owner": req.RepoOwner, - "repo": req.RepoName, - "number": req.IssueNumber, - }, - &model.WebsocketBroadcast{UserId: userID}, - ) -} - -func (p *Plugin) openCloseOrReopenIssueModal(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { - req := &serializer.OpenCreateCommentOrEditIssueModalRequestBody{} - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - c.Log.WithError(err).Warnf("Error decoding the JSON body") - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) - return - } - - userID := r.Header.Get(constants.HeaderMattermostUserID) - - post, appErr := p.API.GetPost(req.PostID) - if appErr != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", req.PostID), StatusCode: http.StatusInternalServerError}) - return - } - if post == nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", req.PostID), StatusCode: http.StatusNotFound}) - return - } - - p.API.PublishWebSocketEvent( - wsEventCloseOrReopenIssue, - map[string]interface{}{ - "channel_id": post.ChannelId, - "owner": req.RepoOwner, - "repo": req.RepoName, - "number": req.IssueNumber, - "status": req.Status, - "postId": req.PostID, - }, - &model.WebsocketBroadcast{UserId: userID}, - ) -} +func (p *Plugin) getIssueInfo(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { + owner := r.FormValue(constants.OwnerQueryParam) + repo := r.FormValue(constants.RepoQueryParam) + number := r.FormValue(constants.NumberQueryParam) + postID := r.FormValue(constants.PostIDQueryParam) -func (p *Plugin) openIssueEditModal(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { - req := &serializer.OpenCreateCommentOrEditIssueModalRequestBody{} - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - c.Log.WithError(err).Warnf("Error decoding the JSON body") - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) + issueNumber, err := strconv.Atoi(number) + if err != nil { + p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Invalid param 'number'.", StatusCode: http.StatusBadRequest}) return } githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) - issue, _, err := githubClient.Issues.Get(c.Ctx, req.RepoOwner, req.RepoName, req.IssueNumber) + issue, _, err := githubClient.Issues.Get(c.Ctx, owner, repo, issueNumber) if err != nil { // If the issue is not found, it probably belongs to a private repo. // Return an empty response in that case. var gerr *github.ErrorResponse if errors.As(err, &gerr) && gerr.Response.StatusCode == http.StatusNotFound { c.Log.WithError(err).With(logger.LogContext{ - "owner": req.RepoOwner, - "repo": req.RepoName, - "number": req.IssueNumber, + "owner": owner, + "repo": repo, + "number": issueNumber, }).Debugf("Issue not found") p.writeJSON(w, nil) return } c.Log.WithError(err).With(logger.LogContext{ - "owner": req.RepoOwner, - "repo": req.RepoName, - "number": req.IssueNumber, + "owner": owner, + "repo": repo, + "number": issueNumber, }).Debugf("Could not get the issue") p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Could not get the issue", StatusCode: http.StatusInternalServerError}) return @@ -1080,35 +1017,30 @@ func (p *Plugin) openIssueEditModal(c *serializer.UserContext, w http.ResponseWr milestoneNumber = *issue.Milestone.Number } - userID := r.Header.Get(constants.HeaderMattermostUserID) - post, appErr := p.API.GetPost(req.PostID) + post, appErr := p.API.GetPost(postID) if appErr != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", req.PostID), StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", postID), StatusCode: http.StatusInternalServerError}) return } if post == nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", req.PostID), StatusCode: http.StatusNotFound}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", postID), StatusCode: http.StatusNotFound}) return } - p.API.PublishWebSocketEvent( - wsEventCreateOrUpdateIssue, - map[string]interface{}{ - "title": *issue.Title, - "channel_id": post.ChannelId, - "postId": req.PostID, - "milestone_title": milestoneTitle, - "milestone_number": milestoneNumber, - "assignees": assignees, - "labels": labels, - "description": description, - "repo_full_name": fmt.Sprintf("%s/%s", req.RepoOwner, req.RepoName), - "issue_number": *issue.Number, - }, - &model.WebsocketBroadcast{UserId: userID}, - ) + issueInfo := map[string]interface{}{ + "title": *issue.Title, + "channel_id": post.ChannelId, + "postId": postID, + "milestone_title": milestoneTitle, + "milestone_number": milestoneNumber, + "assignees": assignees, + "labels": labels, + "description": description, + "repo_full_name": fmt.Sprintf("%s/%s", owner, repo), + "issue_number": *issue.Number, + } - p.writeJSON(w, issue) + p.writeJSON(w, issueInfo) } func (p *Plugin) getIssueByNumber(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { diff --git a/server/plugin/plugin.go b/server/plugin/plugin.go index 0414be207..13d14a725 100644 --- a/server/plugin/plugin.go +++ b/server/plugin/plugin.go @@ -40,11 +40,9 @@ const ( wsEventConnect = "connect" wsEventDisconnect = "disconnect" // WSEventConfigUpdate is the WebSocket event to update the configurations on webapp. - WSEventConfigUpdate = "config_update" - wsEventRefresh = "refresh" - wsEventCreateOrUpdateIssue = "createOrUpdateIssue" - wsEventCloseOrReopenIssue = "closeOrReopenIssue" - wsEventAttachCommentToIssue = "attachCommentToIssue" + WSEventConfigUpdate = "config_update" + wsEventRefresh = "refresh" + wsEventCreateIssue = "createIssue" WSEventRefresh = "refresh" @@ -482,7 +480,7 @@ func (p *Plugin) disconnectGitHubAccount(userID string) { func (p *Plugin) openIssueCreateModal(userID string, channelID string, title string) { p.API.PublishWebSocketEvent( - wsEventCreateOrUpdateIssue, + wsEventCreateIssue, map[string]interface{}{ "title": title, "channel_id": channelID, diff --git a/server/serializer/issue.go b/server/serializer/issue.go index e21432423..a5d400a79 100644 --- a/server/serializer/issue.go +++ b/server/serializer/issue.go @@ -42,11 +42,3 @@ type CommentAndCloseRequest struct { Status string `json:"status"` PostID string `json:"postId"` } - -type OpenCreateCommentOrEditIssueModalRequestBody struct { - RepoOwner string `json:"repo_owner"` - RepoName string `json:"repo_name"` - IssueNumber int `json:"issue_number"` - PostID string `json:"postId"` - Status string `json:"status"` -} diff --git a/webapp/src/actions/index.js b/webapp/src/actions/index.js index 17935a1e9..d6133bb2a 100644 --- a/webapp/src/actions/index.js +++ b/webapp/src/actions/index.js @@ -211,47 +211,11 @@ export function getMilestoneOptions(repo) { }; } -export function attachCommentIssueModal(payload) { +export function getIssueInfo(owner, repo, issueNumber, postID) { return async (dispatch, getState) => { let data; try { - data = await Client.attachCommentIssueModal(payload); - } catch (error) { - return {error}; - } - - const connected = await checkAndHandleNotConnected(data)(dispatch, getState); - if (!connected) { - return {error: data}; - } - - return {data}; - }; -} - -export function editIssueModal(payload) { - return async (dispatch, getState) => { - let data; - try { - data = await Client.editIssueModal(payload); - } catch (error) { - return {error}; - } - - const connected = await checkAndHandleNotConnected(data)(dispatch, getState); - if (!connected) { - return {error: data}; - } - - return {data}; - }; -} - -export function closeOrReopenIssueModal(payload) { - return async (dispatch, getState) => { - let data; - try { - data = await Client.closeOrReopenIssueModal(payload); + data = await Client.getIssueInfo(owner, repo, issueNumber, postID); } catch (error) { return {error}; } diff --git a/webapp/src/client/client.js b/webapp/src/client/client.js index 9b26d88e6..c20b18439 100644 --- a/webapp/src/client/client.js +++ b/webapp/src/client/client.js @@ -7,16 +7,8 @@ import {ClientError} from 'mattermost-redux/client/client4'; import {id as pluginId} from '../manifest'; export default class Client { - editIssueModal = async (payload) => { - return this.doPost(`${this.url}/edit_issue_modal`, payload); - } - - closeOrReopenIssueModal = async (payload) => { - return this.doPost(`${this.url}/close_reopen_issue_modal`, payload); - } - - attachCommentIssueModal = async (payload) => { - return this.doPost(`${this.url}/attach_comment_issue_modal`, payload); + getIssueInfo = async (owner, repo, issueNumber, postID) => { + return this.doGet(`${this.url}/issue_info?owner=${owner}&repo=${repo}&number=${issueNumber}&postId=${postID}`); } setServerRoute(url) { diff --git a/webapp/src/components/github_issue/index.tsx b/webapp/src/components/github_issue/index.tsx index 9edeee267..75eb03873 100644 --- a/webapp/src/components/github_issue/index.tsx +++ b/webapp/src/components/github_issue/index.tsx @@ -4,7 +4,7 @@ import {Theme} from 'mattermost-redux/types/preferences'; import {Post} from 'mattermost-redux/types/posts'; import {useDispatch} from 'react-redux'; -import {attachCommentIssueModal, editIssueModal, closeOrReopenIssueModal} from '../../actions'; +import {openCreateCommentOnIssueModal, openCreateOrUpdateIssueModal, openCloseOrReopenIssueModal} from '../../actions'; type GithubIssueProps = { theme: Theme, @@ -24,6 +24,7 @@ const GithubIssue = ({theme, post}: GithubIssueProps) => { issue_number: postProps.issue_number, postId: post.id, status: postProps.status, + channel_id: post.channel_id, }; const content = ( @@ -31,17 +32,17 @@ const GithubIssue = ({theme, post}: GithubIssueProps) => {
); diff --git a/webapp/src/components/modals/attach_comment_to_issue/attach_comment_to_issue.jsx b/webapp/src/components/modals/attach_comment_to_issue/attach_comment_to_issue.jsx index f959f884a..a674b6cac 100644 --- a/webapp/src/components/modals/attach_comment_to_issue/attach_comment_to_issue.jsx +++ b/webapp/src/components/modals/attach_comment_to_issue/attach_comment_to_issue.jsx @@ -40,11 +40,11 @@ export default class AttachIssueModal extends PureComponent { } if (!this.state.issueValue) { - const {owner, repo, number} = this.props.messageData ?? {}; + const {repo_owner, repo_name, issue_number} = this.props.messageData ?? {}; const issue = { - owner, - repo, - number, + owner: repo_owner, + repo: repo_name, + number: issue_number, comment: this.state.comment, post_id: this.props.post.id, show_attached_message: false, @@ -72,7 +72,7 @@ export default class AttachIssueModal extends PureComponent { owner, repo, number, - comment: this.props.post.message, + comment: this.state.comment, post_id: this.props.post.id, show_attached_message: true, }; @@ -106,17 +106,23 @@ export default class AttachIssueModal extends PureComponent { }); }; + componentDidUpdate(prevProps) { + if (this.props.post && !this.props.messageData && !prevProps.post) { + this.setState({comment: this.props.post.message}); // eslint-disable-line react/no-did-update-set-state + } + } + render() { const {error, submitting, comment, issueValue} = this.state; - const {visible, theme, messageData, post} = this.props; + const {visible, theme, messageData} = this.props; const style = getStyle(theme); if (!visible) { return null; } - const {number} = messageData ?? {}; - const modalTitle = number ? 'Create a comment to GitHub Issue' : 'Attach Message to GitHub Issue'; - const component = number ? ( + const {issue_number} = messageData ?? {}; + const modalTitle = issue_number ? 'Create a comment to GitHub Issue' : 'Attach Message to GitHub Issue'; + const component = issue_number ? (
); diff --git a/webapp/src/components/modals/close_reopen_issue/index.tsx b/webapp/src/components/modals/close_reopen_issue/index.tsx index 5e793a19a..a78c4cb25 100644 --- a/webapp/src/components/modals/close_reopen_issue/index.tsx +++ b/webapp/src/components/modals/close_reopen_issue/index.tsx @@ -36,9 +36,9 @@ const CloseOrReopenIssueModal = ({theme}: {theme: Theme}) => { channel_id: messageData.channel_id, issue_comment: comment, status_reason: currentStatus, - repo: messageData.repo, - number: messageData.number, - owner: messageData.owner, + repo: messageData.repo_name, + number: messageData.issue_number, + owner: messageData.repo_owner, status: messageData.status, postId: messageData.postId, }; diff --git a/webapp/src/components/modals/create_update_issue/create_update_issue.jsx b/webapp/src/components/modals/create_update_issue/create_update_issue.jsx index 2a8bbab83..7ad0983f1 100644 --- a/webapp/src/components/modals/create_update_issue/create_update_issue.jsx +++ b/webapp/src/components/modals/create_update_issue/create_update_issue.jsx @@ -22,6 +22,7 @@ const initialState = { repo: null, issueTitle: '', issueDescription: '', + channelId: '', labels: [], assignees: [], milestone: null, @@ -34,6 +35,7 @@ export default class CreateOrUpdateIssueModal extends PureComponent { update: PropTypes.func.isRequired, close: PropTypes.func.isRequired, create: PropTypes.func.isRequired, + getIssueInfo: PropTypes.func.isRequired, post: PropTypes.object, theme: PropTypes.object.isRequired, visible: PropTypes.bool.isRequired, @@ -46,36 +48,50 @@ export default class CreateOrUpdateIssueModal extends PureComponent { this.validator = new Validator(); } + getIssueInfo = async () => { + const {repo_owner, repo_name, issue_number, postId} = this.props.messageData; + const issueInfo = await this.props.getIssueInfo(repo_owner, repo_name, issue_number, postId); + return issueInfo; + } + + updateState(issueInfo) { + const {channel_id, title, description, milestone_title, milestone_number, repo_full_name} = issueInfo ?? {}; + const assignees = issueInfo?.assignees ?? []; + const labels = issueInfo?.labels ?? []; + + this.setState({milestone: { + value: milestone_number, + label: milestone_title, + }, + repo: { + name: repo_full_name, + }, + assignees, + labels, + channelId: channel_id, + issueDescription: description, + issueTitle: title.substring(0, MAX_TITLE_LENGTH)}); + } + /* eslint-disable react/no-did-update-set-state*/ componentDidUpdate(prevProps) { - if (this.props.post && !this.props.messageData) { + if (this.props.post && !this.props.messageData && !prevProps.post) { this.setState({issueDescription: this.props.post.message}); } - const {channel_id, title, description, assignees, labels, milestone_title, milestone_number, repo_full_name} = this.props.messageData ?? {}; - if (channel_id && (channel_id !== prevProps.messageData?.channel_id || title !== prevProps.messageData?.title || description !== prevProps.messageData?.description || assignees !== prevProps.messageData?.assignees || labels !== prevProps.messageData?.labels || milestone_title !== prevProps.messageData?.milestone_title || milestone_number !== prevProps.messageData?.milestone_number)) { - if (assignees) { - this.setState({assignees}); - } - if (labels) { - this.setState({labels}); - } - this.setState({milestone: { - value: milestone_number, - label: milestone_title, - }, - repo: { - name: repo_full_name, - }, - issueDescription: description, - issueTitle: title.substring(0, MAX_TITLE_LENGTH)}); + if (this.props.messageData?.repo_owner && !prevProps.visible && this.props.visible) { + this.getIssueInfo().then((issueInfo) => { + this.updateState(issueInfo.data); + }); + } else if (this.props.messageData?.channel_id && (this.props.messageData?.channel_id !== prevProps.messageData?.channel_id || this.props.messageData?.title !== prevProps.messageData?.title)) { + this.updateState(this.props.messageData); } } /* eslint-enable */ // handle issue creation or updation after form is populated handleCreateOrUpdate = async (e) => { - const {channel_id, issue_number, repo_full_name} = this.props.messageData ?? {}; + const {issue_number} = this.props.messageData ?? {}; if (e && e.preventDefault) { e.preventDefault(); } @@ -99,7 +115,7 @@ export default class CreateOrUpdateIssueModal extends PureComponent { assignees: this.state.assignees, milestone: this.state.milestone && this.state.milestone.value, post_id: postId, - channel_id, + channel_id: this.state.channelId, issue_number, }; @@ -107,7 +123,7 @@ export default class CreateOrUpdateIssueModal extends PureComponent { issue.repo = this.state.repo; } this.setState({submitting: true}); - if (repo_full_name) { + if (issue_number) { const updated = await this.props.update(issue); if (updated?.error) { const errMessage = getErrorMessage(updated.error.message); @@ -233,6 +249,7 @@ export default class CreateOrUpdateIssueModal extends PureComponent { onChange={this.handleIssueTitleChange} /> {issueTitleValidationError} + {this.renderIssueAttributeSelectors()} bindActionCreators({ close: closeCreateOrUpdateIssueModal, create: createIssue, update: updateIssue, + getIssueInfo, }, dispatch); export default connect(mapStateToProps, mapDispatchToProps)(CreateOrUpdateIssueModal); diff --git a/webapp/src/index.js b/webapp/src/index.js index 0a7a18a42..9b79a57da 100644 --- a/webapp/src/index.js +++ b/webapp/src/index.js @@ -15,7 +15,7 @@ import GithubIssue from './components/github_issue'; import Reducer from './reducers'; import Client from './client'; import {getConnected, setShowRHSAction} from './actions'; -import {handleConnect, handleDisconnect, handleConfigurationUpdate, handleOpenCreateOrUpdateIssueModal, handleOpenCreateCommentOnIssueModal, handleOpenCloseOrReopenIssueModal, handleReconnect, handleRefresh} from './websocket'; +import {handleConnect, handleDisconnect, handleConfigurationUpdate, handleOpenCreateOrUpdateIssueModal, handleReconnect, handleRefresh} from './websocket'; import {getServerRoute} from './selectors'; import {id as pluginId} from './manifest'; @@ -47,9 +47,7 @@ class PluginClass { registry.registerWebSocketEventHandler(`custom_${pluginId}_disconnect`, handleDisconnect(store)); registry.registerWebSocketEventHandler(`custom_${pluginId}_config_update`, handleConfigurationUpdate(store)); registry.registerWebSocketEventHandler(`custom_${pluginId}_refresh`, handleRefresh(store)); - registry.registerWebSocketEventHandler(`custom_${pluginId}_createOrUpdateIssue`, handleOpenCreateOrUpdateIssueModal(store)); - registry.registerWebSocketEventHandler(`custom_${pluginId}_attachCommentToIssue`, handleOpenCreateCommentOnIssueModal(store)); - registry.registerWebSocketEventHandler(`custom_${pluginId}_closeOrReopenIssue`, handleOpenCloseOrReopenIssueModal(store)); + registry.registerWebSocketEventHandler(`custom_${pluginId}_createIssue`, handleOpenCreateOrUpdateIssueModal(store)); registry.registerPostTypeComponent('custom_git_issue', GithubIssue); registry.registerReconnectHandler(handleReconnect(store)); diff --git a/webapp/src/websocket/index.js b/webapp/src/websocket/index.js index 88690dc55..3a91692cf 100644 --- a/webapp/src/websocket/index.js +++ b/webapp/src/websocket/index.js @@ -10,8 +10,6 @@ import { getYourAssignments, getYourPrs, openCreateOrUpdateIssueModal, - openCloseOrReopenIssueModal, - openCreateCommentOnIssueModal, } from '../actions'; import {id as pluginId} from '../manifest'; @@ -95,21 +93,3 @@ export function handleOpenCreateOrUpdateIssueModal(store) { store.dispatch(openCreateOrUpdateIssueModal(msg.data)); }; } - -export function handleOpenCloseOrReopenIssueModal(store) { - return (msg) => { - if (!msg.data) { - return; - } - store.dispatch(openCloseOrReopenIssueModal(msg.data)); - }; -} - -export function handleOpenCreateCommentOnIssueModal(store) { - return (msg) => { - if (!msg.data) { - return; - } - store.dispatch(openCreateCommentOnIssueModal(msg.data)); - }; -} From 0f40aba448ee44aeef6614a87f63b496d1fa5673 Mon Sep 17 00:00:00 2001 From: ayusht2810 Date: Wed, 10 Apr 2024 17:29:59 +0530 Subject: [PATCH 05/18] [MM-618] Update validations, modal title, styling, post messages, conflicts error and lint errors --- server/constants/constants.go | 18 +++++------ server/plugin/api.go | 16 +++++++--- server/plugin/api_test.go | 2 +- server/plugin/command.go | 6 ++-- server/plugin/mm_34646_token_refresh.go | 1 + server/plugin/utils.go | 9 +++--- server/plugin/webhook.go | 4 +-- server/serializer/user.go | 2 +- webapp/src/components/input.jsx | 5 +-- .../attach_comment_to_issue.jsx | 9 +++++- .../components/modals/create_issue/index.js | 31 ------------------- .../create_update_issue.jsx | 19 +++++++----- webapp/src/selectors.js | 6 ++-- 13 files changed, 60 insertions(+), 68 deletions(-) delete mode 100644 webapp/src/components/modals/create_issue/index.js diff --git a/server/constants/constants.go b/server/constants/constants.go index 88c0fdc42..985f81ae9 100644 --- a/server/constants/constants.go +++ b/server/constants/constants.go @@ -34,15 +34,15 @@ const ( IssueOpen = "open" // Actions of webhook events - ActionOpened = "opened" - ActionClosed = "closed" - ActionReopened = "reopened" - ActionSubmitted = "submitted" - ActionLabeled = "labeled" - ActionAssigned = "assigned" - ActionCreated = "created" - ActionDeleted = "deleted" - ActionEdited = "edited" + ActionOpened = "opened" + ActionClosed = "closed" + ActionReopened = "reopened" + ActionSubmitted = "submitted" + ActionLabeled = "labeled" + ActionAssigned = "assigned" + ActionCreated = "created" + ActionDeleted = "deleted" + ActionEdited = "edited" ActionMarkedReadyForReview = "ready_for_review" PostPropGithubRepo = "gh_repo" diff --git a/server/plugin/api.go b/server/plugin/api.go index bd89e0c7e..dae066aa4 100644 --- a/server/plugin/api.go +++ b/server/plugin/api.go @@ -836,7 +836,11 @@ func (p *Plugin) createIssueComment(c *serializer.UserContext, w http.ResponseWr rootID = post.RootId } - permalinkReplyMessage := fmt.Sprintf("[Message](%v) attached to GitHub issue [#%v](%v)", permalink, req.Number, result.GetHTMLURL()) + permalinkReplyMessage := fmt.Sprintf("Comment attached to GitHub issue [#%v](%v) from a [Message](%v)", req.Number, result.GetHTMLURL(), permalink) + if req.ShowAttachedMessage { + permalinkReplyMessage = fmt.Sprintf("[Message](%v) attached to GitHub issue [#%v](%v)", permalink, req.Number, result.GetHTMLURL()) + } + reply := &model.Post{ Message: permalinkReplyMessage, ChannelId: post.ChannelId, @@ -972,8 +976,7 @@ func (p *Plugin) getIssueInfo(c *serializer.UserContext, w http.ResponseWriter, description := "" if issue.Body != nil { - *issue.Body = mdCommentRegex.ReplaceAllString(issue.GetBody(), "") - description = *issue.Body + description = mdCommentRegex.ReplaceAllString(issue.GetBody(), "") } assignees := make([]string, len(issue.Assignees)) @@ -995,6 +998,7 @@ func (p *Plugin) getIssueInfo(c *serializer.UserContext, w http.ResponseWriter, post, appErr := p.API.GetPost(postID) if appErr != nil { + p.client.Log.Error("Unable to get the post", "PostID", postID, "Error", appErr.Error()) p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", postID), StatusCode: http.StatusInternalServerError}) return } @@ -1288,6 +1292,7 @@ func (p *Plugin) updateIssue(c *serializer.UserContext, w http.ResponseWriter, r var appErr *model.AppError post, appErr = p.API.GetPost(issue.PostID) if appErr != nil { + p.client.Log.Error("Unable to get the post", "PostID", issue.PostID, "Error", appErr.Error()) p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) return } @@ -1313,6 +1318,7 @@ func (p *Plugin) updateIssue(c *serializer.UserContext, w http.ResponseWriter, r currentUser, appErr := p.API.GetUser(c.UserID) if appErr != nil { + p.client.Log.Error("Unable to get the user", "UserID", c.UserID, "Error", appErr.Error()) p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "failed to load current user", StatusCode: http.StatusInternalServerError}) return } @@ -1387,6 +1393,7 @@ func (p *Plugin) closeOrReopenIssue(c *serializer.UserContext, w http.ResponseWr post, appErr := p.API.GetPost(req.PostID) if appErr != nil { + p.client.Log.Error("Unable to get the post", "PostID", req.PostID, "Error", appErr.Error()) p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", req.PostID), StatusCode: http.StatusInternalServerError}) return } @@ -1396,6 +1403,7 @@ func (p *Plugin) closeOrReopenIssue(c *serializer.UserContext, w http.ResponseWr } if _, err := p.getUsername(post.UserId); err != nil { + p.client.Log.Error("Unable to get the username", "UserID", post.UserId, "Error", err.Error()) p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) return } @@ -1441,7 +1449,7 @@ func (p *Plugin) createIssue(c *serializer.UserContext, w http.ResponseWriter, r var err error post, err = p.client.Post.GetPost(issue.PostID) if err != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) return } if post == nil { diff --git a/server/plugin/api_test.go b/server/plugin/api_test.go index 40931a0d7..fe013ec55 100644 --- a/server/plugin/api_test.go +++ b/server/plugin/api_test.go @@ -12,7 +12,7 @@ import ( "github.com/mattermost/mattermost/server/public/plugin" "github.com/mattermost/mattermost/server/public/plugin/plugintest" "github.com/mattermost/mattermost/server/public/pluginapi" - + "github.com/mattermost/mattermost-plugin-github/server/constants" "github.com/mattermost/mattermost-plugin-github/server/serializer" "github.com/mattermost/mattermost-plugin-github/server/testutils" diff --git a/server/plugin/command.go b/server/plugin/command.go index e06dfc33c..55572c83a 100644 --- a/server/plugin/command.go +++ b/server/plugin/command.go @@ -7,14 +7,14 @@ import ( "unicode" "github.com/google/go-github/v48/github" - "github.com/mattermost/mattermost-plugin-github/server/constants" - "github.com/mattermost/mattermost-plugin-github/server/serializer" - "github.com/pkg/errors" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/plugin" "github.com/mattermost/mattermost/server/public/pluginapi/experimental/command" + + "github.com/mattermost/mattermost-plugin-github/server/constants" + "github.com/mattermost/mattermost-plugin-github/server/serializer" ) const ( diff --git a/server/plugin/mm_34646_token_refresh.go b/server/plugin/mm_34646_token_refresh.go index 0b5064950..e1dd0a3fa 100644 --- a/server/plugin/mm_34646_token_refresh.go +++ b/server/plugin/mm_34646_token_refresh.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" "github.com/mattermost/mattermost-plugin-github/server/serializer" + "github.com/mattermost/mattermost/server/public/pluginapi/cluster" ) diff --git a/server/plugin/utils.go b/server/plugin/utils.go index 05358aab2..9d161254d 100644 --- a/server/plugin/utils.go +++ b/server/plugin/utils.go @@ -16,12 +16,13 @@ import ( "strings" "unicode" + "github.com/google/go-github/v48/github" + "github.com/pkg/errors" + "github.com/mattermost/mattermost-plugin-github/server/constants" "github.com/mattermost/mattermost-plugin-github/server/serializer" - "github.com/mattermost/mattermost/server/public/model" - "github.com/google/go-github/v48/github" - "github.com/pkg/errors" + "github.com/mattermost/mattermost/server/public/model" ) func getMentionSearchQuery(username, org string) string { @@ -427,7 +428,7 @@ func (p *Plugin) CreateCommentToIssue(c *serializer.UserContext, w http.Response rootID = post.RootId } - permalinkReplyMessage := fmt.Sprintf("[Comment](%v) attached to GitHub issue [#%v](%v)", permalink, issueNumber, result.GetHTMLURL()) + permalinkReplyMessage := fmt.Sprintf("Comment attached to GitHub issue [#%v](%v) from a [message](%v)", issueNumber, result.GetHTMLURL(), permalink) reply := &model.Post{ Message: permalinkReplyMessage, ChannelId: post.ChannelId, diff --git a/server/plugin/webhook.go b/server/plugin/webhook.go index 98dbc916d..e5bfe6ed6 100644 --- a/server/plugin/webhook.go +++ b/server/plugin/webhook.go @@ -337,8 +337,8 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { action := event.GetAction() switch action { case constants.ActionOpened, - constants.ActionReopened, - constants.ActionMarkedReadyForReview, + constants.ActionReopened, + constants.ActionMarkedReadyForReview, constants.ActionLabeled, constants.ActionClosed: default: diff --git a/server/serializer/user.go b/server/serializer/user.go index 1927e7018..e37f4e410 100644 --- a/server/serializer/user.go +++ b/server/serializer/user.go @@ -3,8 +3,8 @@ package serializer import ( "context" - "golang.org/x/oauth2" "github.com/mattermost/mattermost/server/public/pluginapi/experimental/bot/logger" + "golang.org/x/oauth2" ) type Context struct { diff --git a/webapp/src/components/input.jsx b/webapp/src/components/input.jsx index 324cbdcd3..c9392c3f3 100644 --- a/webapp/src/components/input.jsx +++ b/webapp/src/components/input.jsx @@ -16,6 +16,7 @@ export default class Input extends PureComponent { PropTypes.string, PropTypes.number, ]), + error: PropTypes.string, addValidate: PropTypes.func, removeValidate: PropTypes.func, maxLength: PropTypes.number, @@ -84,10 +85,10 @@ export default class Input extends PureComponent { const value = this.props.value || ''; let validationError = null; - if (this.props.required && this.state.invalid) { + if ((this.props.required && this.state.invalid) || this.props.error) { validationError = (

- {requiredMsg} + {requiredMsg || this.props.error}

); } diff --git a/webapp/src/components/modals/attach_comment_to_issue/attach_comment_to_issue.jsx b/webapp/src/components/modals/attach_comment_to_issue/attach_comment_to_issue.jsx index a674b6cac..2096f60e8 100644 --- a/webapp/src/components/modals/attach_comment_to_issue/attach_comment_to_issue.jsx +++ b/webapp/src/components/modals/attach_comment_to_issue/attach_comment_to_issue.jsx @@ -40,6 +40,11 @@ export default class AttachIssueModal extends PureComponent { } if (!this.state.issueValue) { + if (!this.state.comment.trim()) { + this.setState({error: 'This field is required.', submitting: false}); + return; + } + const {repo_owner, repo_name, issue_number} = this.props.messageData ?? {}; const issue = { owner: repo_owner, @@ -90,7 +95,7 @@ export default class AttachIssueModal extends PureComponent { }); }; - handleIssueCommentChange = (comment) => this.setState({comment}); + handleIssueCommentChange = (comment) => this.setState({comment, error: ''}); handleClose = (e) => { if (e && e.preventDefault) { @@ -127,7 +132,9 @@ export default class AttachIssueModal extends PureComponent { diff --git a/webapp/src/components/modals/create_issue/index.js b/webapp/src/components/modals/create_issue/index.js deleted file mode 100644 index e75562886..000000000 --- a/webapp/src/components/modals/create_issue/index.js +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {connect} from 'react-redux'; -import {bindActionCreators} from 'redux'; -import {getPost} from 'mattermost-redux/selectors/entities/posts'; - -import manifest from 'manifest'; -import {closeCreateIssueModal, createIssue} from 'actions'; - -import CreateIssueModal from './create_issue'; - -const mapStateToProps = (state) => { - const {id: pluginId} = manifest; - const {postId, title, channelId} = state[`plugins-${pluginId}`].createIssueModal; - const post = (postId) ? getPost(state, postId) : null; - - return { - visible: state[`plugins-${pluginId}`].isCreateIssueModalVisible, - post, - title, - channelId, - }; -}; - -const mapDispatchToProps = (dispatch) => bindActionCreators({ - close: closeCreateIssueModal, - create: createIssue, -}, dispatch); - -export default connect(mapStateToProps, mapDispatchToProps)(CreateIssueModal); diff --git a/webapp/src/components/modals/create_update_issue/create_update_issue.jsx b/webapp/src/components/modals/create_update_issue/create_update_issue.jsx index 7ad0983f1..0240b5063 100644 --- a/webapp/src/components/modals/create_update_issue/create_update_issue.jsx +++ b/webapp/src/components/modals/create_update_issue/create_update_issue.jsx @@ -205,16 +205,21 @@ export default class CreateOrUpdateIssueModal extends PureComponent { } const theme = this.props.theme; - const {error, submitting, showErrors, issueTitleValid, issueTitle, issueDescription, repo} = this.state; + const {error, submitting, showErrors, issueTitle, issueDescription, repo} = this.state; const style = getStyle(theme); - const {repo_full_name} = this.props.messageData ?? {}; - const modalTitle = repo_full_name ? 'Update GitHub Issue' : 'Create GitHub Issue'; + const {repo_name, repo_owner} = this.props.messageData ?? {}; + const modalTitle = repo_name ? 'Update GitHub Issue' : 'Create GitHub Issue'; const requiredMsg = 'This field is required.'; let issueTitleValidationError = null; - if (showErrors && !issueTitleValid) { + if (showErrors && !issueTitle) { issueTitleValidationError = ( -

+

{requiredMsg}

); @@ -229,14 +234,14 @@ export default class CreateOrUpdateIssueModal extends PureComponent { ); } - const component = repo_full_name ? ( + const component = repo_name ? (
Date: Wed, 1 May 2024 17:37:14 +0530 Subject: [PATCH 06/18] [MM-618] Remove constants and serializers package --- server/constants/constants.go | 55 ---- server/plugin/api.go | 318 +++++++++++++---------- server/plugin/api_test.go | 6 +- server/plugin/command.go | 41 ++- server/{serializer => plugin}/error.go | 2 +- server/plugin/mm_34646_token_refresh.go | 6 +- server/plugin/oauth.go | 58 +++++ server/plugin/plugin.go | 117 ++++++--- server/{serializer => plugin}/sidebar.go | 7 +- server/plugin/utils.go | 51 ++-- server/plugin/webhook.go | 110 ++++---- server/serializer/issue.go | 44 ---- server/serializer/notification.go | 8 - server/serializer/pr.go | 12 - server/serializer/repo.go | 8 - server/serializer/user.go | 62 ----- 16 files changed, 436 insertions(+), 469 deletions(-) delete mode 100644 server/constants/constants.go rename server/{serializer => plugin}/error.go (91%) rename server/{serializer => plugin}/sidebar.go (84%) delete mode 100644 server/serializer/issue.go delete mode 100644 server/serializer/notification.go delete mode 100644 server/serializer/pr.go delete mode 100644 server/serializer/repo.go delete mode 100644 server/serializer/user.go diff --git a/server/constants/constants.go b/server/constants/constants.go deleted file mode 100644 index 985f81ae9..000000000 --- a/server/constants/constants.go +++ /dev/null @@ -1,55 +0,0 @@ -package constants - -import "time" - -const ( - APIErrorIDNotConnected = "not_connected" - // TokenTTL is the OAuth token expiry duration in seconds - TokenTTL = 600 - - RequestTimeout = 30 * time.Second - OauthCompleteTimeout = 2 * time.Minute - HeaderMattermostUserID = "Mattermost-User-ID" - OwnerQueryParam = "owner" - RepoQueryParam = "repo" - NumberQueryParam = "number" - PostIDQueryParam = "postId" - - IssueStatus = "status" - AssigneesForProps = "assignees" - LabelsForProps = "labels" - DescriptionForProps = "description" - TitleForProps = "title" - IssueNumberForProps = "issue_number" - IssueURLForProps = "issue_url" - RepoOwnerForProps = "repo_owner" - RepoNameForProps = "repo_name" - - Close = "Close" - Reopen = "Reopen" - - IssueCompleted = "completed" - IssueNotPlanned = "not_planned" - IssueClose = "closed" - IssueOpen = "open" - - // Actions of webhook events - ActionOpened = "opened" - ActionClosed = "closed" - ActionReopened = "reopened" - ActionSubmitted = "submitted" - ActionLabeled = "labeled" - ActionAssigned = "assigned" - ActionCreated = "created" - ActionDeleted = "deleted" - ActionEdited = "edited" - ActionMarkedReadyForReview = "ready_for_review" - - PostPropGithubRepo = "gh_repo" - PostPropGithubObjectID = "gh_object_id" - PostPropGithubObjectType = "gh_object_type" - - GithubObjectTypeIssue = "issue" - GithubObjectTypeIssueComment = "issue_comment" - GithubObjectTypePRReviewComment = "pr_review_comment" -) diff --git a/server/plugin/api.go b/server/plugin/api.go index 23706c81d..106031a5e 100644 --- a/server/plugin/api.go +++ b/server/plugin/api.go @@ -16,9 +16,6 @@ import ( "github.com/pkg/errors" "golang.org/x/oauth2" - "github.com/mattermost/mattermost-plugin-github/server/constants" - "github.com/mattermost/mattermost-plugin-github/server/serializer" - "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/plugin" "github.com/mattermost/mattermost/server/public/pluginapi" @@ -27,14 +24,35 @@ import ( ) // HTTPHandlerFuncWithUserContext is http.HandleFunc but with a UserContext attached -type HTTPHandlerFuncWithUserContext func(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) +type HTTPHandlerFuncWithUserContext func(c *UserContext, w http.ResponseWriter, r *http.Request) // HTTPHandlerFuncWithContext is http.HandleFunc but with a .ontext attached -type HTTPHandlerFuncWithContext func(c *serializer.Context, w http.ResponseWriter, r *http.Request) +type HTTPHandlerFuncWithContext func(c *Context, w http.ResponseWriter, r *http.Request) // ResponseType indicates type of response returned by api type ResponseType string +type UpdateIssueRequest struct { + Title string `json:"title"` + Body string `json:"body"` + Repo string `json:"repo"` + PostID string `json:"post_id"` + ChannelID string `json:"channel_id"` + Labels []string `json:"labels"` + Assignees []string `json:"assignees"` + Milestone int `json:"milestone"` + IssueNumber int `json:"issue_number"` +} + +type PRDetails struct { + URL string `json:"url"` + Number int `json:"number"` + Status string `json:"status"` + Mergeable bool `json:"mergeable"` + RequestedReviewers []*string `json:"requestedReviewers"` + Reviews []*github.PullRequestReview `json:"reviews"` +} + const ( // ResponseTypeJSON indicates that response type is json ResponseTypeJSON ResponseType = "JSON_RESPONSE" @@ -56,7 +74,7 @@ func (p *Plugin) writeJSON(w http.ResponseWriter, v interface{}) { } } -func (p *Plugin) writeAPIError(w http.ResponseWriter, apiErr *serializer.APIErrorResponse) { +func (p *Plugin) writeAPIError(w http.ResponseWriter, apiErr *APIErrorResponse) { b, err := json.Marshal(apiErr) if err != nil { p.client.Log.Warn("Failed to marshal API error", "error", err.Error()) @@ -141,11 +159,11 @@ func (p *Plugin) checkConfigured(next http.Handler) http.Handler { func (p *Plugin) checkAuth(handler http.HandlerFunc, responseType ResponseType) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - userID := r.Header.Get(constants.HeaderMattermostUserID) + userID := r.Header.Get(headerMattermostUserID) if userID == "" { switch responseType { case ResponseTypeJSON: - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Not authorized.", StatusCode: http.StatusUnauthorized}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Not authorized.", StatusCode: http.StatusUnauthorized}) case ResponseTypePlain: http.Error(w, "Not authorized", http.StatusUnauthorized) default: @@ -158,16 +176,16 @@ func (p *Plugin) checkAuth(handler http.HandlerFunc, responseType ResponseType) } } -func (p *Plugin) createContext(_ http.ResponseWriter, r *http.Request) (*serializer.Context, context.CancelFunc) { - userID := r.Header.Get(constants.HeaderMattermostUserID) +func (p *Plugin) createContext(_ http.ResponseWriter, r *http.Request) (*Context, context.CancelFunc) { + userID := r.Header.Get(headerMattermostUserID) logger := logger.New(p.API).With(logger.LogContext{ "userid": userID, }) - ctx, cancel := context.WithTimeout(context.Background(), constants.RequestTimeout) + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - context := &serializer.Context{ + context := &Context{ Ctx: ctx, UserID: userID, Log: logger, @@ -200,7 +218,7 @@ func (p *Plugin) attachUserContext(handler HTTPHandlerFuncWithUserContext) http. "github username": info.GitHubUsername, }) - userContext := &serializer.UserContext{ + userContext := &UserContext{ Context: *context, GHInfo: info, } @@ -228,7 +246,7 @@ func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Req p.router.ServeHTTP(w, r) } -func (p *Plugin) connectUserToGitHub(c *serializer.Context, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) connectUserToGitHub(c *Context, w http.ResponseWriter, r *http.Request) { privateAllowed := false pValBool, _ := strconv.ParseBool(r.URL.Query().Get("private")) if pValBool { @@ -237,13 +255,13 @@ func (p *Plugin) connectUserToGitHub(c *serializer.Context, w http.ResponseWrite conf := p.getOAuthConfig(privateAllowed) - state := serializer.OAuthState{ + state := OAuthState{ UserID: c.UserID, Token: model.NewId()[:15], PrivateAllowed: privateAllowed, } - _, err := p.store.Set(githubOauthKey+state.Token, state, pluginapi.SetExpiry(constants.TokenTTL)) + _, err := p.store.Set(githubOauthKey+state.Token, state, pluginapi.SetExpiry(tokenTTL)) if err != nil { http.Error(w, "error setting stored state", http.StatusBadRequest) return @@ -283,7 +301,7 @@ func (p *Plugin) connectUserToGitHub(c *serializer.Context, w http.ResponseWrite http.Redirect(w, r, url, http.StatusFound) } -func (p *Plugin) completeConnectUserToGitHub(c *serializer.Context, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) completeConnectUserToGitHub(c *Context, w http.ResponseWriter, r *http.Request) { var rErr error defer func() { p.oauthBroker.publishOAuthComplete(c.UserID, rErr, false) @@ -298,7 +316,7 @@ func (p *Plugin) completeConnectUserToGitHub(c *serializer.Context, w http.Respo stateToken := r.URL.Query().Get("state") - var state serializer.OAuthState + var state OAuthState if err := p.store.Get(fmt.Sprintf("%s%s", githubOauthKey, stateToken), &state); err != nil { c.Log.Warnf("Failed to get state token", "error", err.Error()) rErr = errors.Wrap(err, "missing stored state") @@ -327,7 +345,7 @@ func (p *Plugin) completeConnectUserToGitHub(c *serializer.Context, w http.Respo conf := p.getOAuthConfig(state.PrivateAllowed) - ctx, cancel := context.WithTimeout(context.Background(), constants.OauthCompleteTimeout) + ctx, cancel := context.WithTimeout(context.Background(), oauthCompleteTimeout) defer cancel() tok, err := conf.Exchange(ctx, code) @@ -352,12 +370,12 @@ func (p *Plugin) completeConnectUserToGitHub(c *serializer.Context, w http.Respo // track the successful connection p.TrackUserEvent("account_connected", c.UserID, nil) - userInfo := &serializer.GitHubUserInfo{ + userInfo := &GitHubUserInfo{ UserID: state.UserID, Token: tok, GitHubUsername: gitUser.GetLogin(), LastToDoPostAt: model.GetMillis(), - Settings: &serializer.UserSettings{ + Settings: &UserSettings{ SidebarButtons: settingButtonsTeam, DailyReminder: true, Notifications: true, @@ -458,23 +476,23 @@ func (p *Plugin) completeConnectUserToGitHub(c *serializer.Context, w http.Respo } } -func (p *Plugin) getGitHubUser(c *serializer.Context, w http.ResponseWriter, r *http.Request) { - req := &serializer.GitHubUserRequest{} +func (p *Plugin) getGitHubUser(c *Context, w http.ResponseWriter, r *http.Request) { + req := &GitHubUserRequest{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { c.Log.WithError(err).Warnf("Error decoding GitHubUserRequest from JSON body") - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) return } if req.UserID == "" { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a JSON object with a non-blank user_id field.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a JSON object with a non-blank user_id field.", StatusCode: http.StatusBadRequest}) return } userInfo, apiErr := p.getGitHubUserInfo(req.UserID) if apiErr != nil { - if apiErr.ID == constants.APIErrorIDNotConnected { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "User is not connected to a GitHub account.", StatusCode: http.StatusNotFound}) + if apiErr.ID == apiErrorIDNotConnected { + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "User is not connected to a GitHub account.", StatusCode: http.StatusNotFound}) } else { p.writeAPIError(w, apiErr) } @@ -482,17 +500,17 @@ func (p *Plugin) getGitHubUser(c *serializer.Context, w http.ResponseWriter, r * } if userInfo == nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "User is not connected to a GitHub account.", StatusCode: http.StatusNotFound}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "User is not connected to a GitHub account.", StatusCode: http.StatusNotFound}) return } - resp := &serializer.GitHubUserResponse{Username: userInfo.GitHubUsername} + resp := &GitHubUserResponse{Username: userInfo.GitHubUsername} p.writeJSON(w, resp) } -func (p *Plugin) getConnected(c *serializer.Context, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getConnected(c *Context, w http.ResponseWriter, r *http.Request) { config := p.getConfiguration() - resp := &serializer.ConnectedResponse{ + resp := &ConnectedResponse{ Connected: false, EnterpriseBaseURL: config.EnterpriseBaseURL, Organization: config.GitHubOrg, @@ -565,7 +583,7 @@ func (p *Plugin) getConnected(c *serializer.Context, w http.ResponseWriter, r *h p.writeJSON(w, resp) } -func (p *Plugin) getMentions(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getMentions(c *UserContext, w http.ResponseWriter, r *http.Request) { config := p.getConfiguration() githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) @@ -581,7 +599,7 @@ func (p *Plugin) getMentions(c *serializer.UserContext, w http.ResponseWriter, r p.writeJSON(w, result.Issues) } -func (p *Plugin) getUnreadsData(c *serializer.UserContext) []*serializer.FilteredNotification { +func (p *Plugin) getUnreadsData(c *UserContext) []*FilteredNotification { githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) notifications, _, err := githubClient.Activity.ListNotifications(c.Ctx, &github.NotificationListOptions{}) @@ -590,7 +608,7 @@ func (p *Plugin) getUnreadsData(c *serializer.UserContext) []*serializer.Filtere return nil } - filteredNotifications := []*serializer.FilteredNotification{} + filteredNotifications := []*FilteredNotification{} for _, n := range notifications { if n.GetReason() == notificationReasonSubscribed { continue @@ -608,7 +626,7 @@ func (p *Plugin) getUnreadsData(c *serializer.UserContext) []*serializer.Filtere subjectURL = n.GetSubject().GetLatestCommentURL() } - filteredNotifications = append(filteredNotifications, &serializer.FilteredNotification{ + filteredNotifications = append(filteredNotifications, &FilteredNotification{ Notification: *n, HTMLURL: fixGithubNotificationSubjectURL(subjectURL, issueNum), }) @@ -617,17 +635,17 @@ func (p *Plugin) getUnreadsData(c *serializer.UserContext) []*serializer.Filtere return filteredNotifications } -func (p *Plugin) getPrsDetails(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getPrsDetails(c *UserContext, w http.ResponseWriter, r *http.Request) { githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) - var prList []*serializer.PRDetails + var prList []*PRDetails if err := json.NewDecoder(r.Body).Decode(&prList); err != nil { c.Log.WithError(err).Warnf("Error decoding PRDetails JSON body") - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) return } - prDetails := make([]*serializer.PRDetails, len(prList)) + prDetails := make([]*PRDetails, len(prList)) var wg sync.WaitGroup for i, pr := range prList { i := i @@ -645,7 +663,7 @@ func (p *Plugin) getPrsDetails(c *serializer.UserContext, w http.ResponseWriter, p.writeJSON(w, prDetails) } -func (p *Plugin) fetchPRDetails(c *serializer.UserContext, client *github.Client, prURL string, prNumber int) *serializer.PRDetails { +func (p *Plugin) fetchPRDetails(c *UserContext, client *github.Client, prURL string, prNumber int) *PRDetails { var status string var mergeable bool // Initialize to a non-nil slice to simplify JSON handling semantics @@ -692,7 +710,7 @@ func (p *Plugin) fetchPRDetails(c *serializer.UserContext, client *github.Client }() wg.Wait() - return &serializer.PRDetails{ + return &PRDetails{ URL: prURL, Number: prNumber, Status: status, @@ -702,7 +720,7 @@ func (p *Plugin) fetchPRDetails(c *serializer.UserContext, client *github.Client } } -func fetchReviews(c *serializer.UserContext, client *github.Client, repoOwner string, repoName string, number int) ([]*github.PullRequestReview, error) { +func fetchReviews(c *UserContext, client *github.Client, repoOwner string, repoName string, number int) ([]*github.PullRequestReview, error) { reviewsList, _, err := client.PullRequests.ListReviews(c.Ctx, repoOwner, repoName, number, nil) if err != nil { @@ -717,7 +735,7 @@ func getRepoOwnerAndNameFromURL(url string) (string, string) { return splitted[len(splitted)-2], splitted[len(splitted)-1] } -func (p *Plugin) searchIssues(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) searchIssues(c *UserContext, w http.ResponseWriter, r *http.Request) { config := p.getConfiguration() githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) @@ -758,36 +776,45 @@ func getFailReason(code int, repo string, username string) string { return cause } -func (p *Plugin) createIssueComment(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { - req := &serializer.CreateIssueCommentRequest{} +func (p *Plugin) createIssueComment(c *UserContext, w http.ResponseWriter, r *http.Request) { + type CreateIssueCommentRequest struct { + PostID string `json:"post_id"` + Owner string `json:"owner"` + Repo string `json:"repo"` + Number int `json:"number"` + Comment string `json:"comment"` + ShowAttachedMessage bool `json:"show_attached_message"` + } + + req := &CreateIssueCommentRequest{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { c.Log.WithError(err).Warnf("Error decoding CreateIssueCommentRequest JSON body") - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) return } if req.PostID == "" { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid post id", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid post id", StatusCode: http.StatusBadRequest}) return } if req.Owner == "" { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid repository owner.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid repository owner.", StatusCode: http.StatusBadRequest}) return } if req.Repo == "" { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid repository.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid repository.", StatusCode: http.StatusBadRequest}) return } if req.Number == 0 { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid issue number.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid issue number.", StatusCode: http.StatusBadRequest}) return } if req.Comment == "" { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid non empty comment.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid non empty comment.", StatusCode: http.StatusBadRequest}) return } @@ -795,17 +822,17 @@ func (p *Plugin) createIssueComment(c *serializer.UserContext, w http.ResponseWr post, err := p.client.Post.GetPost(req.PostID) if err != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", req.PostID), StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", req.PostID), StatusCode: http.StatusInternalServerError}) return } if post == nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", req.PostID), StatusCode: http.StatusNotFound}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", req.PostID), StatusCode: http.StatusNotFound}) return } commentUsername, err := p.getUsername(post.UserId) if err != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) return } @@ -826,7 +853,7 @@ func (p *Plugin) createIssueComment(c *serializer.UserContext, w http.ResponseWr if rawResponse != nil { statusCode = rawResponse.StatusCode } - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create an issue comment: %s", getFailReason(statusCode, req.Repo, currentUsername)), StatusCode: statusCode}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create an issue comment: %s", getFailReason(statusCode, req.Repo, currentUsername)), StatusCode: statusCode}) return } @@ -850,14 +877,14 @@ func (p *Plugin) createIssueComment(c *serializer.UserContext, w http.ResponseWr err = p.client.Post.CreatePost(reply) if err != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create the notification post %s", req.PostID), StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create the notification post %s", req.PostID), StatusCode: http.StatusInternalServerError}) return } p.writeJSON(w, result) } -func (p *Plugin) getLHSData(c *serializer.UserContext) (reviewResp []*github.Issue, assignmentResp []*github.Issue, openPRResp []*github.Issue, err error) { +func (p *Plugin) getLHSData(c *UserContext) (reviewResp []*github.Issue, assignmentResp []*github.Issue, openPRResp []*github.Issue, err error) { graphQLClient := p.graphQLConnect(c.GHInfo) reviewResp, assignmentResp, openPRResp, err = graphQLClient.GetLHSData(c.Context.Ctx) @@ -868,13 +895,13 @@ func (p *Plugin) getLHSData(c *serializer.UserContext) (reviewResp []*github.Iss return reviewResp, assignmentResp, openPRResp, nil } -func (p *Plugin) getSidebarData(c *serializer.UserContext) (*serializer.SidebarContent, error) { +func (p *Plugin) getSidebarData(c *UserContext) (*SidebarContent, error) { reviewResp, assignmentResp, openPRResp, err := p.getLHSData(c) if err != nil { return nil, err } - return &serializer.SidebarContent{ + return &SidebarContent{ PRs: openPRResp, Assignments: assignmentResp, Reviews: reviewResp, @@ -882,7 +909,7 @@ func (p *Plugin) getSidebarData(c *serializer.UserContext) (*serializer.SidebarC }, nil } -func (p *Plugin) getSidebarContent(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getSidebarContent(c *UserContext, w http.ResponseWriter, r *http.Request) { sidebarContent, err := p.getSidebarData(c) if err != nil { c.Log.WithError(err).Warnf("Failed to search for the sidebar data") @@ -892,14 +919,14 @@ func (p *Plugin) getSidebarContent(c *serializer.UserContext, w http.ResponseWri p.writeJSON(w, sidebarContent) } -func (p *Plugin) postToDo(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) postToDo(c *UserContext, w http.ResponseWriter, r *http.Request) { githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) username := c.GHInfo.GitHubUsername text, err := p.GetToDo(c.Ctx, username, githubClient) if err != nil { c.Log.WithError(err).Warnf("Failed to get Todos") - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Encountered an error getting the to do items.", StatusCode: http.StatusUnauthorized}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Encountered an error getting the to do items.", StatusCode: http.StatusUnauthorized}) return } @@ -912,8 +939,8 @@ func (p *Plugin) postToDo(c *serializer.UserContext, w http.ResponseWriter, r *h p.writeJSON(w, resp) } -func (p *Plugin) updateSettings(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { - var settings *serializer.UserSettings +func (p *Plugin) updateSettings(c *UserContext, w http.ResponseWriter, r *http.Request) { + var settings *UserSettings if err := json.NewDecoder(r.Body).Decode(&settings); err != nil { c.Log.WithError(err).Warnf("Error decoding settings from JSON body") http.Error(w, "Invalid request body", http.StatusBadRequest) @@ -937,15 +964,15 @@ func (p *Plugin) updateSettings(c *serializer.UserContext, w http.ResponseWriter p.writeJSON(w, info.Settings) } -func (p *Plugin) getIssueInfo(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { - owner := r.FormValue(constants.OwnerQueryParam) - repo := r.FormValue(constants.RepoQueryParam) - number := r.FormValue(constants.NumberQueryParam) - postID := r.FormValue(constants.PostIDQueryParam) +func (p *Plugin) getIssueInfo(c *UserContext, w http.ResponseWriter, r *http.Request) { + owner := r.FormValue(ownerQueryParam) + repo := r.FormValue(repoQueryParam) + number := r.FormValue(numberQueryParam) + postID := r.FormValue(postIDQueryParam) issueNumber, err := strconv.Atoi(number) if err != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Invalid param 'number'.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{Message: "Invalid param 'number'.", StatusCode: http.StatusBadRequest}) return } @@ -970,7 +997,7 @@ func (p *Plugin) getIssueInfo(c *serializer.UserContext, w http.ResponseWriter, "repo": repo, "number": issueNumber, }).Debugf("Could not get the issue") - p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Could not get the issue", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{Message: "Could not get the issue", StatusCode: http.StatusInternalServerError}) return } @@ -999,11 +1026,11 @@ func (p *Plugin) getIssueInfo(c *serializer.UserContext, w http.ResponseWriter, post, appErr := p.API.GetPost(postID) if appErr != nil { p.client.Log.Error("Unable to get the post", "PostID", postID, "Error", appErr.Error()) - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", postID), StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", postID), StatusCode: http.StatusInternalServerError}) return } if post == nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", postID), StatusCode: http.StatusNotFound}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", postID), StatusCode: http.StatusNotFound}) return } @@ -1023,13 +1050,13 @@ func (p *Plugin) getIssueInfo(c *serializer.UserContext, w http.ResponseWriter, p.writeJSON(w, issueInfo) } -func (p *Plugin) getIssueByNumber(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { - owner := r.FormValue(constants.OwnerQueryParam) - repo := r.FormValue(constants.RepoQueryParam) - number := r.FormValue(constants.NumberQueryParam) +func (p *Plugin) getIssueByNumber(c *UserContext, w http.ResponseWriter, r *http.Request) { + owner := r.FormValue(ownerQueryParam) + repo := r.FormValue(repoQueryParam) + number := r.FormValue(numberQueryParam) issueNumber, err := strconv.Atoi(number) if err != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Invalid param 'number'.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{Message: "Invalid param 'number'.", StatusCode: http.StatusBadRequest}) return } @@ -1055,7 +1082,7 @@ func (p *Plugin) getIssueByNumber(c *serializer.UserContext, w http.ResponseWrit "repo": repo, "number": issueNumber, }).Debugf("Could not get the issue") - p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Could not get the issue", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{Message: "Could not get the issue", StatusCode: http.StatusInternalServerError}) return } @@ -1065,14 +1092,14 @@ func (p *Plugin) getIssueByNumber(c *serializer.UserContext, w http.ResponseWrit p.writeJSON(w, result) } -func (p *Plugin) getPrByNumber(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { - owner := r.FormValue(constants.OwnerQueryParam) - repo := r.FormValue(constants.RepoQueryParam) - number := r.FormValue(constants.NumberQueryParam) +func (p *Plugin) getPrByNumber(c *UserContext, w http.ResponseWriter, r *http.Request) { + owner := r.FormValue(ownerQueryParam) + repo := r.FormValue(repoQueryParam) + number := r.FormValue(numberQueryParam) prNumber, err := strconv.Atoi(number) if err != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Invalid param 'number'.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{Message: "Invalid param 'number'.", StatusCode: http.StatusBadRequest}) return } @@ -1099,7 +1126,7 @@ func (p *Plugin) getPrByNumber(c *serializer.UserContext, w http.ResponseWriter, "repo": repo, "number": prNumber, }).Debugf("Could not get pull request") - p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Could not get pull request", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{Message: "Could not get pull request", StatusCode: http.StatusInternalServerError}) return } if result.Body != nil { @@ -1108,10 +1135,10 @@ func (p *Plugin) getPrByNumber(c *serializer.UserContext, w http.ResponseWriter, p.writeJSON(w, result) } -func (p *Plugin) getLabels(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getLabels(c *UserContext, w http.ResponseWriter, r *http.Request) { owner, repo, err := parseRepo(r.URL.Query().Get("repo")) if err != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) return } @@ -1123,7 +1150,7 @@ func (p *Plugin) getLabels(c *serializer.UserContext, w http.ResponseWriter, r * labels, resp, err := githubClient.Issues.ListLabels(c.Ctx, owner, repo, &opt) if err != nil { c.Log.WithError(err).Warnf("Failed to list labels") - p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Failed to fetch labels", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch labels", StatusCode: http.StatusInternalServerError}) return } allLabels = append(allLabels, labels...) @@ -1136,10 +1163,10 @@ func (p *Plugin) getLabels(c *serializer.UserContext, w http.ResponseWriter, r * p.writeJSON(w, allLabels) } -func (p *Plugin) getAssignees(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getAssignees(c *UserContext, w http.ResponseWriter, r *http.Request) { owner, repo, err := parseRepo(r.URL.Query().Get("repo")) if err != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) return } @@ -1151,7 +1178,7 @@ func (p *Plugin) getAssignees(c *serializer.UserContext, w http.ResponseWriter, assignees, resp, err := githubClient.Issues.ListAssignees(c.Ctx, owner, repo, &opt) if err != nil { c.Log.WithError(err).Warnf("Failed to list assignees") - p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Failed to fetch assignees", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch assignees", StatusCode: http.StatusInternalServerError}) return } allAssignees = append(allAssignees, assignees...) @@ -1164,10 +1191,10 @@ func (p *Plugin) getAssignees(c *serializer.UserContext, w http.ResponseWriter, p.writeJSON(w, allAssignees) } -func (p *Plugin) getMilestones(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getMilestones(c *UserContext, w http.ResponseWriter, r *http.Request) { owner, repo, err := parseRepo(r.URL.Query().Get("repo")) if err != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) return } @@ -1179,7 +1206,7 @@ func (p *Plugin) getMilestones(c *serializer.UserContext, w http.ResponseWriter, milestones, resp, err := githubClient.Issues.ListMilestones(c.Ctx, owner, repo, &github.MilestoneListOptions{ListOptions: opt}) if err != nil { c.Log.WithError(err).Warnf("Failed to list milestones") - p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Failed to fetch milestones", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch milestones", StatusCode: http.StatusInternalServerError}) return } allMilestones = append(allMilestones, milestones...) @@ -1229,7 +1256,7 @@ func getRepositoryListByOrg(c context.Context, org string, githubClient *github. return allRepos, http.StatusOK, nil } -func (p *Plugin) getRepositories(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) getRepositories(c *UserContext, w http.ResponseWriter, r *http.Request) { githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) org := p.getConfiguration().GitHubOrg @@ -1243,7 +1270,7 @@ func (p *Plugin) getRepositories(c *serializer.UserContext, w http.ResponseWrite allRepos, err = getRepositoryList(c.Ctx, "", githubClient, opt) if err != nil { c.Log.WithError(err).Warnf("Failed to list repositories") - p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError}) return } } else { @@ -1253,17 +1280,24 @@ func (p *Plugin) getRepositories(c *serializer.UserContext, w http.ResponseWrite allRepos, err = getRepositoryList(c.Ctx, org, githubClient, opt) if err != nil { c.Log.WithError(err).Warnf("Failed to list repositories") - p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError}) return } } else { c.Log.WithError(err).Warnf("Failed to list repositories") - p.writeAPIError(w, &serializer.APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError}) return } } } - resp := make([]serializer.RepositoryResponse, len(allRepos)) + + type RepositoryResponse struct { + Name string `json:"name,omitempty"` + FullName string `json:"full_name,omitempty"` + Permissions map[string]bool `json:"permissions,omitempty"` + } + + resp := make([]RepositoryResponse, len(allRepos)) for i, r := range allRepos { resp[i].Name = r.GetName() resp[i].FullName = r.GetFullName() @@ -1273,12 +1307,12 @@ func (p *Plugin) getRepositories(c *serializer.UserContext, w http.ResponseWrite p.writeJSON(w, resp) } -func (p *Plugin) updateIssue(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) updateIssue(c *UserContext, w http.ResponseWriter, r *http.Request) { // get data for the issue from the request body and fill UpdateIssueRequest to update the issue - issue := &serializer.UpdateIssueRequest{} + issue := &UpdateIssueRequest{} if err := json.NewDecoder(r.Body).Decode(&issue); err != nil { c.Log.WithError(err).Warnf("Error decoding the JSON body") - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) return } @@ -1293,11 +1327,11 @@ func (p *Plugin) updateIssue(c *serializer.UserContext, w http.ResponseWriter, r post, appErr = p.API.GetPost(issue.PostID) if appErr != nil { p.client.Log.Error("Unable to get the post", "PostID", issue.PostID, "Error", appErr.Error()) - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) return } if post == nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", issue.PostID), StatusCode: http.StatusNotFound}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", issue.PostID), StatusCode: http.StatusNotFound}) return } permalink = p.getPermaLink(issue.PostID) @@ -1319,13 +1353,13 @@ func (p *Plugin) updateIssue(c *serializer.UserContext, w http.ResponseWriter, r currentUser, appErr := p.API.GetUser(c.UserID) if appErr != nil { p.client.Log.Error("Unable to get the user", "UserID", c.UserID, "Error", appErr.Error()) - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "failed to load current user", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load current user", StatusCode: http.StatusInternalServerError}) return } splittedRepo := strings.Split(issue.Repo, "/") if len(splittedRepo) < 2 { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid repository", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid repository", StatusCode: http.StatusBadRequest}) } owner, repoName := splittedRepo[0], splittedRepo[1] @@ -1334,12 +1368,12 @@ func (p *Plugin) updateIssue(c *serializer.UserContext, w http.ResponseWriter, r result, resp, err := githubClient.Issues.Edit(c.Ctx, owner, repoName, issue.IssueNumber, githubIssue) if err != nil { if resp != nil && resp.Response.StatusCode == http.StatusGone { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Issues are disabled on this repository.", StatusCode: http.StatusMethodNotAllowed}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Issues are disabled on this repository.", StatusCode: http.StatusMethodNotAllowed}) return } c.Log.WithError(err).Warnf("Failed to update the issue") - p.writeAPIError(w, &serializer.APIErrorResponse{ + p.writeAPIError(w, &APIErrorResponse{ ID: "", Message: fmt.Sprintf("failed to update the issue: %s", getFailReason(resp.StatusCode, issue.Repo, @@ -1375,7 +1409,7 @@ func (p *Plugin) updateIssue(c *serializer.UserContext, w http.ResponseWriter, r } if appErr != nil { c.Log.WithError(appErr).Warnf("failed to create the notification post") - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create the notification post, postID: %s, channelID: %s", issue.PostID, channelID), StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create the notification post, postID: %s, channelID: %s", issue.PostID, channelID), StatusCode: http.StatusInternalServerError}) return } @@ -1383,62 +1417,84 @@ func (p *Plugin) updateIssue(c *serializer.UserContext, w http.ResponseWriter, r p.writeJSON(w, result) } -func (p *Plugin) closeOrReopenIssue(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { - req := &serializer.CommentAndCloseRequest{} +func (p *Plugin) closeOrReopenIssue(c *UserContext, w http.ResponseWriter, r *http.Request) { + type CommentAndCloseRequest struct { + ChannelID string `json:"channel_id"` + IssueComment string `json:"issue_comment"` + StatusReason string `json:"status_reason"` + Number int `json:"number"` + Owner string `json:"owner"` + Repository string `json:"repo"` + Status string `json:"status"` + PostID string `json:"postId"` + } + + req := &CommentAndCloseRequest{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { c.Log.WithError(err).Warnf("Error decoding the JSON body") - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) return } post, appErr := p.API.GetPost(req.PostID) if appErr != nil { p.client.Log.Error("Unable to get the post", "PostID", req.PostID, "Error", appErr.Error()) - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", req.PostID), StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", req.PostID), StatusCode: http.StatusInternalServerError}) return } if post == nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", req.PostID), StatusCode: http.StatusNotFound}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", req.PostID), StatusCode: http.StatusNotFound}) return } if _, err := p.getUsername(post.UserId); err != nil { p.client.Log.Error("Unable to get the username", "UserID", post.UserId, "Error", err.Error()) - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) return } if req.IssueComment != "" { p.CreateCommentToIssue(c, w, req.IssueComment, req.Owner, req.Repository, post, req.Number) } - if req.Status == constants.Close { - p.CloseOrReopenIssue(c, w, constants.IssueClose, req.StatusReason, req.Owner, req.Repository, post, req.Number) + if req.Status == statusClose { + p.CloseOrReopenIssue(c, w, issueClose, req.StatusReason, req.Owner, req.Repository, post, req.Number) } else { - p.CloseOrReopenIssue(c, w, constants.IssueOpen, req.StatusReason, req.Owner, req.Repository, post, req.Number) + p.CloseOrReopenIssue(c, w, issueOpen, req.StatusReason, req.Owner, req.Repository, post, req.Number) } } -func (p *Plugin) createIssue(c *serializer.UserContext, w http.ResponseWriter, r *http.Request) { +func (p *Plugin) createIssue(c *UserContext, w http.ResponseWriter, r *http.Request) { + type CreateIssueRequest struct { + Title string `json:"title"` + Body string `json:"body"` + Repo string `json:"repo"` + PostID string `json:"post_id"` + ChannelID string `json:"channel_id"` + Labels []string `json:"labels"` + Assignees []string `json:"assignees"` + Milestone int `json:"milestone"` + } + // get data for the issue from the request body and fill CreateIssueRequest object to create the issue - issue := &serializer.CreateIssueRequest{} + issue := &CreateIssueRequest{} if err := json.NewDecoder(r.Body).Decode(&issue); err != nil { c.Log.WithError(err).Warnf("Error decoding the JSON body") - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) return } if issue.Title == "" { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid issue title.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid issue title.", StatusCode: http.StatusBadRequest}) return } if issue.Repo == "" { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid repository name.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid repository name.", StatusCode: http.StatusBadRequest}) return } if issue.PostID == "" && issue.ChannelID == "" { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide either a postID or a channelID", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide either a postID or a channelID", StatusCode: http.StatusBadRequest}) return } @@ -1449,17 +1505,17 @@ func (p *Plugin) createIssue(c *serializer.UserContext, w http.ResponseWriter, r var err error post, err = p.client.Post.GetPost(issue.PostID) if err != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) return } if post == nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", issue.PostID), StatusCode: http.StatusNotFound}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", issue.PostID), StatusCode: http.StatusNotFound}) return } username, err := p.getUsername(post.UserId) if err != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) return } @@ -1488,7 +1544,7 @@ func (p *Plugin) createIssue(c *serializer.UserContext, w http.ResponseWriter, r currentUser, err := p.client.User.Get(c.UserID) if err != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "failed to load current user", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load current user", StatusCode: http.StatusInternalServerError}) return } @@ -1499,12 +1555,12 @@ func (p *Plugin) createIssue(c *serializer.UserContext, w http.ResponseWriter, r result, resp, err := githubClient.Issues.Create(c.Ctx, owner, repoName, githubIssue) if err != nil { if resp != nil && resp.Response.StatusCode == http.StatusGone { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Issues are disabled on this repository.", StatusCode: http.StatusMethodNotAllowed}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Issues are disabled on this repository.", StatusCode: http.StatusMethodNotAllowed}) return } c.Log.WithError(err).Warnf("Failed to create issue") - p.writeAPIError(w, &serializer.APIErrorResponse{ + p.writeAPIError(w, &APIErrorResponse{ ID: "", Message: fmt.Sprintf("failed to create issue: %s", getFailReason(resp.StatusCode, issue.Repo, currentUser.Username)), StatusCode: resp.StatusCode, @@ -1537,7 +1593,7 @@ func (p *Plugin) createIssue(c *serializer.UserContext, w http.ResponseWriter, r } if err != nil { c.Log.WithError(err).Warnf("failed to create notification post") - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "failed to create notification post, postID: " + issue.PostID + ", channelID: " + channelID, StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to create notification post, postID: " + issue.PostID + ", channelID: " + channelID, StatusCode: http.StatusInternalServerError}) return } diff --git a/server/plugin/api_test.go b/server/plugin/api_test.go index fe013ec55..7a3a560de 100644 --- a/server/plugin/api_test.go +++ b/server/plugin/api_test.go @@ -13,8 +13,6 @@ import ( "github.com/mattermost/mattermost/server/public/plugin/plugintest" "github.com/mattermost/mattermost/server/public/pluginapi" - "github.com/mattermost/mattermost-plugin-github/server/constants" - "github.com/mattermost/mattermost-plugin-github/server/serializer" "github.com/mattermost/mattermost-plugin-github/server/testutils" ) @@ -84,7 +82,7 @@ func TestPlugin_ServeHTTP(t *testing.T) { expectedResponse: testutils.ExpectedResponse{ StatusCode: http.StatusUnauthorized, ResponseType: testutils.ContentTypeJSON, - Body: serializer.APIErrorResponse{ID: "", Message: "Not authorized.", StatusCode: http.StatusUnauthorized}, + Body: APIErrorResponse{ID: "", Message: "Not authorized.", StatusCode: http.StatusUnauthorized}, }, userID: "", }, "unauthorized test http": { @@ -120,7 +118,7 @@ func TestPlugin_ServeHTTP(t *testing.T) { p.SetAPI(&plugintest.API{}) req := test.httpTest.CreateHTTPRequest(test.request) - req.Header.Add(constants.HeaderMattermostUserID, test.userID) + req.Header.Add(headerMattermostUserID, test.userID) rr := httptest.NewRecorder() p.ServeHTTP(&plugin.Context{}, rr, req) test.httpTest.CompareHTTPResponse(rr, test.expectedResponse) diff --git a/server/plugin/command.go b/server/plugin/command.go index 2e247b125..a14970c5c 100644 --- a/server/plugin/command.go +++ b/server/plugin/command.go @@ -12,9 +12,6 @@ import ( "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/plugin" "github.com/mattermost/mattermost/server/public/pluginapi/experimental/command" - - "github.com/mattermost/mattermost-plugin-github/server/constants" - "github.com/mattermost/mattermost-plugin-github/server/serializer" ) const ( @@ -133,7 +130,7 @@ func (p *Plugin) postCommandResponse(args *model.CommandArgs, text string) { p.client.Post.SendEphemeralPost(args.UserId, post) } -func (p *Plugin) getMutedUsernames(userInfo *serializer.GitHubUserInfo) []string { +func (p *Plugin) getMutedUsernames(userInfo *GitHubUserInfo) []string { var mutedUsernameBytes []byte err := p.store.Get(userInfo.UserID+"-muted-users", &mutedUsernameBytes) if err != nil { @@ -148,7 +145,7 @@ func (p *Plugin) getMutedUsernames(userInfo *serializer.GitHubUserInfo) []string return mutedUsers } -func (p *Plugin) handleMuteList(args *model.CommandArgs, userInfo *serializer.GitHubUserInfo) string { +func (p *Plugin) handleMuteList(args *model.CommandArgs, userInfo *GitHubUserInfo) string { mutedUsernames := p.getMutedUsernames(userInfo) var mutedUsers string for _, user := range mutedUsernames { @@ -169,7 +166,7 @@ func contains(s []string, e string) bool { return false } -func (p *Plugin) handleMuteAdd(args *model.CommandArgs, username string, userInfo *serializer.GitHubUserInfo) string { +func (p *Plugin) handleMuteAdd(args *model.CommandArgs, username string, userInfo *GitHubUserInfo) string { mutedUsernames := p.getMutedUsernames(userInfo) if contains(mutedUsernames, username) { return username + " is already muted" @@ -195,7 +192,7 @@ func (p *Plugin) handleMuteAdd(args *model.CommandArgs, username string, userInf return fmt.Sprintf("`%v`", username) + " is now muted. You'll no longer receive notifications for comments in your PRs and issues." } -func (p *Plugin) handleUnmute(args *model.CommandArgs, username string, userInfo *serializer.GitHubUserInfo) string { +func (p *Plugin) handleUnmute(args *model.CommandArgs, username string, userInfo *GitHubUserInfo) string { mutedUsernames := p.getMutedUsernames(userInfo) userToMute := []string{username} newMutedList := arrayDifference(mutedUsernames, userToMute) @@ -208,7 +205,7 @@ func (p *Plugin) handleUnmute(args *model.CommandArgs, username string, userInfo return fmt.Sprintf("`%v`", username) + " is no longer muted" } -func (p *Plugin) handleUnmuteAll(args *model.CommandArgs, userInfo *serializer.GitHubUserInfo) string { +func (p *Plugin) handleUnmuteAll(args *model.CommandArgs, userInfo *GitHubUserInfo) string { _, err := p.store.Set(userInfo.UserID+"-muted-users", []byte("")) if err != nil { return "Error occurred unmuting users" @@ -217,7 +214,7 @@ func (p *Plugin) handleUnmuteAll(args *model.CommandArgs, userInfo *serializer.G return "Unmuted all users" } -func (p *Plugin) handleMuteCommand(_ *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *serializer.GitHubUserInfo) string { +func (p *Plugin) handleMuteCommand(_ *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string { if len(parameters) == 0 { return "Invalid mute command. Available commands are 'list', 'add' and 'delete'." } @@ -259,7 +256,7 @@ func arrayDifference(a, b []string) []string { return diff } -func (p *Plugin) handleSubscribe(c *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *serializer.GitHubUserInfo) string { +func (p *Plugin) handleSubscribe(c *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string { switch { case len(parameters) == 0: return "Please specify a repository or 'list' command." @@ -270,7 +267,7 @@ func (p *Plugin) handleSubscribe(c *plugin.Context, args *model.CommandArgs, par } } -func (p *Plugin) handleSubscriptions(c *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *serializer.GitHubUserInfo) string { +func (p *Plugin) handleSubscriptions(c *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string { if len(parameters) == 0 { return "Invalid subscribe command. Available commands are 'list', 'add' and 'delete'." } @@ -290,7 +287,7 @@ func (p *Plugin) handleSubscriptions(c *plugin.Context, args *model.CommandArgs, } } -func (p *Plugin) handleSubscriptionsList(_ *plugin.Context, args *model.CommandArgs, parameters []string, _ *serializer.GitHubUserInfo) string { +func (p *Plugin) handleSubscriptionsList(_ *plugin.Context, args *model.CommandArgs, parameters []string, _ *GitHubUserInfo) string { txt := "" subs, err := p.GetSubscriptionsByChannel(args.ChannelId) if err != nil { @@ -368,7 +365,7 @@ func (p *Plugin) checkIfConfiguredWebhookExists(ctx context.Context, githubClien return found, nil } -func (p *Plugin) handleSubscribesAdd(_ *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *serializer.GitHubUserInfo) string { +func (p *Plugin) handleSubscribesAdd(_ *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string { const errorNoWebhookFound = "\nNo webhook was found for this repository or organization. To create one, enter the following slash command `/github setup webhook`" const errorWebhookToUser = "\nNot able to get the list of webhooks. This feature is not available for subscription to a user." subscriptionEvents := Features("pulls,issues,creates,deletes") @@ -534,7 +531,7 @@ func (p *Plugin) getSubscribedFeatures(channelID, owner, repo string) (Features, return previousFeatures, nil } -func (p *Plugin) handleUnsubscribe(_ *plugin.Context, args *model.CommandArgs, parameters []string, _ *serializer.GitHubUserInfo) string { +func (p *Plugin) handleUnsubscribe(_ *plugin.Context, args *model.CommandArgs, parameters []string, _ *GitHubUserInfo) string { if len(parameters) == 0 { return "Please specify a repository." } @@ -582,12 +579,12 @@ func (p *Plugin) handleUnsubscribe(_ *plugin.Context, args *model.CommandArgs, p return "" } -func (p *Plugin) handleDisconnect(_ *plugin.Context, args *model.CommandArgs, _ []string, _ *serializer.GitHubUserInfo) string { +func (p *Plugin) handleDisconnect(_ *plugin.Context, args *model.CommandArgs, _ []string, _ *GitHubUserInfo) string { p.disconnectGitHubAccount(args.UserId) return "Disconnected your GitHub account." } -func (p *Plugin) handleTodo(_ *plugin.Context, _ *model.CommandArgs, _ []string, userInfo *serializer.GitHubUserInfo) string { +func (p *Plugin) handleTodo(_ *plugin.Context, _ *model.CommandArgs, _ []string, userInfo *GitHubUserInfo) string { githubClient := p.githubConnectUser(context.Background(), userInfo) text, err := p.GetToDo(context.Background(), userInfo.GitHubUsername, githubClient) @@ -599,7 +596,7 @@ func (p *Plugin) handleTodo(_ *plugin.Context, _ *model.CommandArgs, _ []string, return text } -func (p *Plugin) handleMe(_ *plugin.Context, _ *model.CommandArgs, _ []string, userInfo *serializer.GitHubUserInfo) string { +func (p *Plugin) handleMe(_ *plugin.Context, _ *model.CommandArgs, _ []string, userInfo *GitHubUserInfo) string { githubClient := p.githubConnectUser(context.Background(), userInfo) gitUser, _, err := githubClient.Users.Get(context.Background(), "") if err != nil { @@ -610,7 +607,7 @@ func (p *Plugin) handleMe(_ *plugin.Context, _ *model.CommandArgs, _ []string, u return text } -func (p *Plugin) handleHelp(_ *plugin.Context, _ *model.CommandArgs, _ []string, _ *serializer.GitHubUserInfo) string { +func (p *Plugin) handleHelp(_ *plugin.Context, _ *model.CommandArgs, _ []string, _ *GitHubUserInfo) string { message, err := renderTemplate("helpText", p.getConfiguration()) if err != nil { p.client.Log.Warn("Failed to render help template", "error", err.Error()) @@ -620,7 +617,7 @@ func (p *Plugin) handleHelp(_ *plugin.Context, _ *model.CommandArgs, _ []string, return "###### Mattermost GitHub Plugin - Slash Command Help\n" + message } -func (p *Plugin) handleSettings(_ *plugin.Context, _ *model.CommandArgs, parameters []string, userInfo *serializer.GitHubUserInfo) string { +func (p *Plugin) handleSettings(_ *plugin.Context, _ *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string { if len(parameters) < 2 { return "Please specify both a setting and value. Use `/github help` for more usage information." } @@ -685,7 +682,7 @@ func (p *Plugin) handleSettings(_ *plugin.Context, _ *model.CommandArgs, paramet return "Settings updated." } -func (p *Plugin) handleIssue(_ *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *serializer.GitHubUserInfo) string { +func (p *Plugin) handleIssue(_ *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string { if len(parameters) == 0 { return "Invalid issue command. Available command is 'create'." } @@ -739,7 +736,7 @@ func (p *Plugin) handleSetup(c *plugin.Context, args *model.CommandArgs, paramet return "" } -type CommandHandleFunc func(c *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *serializer.GitHubUserInfo) string +type CommandHandleFunc func(c *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string func (p *Plugin) isAuthorizedSysAdmin(userID string) (bool, error) { user, err := p.client.User.Get(userID) @@ -838,7 +835,7 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo info, apiErr := p.getGitHubUserInfo(args.UserId) if apiErr != nil { text := "Unknown error." - if apiErr.ID == constants.APIErrorIDNotConnected { + if apiErr.ID == apiErrorIDNotConnected { text = "You must connect your account to GitHub first. Either click on the GitHub logo in the bottom left of the screen or enter `/github connect`." } p.postCommandResponse(args, text) diff --git a/server/serializer/error.go b/server/plugin/error.go similarity index 91% rename from server/serializer/error.go rename to server/plugin/error.go index 1e2974f51..3919c2cff 100644 --- a/server/serializer/error.go +++ b/server/plugin/error.go @@ -1,4 +1,4 @@ -package serializer +package plugin type APIErrorResponse struct { ID string `json:"id"` diff --git a/server/plugin/mm_34646_token_refresh.go b/server/plugin/mm_34646_token_refresh.go index b4d38c9a2..60eb6f4fe 100644 --- a/server/plugin/mm_34646_token_refresh.go +++ b/server/plugin/mm_34646_token_refresh.go @@ -7,8 +7,6 @@ import ( "github.com/google/go-github/v54/github" "github.com/pkg/errors" - "github.com/mattermost/mattermost-plugin-github/server/serializer" - "github.com/mattermost/mattermost/server/public/pluginapi/cluster" ) @@ -46,7 +44,7 @@ func (p *Plugin) forceResetAllMM34646() error { } for _, key := range keys { - var tryInfo serializer.GitHubUserInfo + var tryInfo GitHubUserInfo err = p.store.Get(key, &tryInfo) if err != nil { p.client.Log.Warn("failed to inspect key", "key", key, "error", @@ -92,7 +90,7 @@ func (p *Plugin) forceResetAllMM34646() error { return nil } -func (p *Plugin) forceResetUserTokenMM34646(ctx context.Context, config *Configuration, info *serializer.GitHubUserInfo) (string, error) { +func (p *Plugin) forceResetUserTokenMM34646(ctx context.Context, config *Configuration, info *GitHubUserInfo) (string, error) { if info.MM34646ResetTokenDone { return info.Token.AccessToken, nil } diff --git a/server/plugin/oauth.go b/server/plugin/oauth.go index 294727fce..7036cc712 100644 --- a/server/plugin/oauth.go +++ b/server/plugin/oauth.go @@ -1,14 +1,72 @@ package plugin import ( + "context" "sync" + + "github.com/mattermost/mattermost/server/public/pluginapi/experimental/bot/logger" + "golang.org/x/oauth2" ) +type Context struct { + Ctx context.Context + UserID string + Log logger.Logger +} + +type GitHubUserRequest struct { + UserID string `json:"user_id"` +} + +type GitHubUserResponse struct { + Username string `json:"username"` +} + +type ConnectedResponse struct { + Connected bool `json:"connected"` + GitHubUsername string `json:"github_username"` + GitHubClientID string `json:"github_client_id"` + EnterpriseBaseURL string `json:"enterprise_base_url,omitempty"` + Organization string `json:"organization"` + UserSettings *UserSettings `json:"user_settings"` + ClientConfiguration map[string]interface{} `json:"configuration"` +} + +type UserSettings struct { + SidebarButtons string `json:"sidebar_buttons"` + DailyReminder bool `json:"daily_reminder"` + DailyReminderOnChange bool `json:"daily_reminder_on_change"` + Notifications bool `json:"notifications"` +} + +type GitHubUserInfo struct { + UserID string + Token *oauth2.Token + GitHubUsername string + LastToDoPostAt int64 + Settings *UserSettings + AllowedPrivateRepos bool + + // MM34646ResetTokenDone is set for a user whose token has been reset for MM-34646. + MM34646ResetTokenDone bool +} + type OAuthCompleteEvent struct { UserID string Err error } +type UserContext struct { + Context + GHInfo *GitHubUserInfo +} + +type OAuthState struct { + UserID string `json:"user_id"` + Token string `json:"token"` + PrivateAllowed bool `json:"private_allowed"` +} + type OAuthBroker struct { sendOAuthCompleteEvent func(event OAuthCompleteEvent) diff --git a/server/plugin/plugin.go b/server/plugin/plugin.go index ee1af71d1..1da17175e 100644 --- a/server/plugin/plugin.go +++ b/server/plugin/plugin.go @@ -10,6 +10,7 @@ import ( "regexp" "strings" "sync" + "time" "github.com/google/go-github/v54/github" "github.com/gorilla/mux" @@ -23,16 +24,17 @@ import ( "github.com/mattermost/mattermost/server/public/pluginapi/experimental/bot/poster" "github.com/mattermost/mattermost/server/public/pluginapi/experimental/telemetry" - "github.com/mattermost/mattermost-plugin-github/server/constants" "github.com/mattermost/mattermost-plugin-github/server/plugin/graphql" - "github.com/mattermost/mattermost-plugin-github/server/serializer" ) const ( - githubTokenKey = "_githubtoken" - githubOauthKey = "githuboauthkey_" - githubUsernameKey = "_githubusername" - githubPrivateRepoKey = "_githubprivate" + githubTokenKey = "_githubtoken" + githubOauthKey = "githuboauthkey_" + githubUsernameKey = "_githubusername" + githubPrivateRepoKey = "_githubprivate" + githubObjectTypeIssue = "issue" + githubObjectTypeIssueComment = "issue_comment" + githubObjectTypePRReviewComment = "pr_review_comment" mm34646MutexKey = "mm34646_token_reset_mutex" mm34646DoneKey = "mm34646_token_reset_done" @@ -57,6 +59,53 @@ const ( dailySummary = "_dailySummary" chimeraGitHubAppIdentifier = "plugin-github" + + apiErrorIDNotConnected = "not_connected" + + // TokenTTL is the OAuth token expiry duration in seconds + tokenTTL = 600 + + requestTimeout = 30 * time.Second + oauthCompleteTimeout = 2 * time.Minute + headerMattermostUserID = "Mattermost-User-ID" + ownerQueryParam = "owner" + repoQueryParam = "repo" + numberQueryParam = "number" + postIDQueryParam = "postId" + + issueStatus = "status" + assigneesForProps = "assignees" + labelsForProps = "labels" + descriptionForProps = "description" + titleForProps = "title" + issueNumberForProps = "issue_number" + issueURLForProps = "issue_url" + repoOwnerForProps = "repo_owner" + repoNameForProps = "repo_name" + + statusClose = "Close" + statusReopen = "Reopen" + + issueCompleted = "completed" + issueNotPlanned = "not_planned" + issueClose = "closed" + issueOpen = "open" + + // Actions of webhook events + actionOpened = "opened" + actionClosed = "closed" + actionReopened = "reopened" + actionSubmitted = "submitted" + actionLabeled = "labeled" + actionAssigned = "assigned" + actionCreated = "created" + actionDeleted = "deleted" + actionEdited = "edited" + actionMarkedReadyForReview = "ready_for_review" + + postPropGithubRepo = "gh_repo" + postPropGithubObjectID = "gh_object_id" + postPropGithubObjectType = "gh_object_type" ) var ( @@ -171,12 +220,12 @@ func (p *Plugin) GetGitHubClient(ctx context.Context, userID string) (*github.Cl return p.githubConnectUser(ctx, userInfo), nil } -func (p *Plugin) githubConnectUser(ctx context.Context, info *serializer.GitHubUserInfo) *github.Client { +func (p *Plugin) githubConnectUser(ctx context.Context, info *GitHubUserInfo) *github.Client { tok := *info.Token return p.githubConnectToken(tok) } -func (p *Plugin) graphQLConnect(info *serializer.GitHubUserInfo) *graphql.Client { +func (p *Plugin) graphQLConnect(info *GitHubUserInfo) *graphql.Client { conf := p.getConfiguration() return graphql.NewClient(p.client.Log, *info.Token, info.GitHubUsername, conf.GitHubOrg, conf.EnterpriseBaseURL) } @@ -310,7 +359,7 @@ func (p *Plugin) getPostPropsForReaction(reaction *model.Reaction) (org, repo st } // Getting the Github repository from notification post props - repo, ok = post.GetProp(constants.PostPropGithubRepo).(string) + repo, ok = post.GetProp(postPropGithubRepo).(string) if !ok || repo == "" { return org, repo, id, objectType, false } @@ -324,13 +373,13 @@ func (p *Plugin) getPostPropsForReaction(reaction *model.Reaction) (org, repo st org, repo = orgRepo[0], orgRepo[1] // Getting the Github object id from notification post props - id, ok = post.GetProp(constants.PostPropGithubObjectID).(float64) + id, ok = post.GetProp(postPropGithubObjectID).(float64) if !ok || id == 0 { return org, repo, id, objectType, false } // Getting the Github object type from notification post props - objectType, ok = post.GetProp(constants.PostPropGithubObjectType).(string) + objectType, ok = post.GetProp(postPropGithubObjectType).(string) if !ok || objectType == "" { return org, repo, id, objectType, false } @@ -352,27 +401,27 @@ func (p *Plugin) ReactionHasBeenAdded(c *plugin.Context, reaction *model.Reactio info, appErr := p.getGitHubUserInfo(reaction.UserId) if appErr != nil { - if appErr.ID != constants.APIErrorIDNotConnected { + if appErr.ID != apiErrorIDNotConnected { p.client.Log.Debug("Error in getting user info", "error", appErr.Error()) } return } - ctx, cancel := context.WithTimeout(context.Background(), constants.RequestTimeout) + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) defer cancel() ghClient := p.githubConnectUser(ctx, info) switch objectType { - case constants.GithubObjectTypeIssueComment: + case githubObjectTypeIssueComment: if _, _, err := ghClient.Reactions.CreateIssueCommentReaction(context.Background(), owner, repo, int64(id), githubEmoji); err != nil { p.client.Log.Debug("Error occurred while creating issue comment reaction", "error", err.Error()) return } - case constants.GithubObjectTypeIssue: + case githubObjectTypeIssue: if _, _, err := ghClient.Reactions.CreateIssueReaction(context.Background(), owner, repo, int(id), githubEmoji); err != nil { p.client.Log.Debug("Error occurred while creating issue reaction", "error", err.Error()) return } - case constants.GithubObjectTypePRReviewComment: + case githubObjectTypePRReviewComment: if _, _, err := ghClient.Reactions.CreatePullRequestCommentReaction(context.Background(), owner, repo, int64(id), githubEmoji); err != nil { p.client.Log.Debug("Error occurred while creating PR review comment reaction", "error", err.Error()) return @@ -396,17 +445,17 @@ func (p *Plugin) ReactionHasBeenRemoved(c *plugin.Context, reaction *model.React info, appErr := p.getGitHubUserInfo(reaction.UserId) if appErr != nil { - if appErr.ID != constants.APIErrorIDNotConnected { + if appErr.ID != apiErrorIDNotConnected { p.client.Log.Debug("Error in getting user info", "error", appErr.Error()) } return } - ctx, cancel := context.WithTimeout(context.Background(), constants.RequestTimeout) + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) defer cancel() ghClient := p.githubConnectUser(ctx, info) switch objectType { - case constants.GithubObjectTypeIssueComment: + case githubObjectTypeIssueComment: reactions, _, err := ghClient.Reactions.ListIssueCommentReactions(context.Background(), owner, repo, int64(id), &github.ListOptions{}) if err != nil { p.client.Log.Debug("Error getting issue comment reaction list", "error", err.Error()) @@ -421,7 +470,7 @@ func (p *Plugin) ReactionHasBeenRemoved(c *plugin.Context, reaction *model.React return } } - case constants.GithubObjectTypeIssue: + case githubObjectTypeIssue: reactions, _, err := ghClient.Reactions.ListIssueReactions(context.Background(), owner, repo, int(id), &github.ListOptions{}) if err != nil { p.client.Log.Debug("Error getting issue reaction list", "error", err.Error()) @@ -436,7 +485,7 @@ func (p *Plugin) ReactionHasBeenRemoved(c *plugin.Context, reaction *model.React return } } - case constants.GithubObjectTypePRReviewComment: + case githubObjectTypePRReviewComment: reactions, _, err := ghClient.Reactions.ListPullRequestCommentReactions(context.Background(), owner, repo, int64(id), &github.ListOptions{}) if err != nil { p.client.Log.Debug("Error getting PR review comment reaction list", "error", err.Error()) @@ -511,7 +560,7 @@ func (p *Plugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*mode msg := post.Message info, appErr := p.getGitHubUserInfo(post.UserId) if appErr != nil { - if appErr.ID != constants.APIErrorIDNotConnected { + if appErr.ID != apiErrorIDNotConnected { p.client.Log.Warn("Error in getting user info", "error", appErr.Message) } return nil, "" @@ -585,7 +634,7 @@ func (p *Plugin) getOAuthConfigForChimeraApp(scopes []string) *oauth2.Config { } } -func (p *Plugin) storeGitHubUserInfo(info *serializer.GitHubUserInfo) error { +func (p *Plugin) storeGitHubUserInfo(info *GitHubUserInfo) error { config := p.getConfiguration() encryptedToken, err := encrypt([]byte(config.EncryptionKey), info.Token.AccessToken) @@ -602,22 +651,22 @@ func (p *Plugin) storeGitHubUserInfo(info *serializer.GitHubUserInfo) error { return nil } -func (p *Plugin) getGitHubUserInfo(userID string) (*serializer.GitHubUserInfo, *serializer.APIErrorResponse) { +func (p *Plugin) getGitHubUserInfo(userID string) (*GitHubUserInfo, *APIErrorResponse) { config := p.getConfiguration() - var userInfo *serializer.GitHubUserInfo + var userInfo *GitHubUserInfo err := p.store.Get(userID+githubTokenKey, &userInfo) if err != nil { - return nil, &serializer.APIErrorResponse{ID: "", Message: "Unable to get user info.", StatusCode: http.StatusInternalServerError} + return nil, &APIErrorResponse{ID: "", Message: "Unable to get user info.", StatusCode: http.StatusInternalServerError} } if userInfo == nil { - return nil, &serializer.APIErrorResponse{ID: constants.APIErrorIDNotConnected, Message: "Must connect user account to GitHub first.", StatusCode: http.StatusBadRequest} + return nil, &APIErrorResponse{ID: apiErrorIDNotConnected, Message: "Must connect user account to GitHub first.", StatusCode: http.StatusBadRequest} } unencryptedToken, err := decrypt([]byte(config.EncryptionKey), userInfo.Token.AccessToken) if err != nil { p.client.Log.Warn("Failed to decrypt access token", "error", err.Error()) - return nil, &serializer.APIErrorResponse{ID: "", Message: "Unable to decrypt access token.", StatusCode: http.StatusInternalServerError} + return nil, &APIErrorResponse{ID: "", Message: "Unable to decrypt access token.", StatusCode: http.StatusInternalServerError} } userInfo.Token.AccessToken = unencryptedToken @@ -754,7 +803,7 @@ func (p *Plugin) GetDailySummaryText(userID string) (string, error) { return string(summaryByte), nil } -func (p *Plugin) PostToDo(info *serializer.GitHubUserInfo, userID string) error { +func (p *Plugin) PostToDo(info *GitHubUserInfo, userID string) error { ctx := context.Background() text, err := p.GetToDo(ctx, info.GitHubUsername, p.githubConnectUser(ctx, info)) if err != nil { @@ -889,7 +938,7 @@ func (p *Plugin) GetToDo(ctx context.Context, username string, githubClient *git return text, nil } -func (p *Plugin) HasUnreads(info *serializer.GitHubUserInfo) bool { +func (p *Plugin) HasUnreads(info *GitHubUserInfo) bool { username := info.GitHubUsername ctx := context.Background() githubClient := p.githubConnectUser(ctx, info) @@ -985,9 +1034,9 @@ func (p *Plugin) sendRefreshEvent(userID string) { "userid": userID, }) - ctx, cancel := context.WithTimeout(context.Background(), constants.RequestTimeout) + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - context := &serializer.Context{ + context := &Context{ Ctx: ctx, UserID: userID, Log: eventLogger, @@ -1001,7 +1050,7 @@ func (p *Plugin) sendRefreshEvent(userID string) { return } - userContext := &serializer.UserContext{ + userContext := &UserContext{ Context: *context, GHInfo: info, } @@ -1031,7 +1080,7 @@ func (p *Plugin) sendRefreshEvent(userID string) { func (p *Plugin) getUsername(mmUserID string) (string, error) { info, apiEr := p.getGitHubUserInfo(mmUserID) if apiEr != nil { - if apiEr.ID != constants.APIErrorIDNotConnected { + if apiEr.ID != apiErrorIDNotConnected { return "", apiEr } diff --git a/server/serializer/sidebar.go b/server/plugin/sidebar.go similarity index 84% rename from server/serializer/sidebar.go rename to server/plugin/sidebar.go index 80d76cdf9..c58b3c46c 100644 --- a/server/serializer/sidebar.go +++ b/server/plugin/sidebar.go @@ -1,4 +1,4 @@ -package serializer +package plugin import ( "encoding/json" @@ -6,6 +6,11 @@ import ( "github.com/google/go-github/v54/github" ) +type FilteredNotification struct { + github.Notification + HTMLURL string `json:"html_url"` +} + type SidebarContent struct { PRs []*github.Issue `json:"prs"` Reviews []*github.Issue `json:"reviews"` diff --git a/server/plugin/utils.go b/server/plugin/utils.go index e7e391244..baedcf91f 100644 --- a/server/plugin/utils.go +++ b/server/plugin/utils.go @@ -19,9 +19,6 @@ import ( "github.com/google/go-github/v54/github" "github.com/pkg/errors" - "github.com/mattermost/mattermost-plugin-github/server/constants" - "github.com/mattermost/mattermost-plugin-github/server/serializer" - "github.com/mattermost/mattermost/server/public/model" ) @@ -371,40 +368,40 @@ func isValidURL(rawURL string) error { return nil } -func (p *Plugin) validateIssueRequestForUpdation(issue *serializer.UpdateIssueRequest, w http.ResponseWriter) bool { +func (p *Plugin) validateIssueRequestForUpdation(issue *UpdateIssueRequest, w http.ResponseWriter) bool { if issue.Title == "" { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide a valid issue title.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid issue title.", StatusCode: http.StatusBadRequest}) return false } if issue.PostID == "" && issue.ChannelID == "" { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Please provide either a postID or a channelID", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide either a postID or a channelID", StatusCode: http.StatusBadRequest}) return false } return true } -func (p *Plugin) updatePost(issue *serializer.UpdateIssueRequest, w http.ResponseWriter) { +func (p *Plugin) updatePost(issue *UpdateIssueRequest, w http.ResponseWriter) { post, appErr := p.API.GetPost(issue.PostID) if appErr != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) return } if post == nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", issue.PostID), StatusCode: http.StatusNotFound}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", issue.PostID), StatusCode: http.StatusNotFound}) return } - post.Props[constants.AssigneesForProps] = issue.Assignees - post.Props[constants.LabelsForProps] = issue.Labels - post.Props[constants.DescriptionForProps] = issue.Body - post.Props[constants.TitleForProps] = issue.Title + post.Props[assigneesForProps] = issue.Assignees + post.Props[labelsForProps] = issue.Labels + post.Props[descriptionForProps] = issue.Body + post.Props[titleForProps] = issue.Title if _, appErr = p.API.UpdatePost(post); appErr != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to update the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to update the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) } } -func (p *Plugin) CreateCommentToIssue(c *serializer.UserContext, w http.ResponseWriter, comment, owner, repo string, post *model.Post, issueNumber int) { +func (p *Plugin) CreateCommentToIssue(c *UserContext, w http.ResponseWriter, comment, owner, repo string, post *model.Post, issueNumber int) { currentUsername := c.GHInfo.GitHubUsername permalink := p.getPermaLink(post.Id) issueComment := &github.IssueComment{ @@ -418,7 +415,7 @@ func (p *Plugin) CreateCommentToIssue(c *serializer.UserContext, w http.Response if rawResponse != nil { statusCode = rawResponse.StatusCode } - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create an issue comment: %s", getFailReason(statusCode, repo, currentUsername)), StatusCode: statusCode}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create an issue comment: %s", getFailReason(statusCode, repo, currentUsername)), StatusCode: statusCode}) return } @@ -437,12 +434,12 @@ func (p *Plugin) CreateCommentToIssue(c *serializer.UserContext, w http.Response } if _, appErr := p.API.CreatePost(reply); appErr != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create the notification post %s", post.Id), StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create the notification post %s", post.Id), StatusCode: http.StatusInternalServerError}) return } } -func (p *Plugin) CloseOrReopenIssue(c *serializer.UserContext, w http.ResponseWriter, status, statusReason, owner, repo string, post *model.Post, issueNumber int) { +func (p *Plugin) CloseOrReopenIssue(c *UserContext, w http.ResponseWriter, status, statusReason, owner, repo string, post *model.Post, issueNumber int) { currentUsername := c.GHInfo.GitHubUsername githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) githubIssue := &github.IssueRequest{ @@ -453,12 +450,12 @@ func (p *Plugin) CloseOrReopenIssue(c *serializer.UserContext, w http.ResponseWr issue, resp, err := githubClient.Issues.Edit(c.Ctx, owner, repo, issueNumber, githubIssue) if err != nil { if resp != nil && resp.Response.StatusCode == http.StatusGone { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: "Issues are disabled on this repository.", StatusCode: http.StatusMethodNotAllowed}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Issues are disabled on this repository.", StatusCode: http.StatusMethodNotAllowed}) return } c.Log.WithError(err).Warnf("Failed to update the issue") - p.writeAPIError(w, &serializer.APIErrorResponse{ + p.writeAPIError(w, &APIErrorResponse{ ID: "", Message: fmt.Sprintf("failed to update the issue: %s", getFailReason(resp.StatusCode, repo, @@ -471,9 +468,9 @@ func (p *Plugin) CloseOrReopenIssue(c *serializer.UserContext, w http.ResponseWr var permalinkReplyMessage string switch statusReason { - case constants.IssueCompleted: + case issueCompleted: permalinkReplyMessage = fmt.Sprintf("Issue closed as completed [#%v](%v)", issueNumber, issue.GetHTMLURL()) - case constants.IssueNotPlanned: + case issueNotPlanned: permalinkReplyMessage = fmt.Sprintf("Issue closed as not planned [#%v](%v)", issueNumber, issue.GetHTMLURL()) default: permalinkReplyMessage = fmt.Sprintf("Issue reopend [#%v](%v)", issueNumber, issue.GetHTMLURL()) @@ -493,16 +490,16 @@ func (p *Plugin) CloseOrReopenIssue(c *serializer.UserContext, w http.ResponseWr } if _, appErr := p.API.CreatePost(reply); appErr != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create the notification post %s", post.Id), StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create the notification post %s", post.Id), StatusCode: http.StatusInternalServerError}) return } - if status == constants.IssueClose { - post.Props[constants.IssueStatus] = constants.Reopen + if status == issueClose { + post.Props[issueStatus] = statusReopen } else { - post.Props[constants.IssueStatus] = constants.Close + post.Props[issueStatus] = statusClose } if _, appErr := p.API.UpdatePost(post); appErr != nil { - p.writeAPIError(w, &serializer.APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to update the post %s", post.Id), StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to update the post %s", post.Id), StatusCode: http.StatusInternalServerError}) } p.writeJSON(w, issue) } diff --git a/server/plugin/webhook.go b/server/plugin/webhook.go index 99f8e787b..bb0dd442b 100644 --- a/server/plugin/webhook.go +++ b/server/plugin/webhook.go @@ -14,8 +14,6 @@ import ( "sync" "time" - "github.com/mattermost/mattermost-plugin-github/server/constants" - "github.com/google/go-github/v54/github" "github.com/microcosm-cc/bluemonday" @@ -336,11 +334,11 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { action := event.GetAction() switch action { - case constants.ActionOpened, - constants.ActionReopened, - constants.ActionMarkedReadyForReview, - constants.ActionLabeled, - constants.ActionClosed: + case actionOpened, + actionReopened, + actionMarkedReadyForReview, + actionLabeled, + actionClosed: default: return } @@ -369,11 +367,11 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { continue } - if sub.PullsMerged() && action != constants.ActionClosed { + if sub.PullsMerged() && action != actionClosed { continue } - if sub.PullsCreated() && action != constants.ActionOpened { + if sub.PullsCreated() && action != actionOpened { continue } @@ -393,15 +391,15 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { repoName := strings.ToLower(repo.GetFullName()) prNumber := event.GetPullRequest().Number - post.AddProp(constants.PostPropGithubRepo, repoName) - post.AddProp(constants.PostPropGithubObjectID, prNumber) - post.AddProp(constants.PostPropGithubObjectType, constants.GithubObjectTypeIssue) + post.AddProp(postPropGithubRepo, repoName) + post.AddProp(postPropGithubObjectID, prNumber) + post.AddProp(postPropGithubObjectType, githubObjectTypeIssue) if !contained && label != "" { continue } - if action == constants.ActionLabeled { + if action == actionLabeled { if label != "" && label == eventLabel { pullRequestLabelledMessage, err := renderTemplate("pullRequestLabelled", event) if err != nil { @@ -415,7 +413,7 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { } } - if action == constants.ActionOpened { + if action == actionOpened { prNotificationType := "newPR" if isPRInDraftState { prNotificationType = "newDraftPR" @@ -429,7 +427,7 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { post.Message = p.sanitizeDescription(newPRMessage) } - if action == constants.ActionReopened { + if action == actionReopened { reopenedPRMessage, err := renderTemplate("reopenedPR", event) if err != nil { p.client.Log.Warn("Failed to render template", "error", err.Error()) @@ -439,7 +437,7 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { post.Message = p.sanitizeDescription(reopenedPRMessage) } - if action == constants.ActionMarkedReadyForReview { + if action == actionMarkedReadyForReview { markedReadyToReviewPRMessage, err := renderTemplate("markedReadyToReviewPR", GetEventWithRenderConfig(event, sub)) if err != nil { p.client.Log.Warn("Failed to render template", "error", err.Error()) @@ -449,7 +447,7 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { post.Message = p.sanitizeDescription(markedReadyToReviewPRMessage) } - if action == constants.ActionClosed { + if action == actionClosed { post.Message = closedPRMessage } @@ -471,7 +469,7 @@ func (p *Plugin) sanitizeDescription(description string) string { func (p *Plugin) handlePRDescriptionMentionNotification(event *github.PullRequestEvent) { action := event.GetAction() - if action != constants.ActionOpened { + if action != actionOpened { return } @@ -533,7 +531,7 @@ func (p *Plugin) postIssueEvent(event *github.IssuesEvent) { // This condition is made to check if the message doesn't get automatically labeled to prevent duplicated issue messages timeDiff := time.Until(issue.GetCreatedAt().Time) * -1 - if action == constants.ActionLabeled && timeDiff.Seconds() < 4.00 { + if action == actionLabeled && timeDiff.Seconds() < 4.00 { return } @@ -544,16 +542,16 @@ func (p *Plugin) postIssueEvent(event *github.IssuesEvent) { issueTemplate := "" switch action { - case constants.ActionOpened: + case actionOpened: issueTemplate = "newIssue" - case constants.ActionClosed: + case actionClosed: issueTemplate = "closedIssue" - case constants.ActionReopened: + case actionReopened: issueTemplate = "reopenedIssue" - case constants.ActionLabeled: + case actionLabeled: issueTemplate = "issueLabelled" default: @@ -571,7 +569,7 @@ func (p *Plugin) postIssueEvent(event *github.IssuesEvent) { continue } - if sub.IssueCreations() && action != constants.ActionOpened && action != constants.ActionReopened && action != constants.ActionLabeled { + if sub.IssueCreations() && action != actionOpened && action != actionReopened && action != actionLabeled { continue } @@ -600,26 +598,26 @@ func (p *Plugin) postIssueEvent(event *github.IssuesEvent) { Message: renderedMessage, } - if action == constants.ActionOpened { + if action == actionOpened { post.Type = "custom_git_issue" post.Props = map[string]interface{}{ - constants.TitleForProps: *issue.Title, - constants.IssueURLForProps: *issue.HTMLURL, - constants.IssueNumberForProps: *issue.Number, - constants.DescriptionForProps: description, - constants.AssigneesForProps: assignees, - constants.LabelsForProps: labels, - constants.RepoOwnerForProps: *repo.Owner.Login, - constants.RepoNameForProps: *repo.Name, - constants.IssueStatus: constants.Close, + titleForProps: *issue.Title, + issueURLForProps: *issue.HTMLURL, + issueNumberForProps: *issue.Number, + descriptionForProps: description, + assigneesForProps: assignees, + labelsForProps: labels, + repoOwnerForProps: *repo.Owner.Login, + repoNameForProps: *repo.Name, + issueStatus: statusClose, } } repoName := strings.ToLower(repo.GetFullName()) issueNumber := issue.Number - post.AddProp(constants.PostPropGithubRepo, repoName) - post.AddProp(constants.PostPropGithubObjectID, issueNumber) - post.AddProp(constants.PostPropGithubObjectType, constants.GithubObjectTypeIssue) + post.AddProp(postPropGithubRepo, repoName) + post.AddProp(postPropGithubObjectID, issueNumber) + post.AddProp(postPropGithubObjectType, githubObjectTypeIssue) label := sub.Label() @@ -634,7 +632,7 @@ func (p *Plugin) postIssueEvent(event *github.IssuesEvent) { continue } - if action == constants.ActionLabeled { + if action == actionLabeled { if label == "" || label != eventLabel { continue } @@ -783,7 +781,7 @@ func (p *Plugin) postIssueCommentEvent(event *github.IssueCommentEvent) { return } - if event.GetAction() != constants.ActionCreated { + if event.GetAction() != actionCreated { return } @@ -801,9 +799,9 @@ func (p *Plugin) postIssueCommentEvent(event *github.IssueCommentEvent) { repoName := strings.ToLower(repo.GetFullName()) commentID := event.GetComment().GetID() - post.AddProp(constants.PostPropGithubRepo, repoName) - post.AddProp(constants.PostPropGithubObjectID, commentID) - post.AddProp(constants.PostPropGithubObjectType, constants.GithubObjectTypeIssueComment) + post.AddProp(postPropGithubRepo, repoName) + post.AddProp(postPropGithubObjectID, commentID) + post.AddProp(postPropGithubObjectType, githubObjectTypeIssueComment) labels := make([]string, len(event.GetIssue().Labels)) for index, label := range event.GetIssue().Labels { @@ -832,7 +830,7 @@ func (p *Plugin) postIssueCommentEvent(event *github.IssueCommentEvent) { continue } - if event.GetAction() == constants.ActionCreated { + if event.GetAction() == actionCreated { post.Message = message } @@ -864,7 +862,7 @@ func (p *Plugin) postPullRequestReviewEvent(event *github.PullRequestReviewEvent } action := event.GetAction() - if action != constants.ActionSubmitted { + if action != actionSubmitted { return } @@ -946,9 +944,9 @@ func (p *Plugin) postPullRequestReviewCommentEvent(event *github.PullRequestRevi repoName := strings.ToLower(repo.GetFullName()) commentID := event.GetComment().GetID() - post.AddProp(constants.PostPropGithubRepo, repoName) - post.AddProp(constants.PostPropGithubObjectID, commentID) - post.AddProp(constants.PostPropGithubObjectType, constants.GithubObjectTypePRReviewComment) + post.AddProp(postPropGithubRepo, repoName) + post.AddProp(postPropGithubObjectID, commentID) + post.AddProp(postPropGithubObjectType, githubObjectTypePRReviewComment) labels := make([]string, len(event.GetPullRequest().Labels)) for index, label := range event.GetPullRequest().Labels { @@ -986,7 +984,7 @@ func (p *Plugin) postPullRequestReviewCommentEvent(event *github.PullRequestRevi func (p *Plugin) handleCommentMentionNotification(event *github.IssueCommentEvent) { action := event.GetAction() - if action == constants.ActionEdited || action == constants.ActionDeleted { + if action == actionEdited || action == actionDeleted { return } @@ -1067,7 +1065,7 @@ func (p *Plugin) handleCommentAuthorNotification(event *github.IssueCommentEvent } action := event.GetAction() - if action == constants.ActionEdited || action == constants.ActionDeleted { + if action == actionEdited || action == actionDeleted { return } @@ -1211,7 +1209,7 @@ func (p *Plugin) handlePullRequestNotification(event *github.PullRequestEvent) { if isPrivate && !p.permissionToRepo(requestedUserID, repoName) { requestedUserID = "" } - case constants.ActionClosed: + case actionClosed: if author == sender { return } @@ -1219,7 +1217,7 @@ func (p *Plugin) handlePullRequestNotification(event *github.PullRequestEvent) { if isPrivate && !p.permissionToRepo(authorUserID, repoName) { authorUserID = "" } - case constants.ActionReopened: + case actionReopened: if author == sender { return } @@ -1227,7 +1225,7 @@ func (p *Plugin) handlePullRequestNotification(event *github.PullRequestEvent) { if isPrivate && !p.permissionToRepo(authorUserID, repoName) { authorUserID = "" } - case constants.ActionAssigned: + case actionAssigned: assignee := event.GetPullRequest().GetAssignee().GetLogin() if assignee == sender { return @@ -1269,17 +1267,17 @@ func (p *Plugin) handleIssueNotification(event *github.IssuesEvent) { assigneeUserID := "" switch event.GetAction() { - case constants.ActionClosed: + case actionClosed: authorUserID = p.getGitHubToUserIDMapping(author) if isPrivate && !p.permissionToRepo(authorUserID, repoName) { authorUserID = "" } - case constants.ActionReopened: + case actionReopened: authorUserID = p.getGitHubToUserIDMapping(author) if isPrivate && !p.permissionToRepo(authorUserID, repoName) { authorUserID = "" } - case constants.ActionAssigned: + case actionAssigned: assignee := event.GetAssignee().GetLogin() if assignee == sender { return @@ -1320,7 +1318,7 @@ func (p *Plugin) handlePullRequestReviewNotification(event *github.PullRequestRe return } - if event.GetAction() != constants.ActionSubmitted { + if event.GetAction() != actionSubmitted { return } diff --git a/server/serializer/issue.go b/server/serializer/issue.go deleted file mode 100644 index a5d400a79..000000000 --- a/server/serializer/issue.go +++ /dev/null @@ -1,44 +0,0 @@ -package serializer - -type CreateIssueRequest struct { - Title string `json:"title"` - Body string `json:"body"` - Repo string `json:"repo"` - PostID string `json:"post_id"` - ChannelID string `json:"channel_id"` - Labels []string `json:"labels"` - Assignees []string `json:"assignees"` - Milestone int `json:"milestone"` -} - -type CreateIssueCommentRequest struct { - PostID string `json:"post_id"` - Owner string `json:"owner"` - Repo string `json:"repo"` - Number int `json:"number"` - Comment string `json:"comment"` - ShowAttachedMessage bool `json:"show_attached_message"` -} - -type UpdateIssueRequest struct { - Title string `json:"title"` - Body string `json:"body"` - Repo string `json:"repo"` - PostID string `json:"post_id"` - ChannelID string `json:"channel_id"` - Labels []string `json:"labels"` - Assignees []string `json:"assignees"` - Milestone int `json:"milestone"` - IssueNumber int `json:"issue_number"` -} - -type CommentAndCloseRequest struct { - ChannelID string `json:"channel_id"` - IssueComment string `json:"issue_comment"` - StatusReason string `json:"status_reason"` - Number int `json:"number"` - Owner string `json:"owner"` - Repository string `json:"repo"` - Status string `json:"status"` - PostID string `json:"postId"` -} diff --git a/server/serializer/notification.go b/server/serializer/notification.go deleted file mode 100644 index 0b3e1c03f..000000000 --- a/server/serializer/notification.go +++ /dev/null @@ -1,8 +0,0 @@ -package serializer - -import "github.com/google/go-github/v54/github" - -type FilteredNotification struct { - github.Notification - HTMLURL string `json:"html_url"` -} diff --git a/server/serializer/pr.go b/server/serializer/pr.go deleted file mode 100644 index 5cdb3b1ef..000000000 --- a/server/serializer/pr.go +++ /dev/null @@ -1,12 +0,0 @@ -package serializer - -import "github.com/google/go-github/v54/github" - -type PRDetails struct { - URL string `json:"url"` - Number int `json:"number"` - Status string `json:"status"` - Mergeable bool `json:"mergeable"` - RequestedReviewers []*string `json:"requestedReviewers"` - Reviews []*github.PullRequestReview `json:"reviews"` -} diff --git a/server/serializer/repo.go b/server/serializer/repo.go deleted file mode 100644 index a7d3eaf7e..000000000 --- a/server/serializer/repo.go +++ /dev/null @@ -1,8 +0,0 @@ -package serializer - -// Only send down fields to the client that are needed -type RepositoryResponse struct { - Name string `json:"name,omitempty"` - FullName string `json:"full_name,omitempty"` - Permissions map[string]bool `json:"permissions,omitempty"` -} diff --git a/server/serializer/user.go b/server/serializer/user.go deleted file mode 100644 index e37f4e410..000000000 --- a/server/serializer/user.go +++ /dev/null @@ -1,62 +0,0 @@ -package serializer - -import ( - "context" - - "github.com/mattermost/mattermost/server/public/pluginapi/experimental/bot/logger" - "golang.org/x/oauth2" -) - -type Context struct { - Ctx context.Context - UserID string - Log logger.Logger -} - -type GitHubUserRequest struct { - UserID string `json:"user_id"` -} - -type GitHubUserResponse struct { - Username string `json:"username"` -} - -type ConnectedResponse struct { - Connected bool `json:"connected"` - GitHubUsername string `json:"github_username"` - GitHubClientID string `json:"github_client_id"` - EnterpriseBaseURL string `json:"enterprise_base_url,omitempty"` - Organization string `json:"organization"` - UserSettings *UserSettings `json:"user_settings"` - ClientConfiguration map[string]interface{} `json:"configuration"` -} - -type UserSettings struct { - SidebarButtons string `json:"sidebar_buttons"` - DailyReminder bool `json:"daily_reminder"` - DailyReminderOnChange bool `json:"daily_reminder_on_change"` - Notifications bool `json:"notifications"` -} - -type GitHubUserInfo struct { - UserID string - Token *oauth2.Token - GitHubUsername string - LastToDoPostAt int64 - Settings *UserSettings - AllowedPrivateRepos bool - - // MM34646ResetTokenDone is set for a user whose token has been reset for MM-34646. - MM34646ResetTokenDone bool -} - -type UserContext struct { - Context - GHInfo *GitHubUserInfo -} - -type OAuthState struct { - UserID string `json:"user_id"` - Token string `json:"token"` - PrivateAllowed bool `json:"private_allowed"` -} From 136221f5b067e00b5d20182b55ba256c62235c0f Mon Sep 17 00:00:00 2001 From: raghavaggarwal2308 Date: Fri, 5 Jul 2024 15:24:23 +0530 Subject: [PATCH 07/18] [MM-556] Review fixes --- webapp/package-lock.json | 27 +++++++---- webapp/src/components/github_issue/index.tsx | 49 +++++++++----------- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 91b58967d..571d900b9 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -20209,17 +20209,20 @@ "@primer/octicons-react": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-10.1.0.tgz", - "integrity": "sha512-WjIaetTaf4x66xxaG/gxwsWRL2JYG33n8CfeR/L134YcX2zl9TPps9crLzI2f3rxjOdKZgVFBoUh94Cim4Fflw==" + "integrity": "sha512-WjIaetTaf4x66xxaG/gxwsWRL2JYG33n8CfeR/L134YcX2zl9TPps9crLzI2f3rxjOdKZgVFBoUh94Cim4Fflw==", + "requires": {} }, "@react-native-community/netinfo": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-4.7.0.tgz", - "integrity": "sha512-a/sDB+AsLEUNmhAUlAaTYeXKyQdFGBUfatqKkX5jluBo2CB3OAuTHfm7rSjcaLB9EmG5iSq3fOTpync2E7EYTA==" + "integrity": "sha512-a/sDB+AsLEUNmhAUlAaTYeXKyQdFGBUfatqKkX5jluBo2CB3OAuTHfm7rSjcaLB9EmG5iSq3fOTpync2E7EYTA==", + "requires": {} }, "@restart/context": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz", - "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==" + "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==", + "requires": {} }, "@restart/hooks": { "version": "0.3.27", @@ -20876,7 +20879,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "7.2.0", @@ -20931,13 +20935,15 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true + "dev": true, + "requires": {} }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "ansi-colors": { "version": "4.1.3", @@ -23211,7 +23217,8 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.1.2.tgz", "integrity": "sha512-ykUeqkGyUGgwTtk78C0o8UG2fzwmgJ0qxBGPp2WqRKsTwcLuVf01kTDRAtOsd4u6whX2XOC8749n2vPydP82fg==", - "dev": true + "dev": true, + "requires": {} }, "eslint-scope": { "version": "5.1.1", @@ -25794,7 +25801,8 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true + "dev": true, + "requires": {} }, "jest-regex-util": { "version": "26.0.0", @@ -31955,7 +31963,8 @@ "ws": { "version": "7.5.10", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==" + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "requires": {} }, "xml": { "version": "1.0.1", diff --git a/webapp/src/components/github_issue/index.tsx b/webapp/src/components/github_issue/index.tsx index 75eb03873..1b13fd5b5 100644 --- a/webapp/src/components/github_issue/index.tsx +++ b/webapp/src/components/github_issue/index.tsx @@ -28,20 +28,17 @@ const GithubIssue = ({theme, post}: GithubIssueProps) => { }; const content = ( -
+
@@ -49,7 +46,7 @@ const GithubIssue = ({theme, post}: GithubIssueProps) => { if (postProps.assignees?.length) { assignees = ( -
+
{'Assignees'}
{postProps.assignees.map((assignee: string, index: number) => ( @@ -62,7 +59,7 @@ const GithubIssue = ({theme, post}: GithubIssueProps) => { if (postProps.labels?.length) { labels = ( -
+
{'Labels'}
{postProps.labels.map((label: string, index: number) => ( @@ -75,39 +72,39 @@ const GithubIssue = ({theme, post}: GithubIssueProps) => { return (
-
+

{'#' + postProps.issue_number + ' ' + postProps.title} -

-

{postProps.description}

- {assignees} - {labels} + +

{postProps.description}

+
+ {assignees} + {labels} +
{content}
); }; const getStyle = makeStyleFromTheme((theme) => ({ - button: { - fontFamily: 'Open Sans', - fontSize: '12px', - fontWeight: 'bold', - letterSpacing: '1px', - lineHeight: '19px', - margin: '12px 12px 8px 0px', - borderRadius: '4px', - color: theme.buttonColor, + button_container: { + margin: '16px 0 8px 0', + }, + issue_description: { + marginBottom: '10px', }, - close_or_reopen_button: { - backgroundColor: theme.errorTextColor, + assignee: { + marginRight: '20px', }, - other_buttons: { - backgroundColor: theme.buttonBg, + issue_title: { + fontFamily: 'Metropolis', + fontWeight: 600, }, assignees_and_labels: { display: 'inline-block', From 482fcf9a39dec85c31c6160b28ac051ece213a08 Mon Sep 17 00:00:00 2001 From: kshitij katiyar <90389917+Kshitij-Katiyar@users.noreply.github.com> Date: Tue, 6 Aug 2024 15:15:50 +0530 Subject: [PATCH 08/18] [MM-617]: converted the custom post to slack attachment for issue creation event on Github (#37) * [MM-617]: converted the custom post to slack attachment for issue creation * [MM-617]: fixed lint and testcases * [MM-617]: removed unused variables * [MM-617]: Fixed the title of the issue slackAttachment * [MM-617]: review fixes * [MM-617]: fixed lint * [MM-617]: Review fixes and code clean up * [MM-617]: removed the custom post file * [MM-617]: review fixes * [MM-617]: review fixes * [MM-617]: Fixed lint * [MM-617]: improved the error logging --- server/plugin/api.go | 110 +++++++++- server/plugin/plugin.go | 5 + server/plugin/template.go | 16 +- server/plugin/template_test.go | 192 +++--------------- server/plugin/utils.go | 87 +++++++- server/plugin/webhook.go | 68 +++++-- webapp/src/components/github_issue/index.tsx | 116 ----------- .../attach_comment_to_issue.jsx | 6 +- .../modals/close_reopen_issue/index.tsx | 18 +- .../create_update_issue.jsx | 5 +- webapp/src/index.js | 7 +- webapp/src/types/common/index.d.ts | 4 + webapp/src/websocket/index.js | 53 +++++ 13 files changed, 346 insertions(+), 341 deletions(-) delete mode 100644 webapp/src/components/github_issue/index.tsx create mode 100644 webapp/src/types/common/index.d.ts diff --git a/server/plugin/api.go b/server/plugin/api.go index 106031a5e..72464952b 100644 --- a/server/plugin/api.go +++ b/server/plugin/api.go @@ -58,6 +58,22 @@ const ( ResponseTypeJSON ResponseType = "JSON_RESPONSE" // ResponseTypePlain indicates that response type is text plain ResponseTypePlain ResponseType = "TEXT_RESPONSE" + + KeyRepoName string = "repo_name" + KeyRepoOwner string = "repo_owner" + KeyIssueNumber string = "issue_number" + KeyIssueID string = "issue_id" + KeyStatus string = "status" + KeyChannelID string = "channel_id" + KeyPostID string = "postId" + + WebsocketEventOpenCommentModal string = "open_comment_modal" + WebsocketEventOpenStatusModal string = "open_status_modal" + WebsocketEventOpenEditModal string = "open_edit_modal" + + PathOpenIssueCommentModal string = "/open-comment-modal" + PathOpenIssueEditModal string = "/open-edit-modal" + PathOpenIssueStatusModal string = "/open-status-modal" ) func (p *Plugin) writeJSON(w http.ResponseWriter, v interface{}) { @@ -124,6 +140,9 @@ func (p *Plugin) initializeAPI() { apiRouter.HandleFunc("/issue", p.checkAuth(p.attachUserContext(p.getIssueByNumber), ResponseTypePlain)).Methods(http.MethodGet) apiRouter.HandleFunc("/pr", p.checkAuth(p.attachUserContext(p.getPrByNumber), ResponseTypePlain)).Methods(http.MethodGet) apiRouter.HandleFunc("/lhs-content", p.checkAuth(p.attachUserContext(p.getSidebarContent), ResponseTypePlain)).Methods(http.MethodGet) + apiRouter.HandleFunc(PathOpenIssueCommentModal, p.checkAuth(p.attachUserContext(p.handleOpenIssueCommentModal), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc(PathOpenIssueEditModal, p.checkAuth(p.attachUserContext(p.handleOpenEditIssueModal), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc(PathOpenIssueStatusModal, p.checkAuth(p.attachUserContext(p.handleOpenIssueStatusModal), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc("/config", checkPluginRequest(p.getConfig)).Methods(http.MethodGet) apiRouter.HandleFunc("/token", checkPluginRequest(p.getToken)).Methods(http.MethodGet) @@ -863,7 +882,7 @@ func (p *Plugin) createIssueComment(c *UserContext, w http.ResponseWriter, r *ht rootID = post.RootId } - permalinkReplyMessage := fmt.Sprintf("Comment attached to GitHub issue [#%v](%v) from a [Message](%v)", req.Number, result.GetHTMLURL(), permalink) + permalinkReplyMessage := fmt.Sprintf("Comment attached to GitHub issue [#%v](%v)", req.Number, result.GetHTMLURL()) if req.ShowAttachedMessage { permalinkReplyMessage = fmt.Sprintf("[Message](%v) attached to GitHub issue [#%v](%v)", permalink, req.Number, result.GetHTMLURL()) } @@ -1321,7 +1340,6 @@ func (p *Plugin) updateIssue(c *UserContext, w http.ResponseWriter, r *http.Requ } var post *model.Post - permalink := "" if issue.PostID != "" { var appErr *model.AppError post, appErr = p.API.GetPost(issue.PostID) @@ -1334,7 +1352,6 @@ func (p *Plugin) updateIssue(c *UserContext, w http.ResponseWriter, r *http.Requ p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", issue.PostID), StatusCode: http.StatusNotFound}) return } - permalink = p.getPermaLink(issue.PostID) } githubIssue := &github.IssueRequest{ @@ -1392,7 +1409,6 @@ func (p *Plugin) updateIssue(c *UserContext, w http.ResponseWriter, r *http.Requ rootID = post.RootId } channelID = post.ChannelId - message += fmt.Sprintf(" from a [message](%s)", permalink) } reply := &model.Post{ @@ -1622,6 +1638,92 @@ func (p *Plugin) getToken(w http.ResponseWriter, r *http.Request) { p.writeJSON(w, info.Token) } +func (p *Plugin) handleOpenEditIssueModal(c *UserContext, w http.ResponseWriter, r *http.Request) { + response := &model.PostActionIntegrationResponse{} + decoder := json.NewDecoder(r.Body) + postActionIntegrationRequest := &model.PostActionIntegrationRequest{} + if err := decoder.Decode(&postActionIntegrationRequest); err != nil { + p.API.LogError("Error decoding PostActionIntegrationRequest params", "Error", err.Error()) + p.returnPostActionIntegrationResponse(w, response) + return + } + + p.client.Frontend.PublishWebSocketEvent( + WebsocketEventOpenEditModal, + map[string]interface{}{ + KeyRepoName: postActionIntegrationRequest.Context[KeyRepoName], + KeyRepoOwner: postActionIntegrationRequest.Context[KeyRepoOwner], + KeyIssueNumber: postActionIntegrationRequest.Context[KeyIssueNumber], + KeyPostID: postActionIntegrationRequest.PostId, + KeyStatus: postActionIntegrationRequest.Context[KeyStatus], + KeyChannelID: postActionIntegrationRequest.ChannelId, + }, + &model.WebsocketBroadcast{UserId: postActionIntegrationRequest.UserId}, + ) + + p.returnPostActionIntegrationResponse(w, response) +} + +func (p *Plugin) returnPostActionIntegrationResponse(w http.ResponseWriter, res *model.PostActionIntegrationResponse) { + w.Header().Set("Content-Type", "application/json") + + if err := json.NewEncoder(w).Encode(res); err != nil { + p.API.LogWarn("Failed to write PostActionIntegrationResponse", "Error", err.Error()) + } +} + +func (p *Plugin) handleOpenIssueStatusModal(c *UserContext, w http.ResponseWriter, r *http.Request) { + response := &model.PostActionIntegrationResponse{} + decoder := json.NewDecoder(r.Body) + postActionIntegrationRequest := &model.PostActionIntegrationRequest{} + if err := decoder.Decode(&postActionIntegrationRequest); err != nil { + p.API.LogError("Error decoding PostActionIntegrationRequest params", "Error", err.Error()) + p.returnPostActionIntegrationResponse(w, response) + return + } + + p.client.Frontend.PublishWebSocketEvent( + WebsocketEventOpenStatusModal, + map[string]interface{}{ + KeyRepoName: postActionIntegrationRequest.Context[KeyRepoName], + KeyRepoOwner: postActionIntegrationRequest.Context[KeyRepoOwner], + KeyIssueNumber: postActionIntegrationRequest.Context[KeyIssueNumber], + KeyPostID: postActionIntegrationRequest.PostId, + KeyStatus: postActionIntegrationRequest.Context[KeyStatus], + KeyChannelID: postActionIntegrationRequest.ChannelId, + }, + &model.WebsocketBroadcast{UserId: postActionIntegrationRequest.UserId}, + ) + + p.returnPostActionIntegrationResponse(w, response) +} + +func (p *Plugin) handleOpenIssueCommentModal(c *UserContext, w http.ResponseWriter, r *http.Request) { + response := &model.PostActionIntegrationResponse{} + decoder := json.NewDecoder(r.Body) + postActionIntegrationRequest := &model.PostActionIntegrationRequest{} + if err := decoder.Decode(&postActionIntegrationRequest); err != nil { + p.API.LogError("Error decoding PostActionIntegrationRequest params", "Error", err.Error()) + p.returnPostActionIntegrationResponse(w, response) + return + } + + p.client.Frontend.PublishWebSocketEvent( + WebsocketEventOpenCommentModal, + map[string]interface{}{ + KeyRepoName: postActionIntegrationRequest.Context[KeyRepoName], + KeyRepoOwner: postActionIntegrationRequest.Context[KeyRepoOwner], + KeyIssueNumber: postActionIntegrationRequest.Context[KeyIssueNumber], + KeyPostID: postActionIntegrationRequest.PostId, + KeyStatus: postActionIntegrationRequest.Context[KeyStatus], + KeyChannelID: postActionIntegrationRequest.ChannelId, + }, + &model.WebsocketBroadcast{UserId: postActionIntegrationRequest.UserId}, + ) + + p.returnPostActionIntegrationResponse(w, response) +} + // parseRepo parses the owner & repository name from the repo query parameter func parseRepo(repoParam string) (owner, repo string, err error) { if repoParam == "" { diff --git a/server/plugin/plugin.go b/server/plugin/plugin.go index 1da17175e..f89430b04 100644 --- a/server/plugin/plugin.go +++ b/server/plugin/plugin.go @@ -78,6 +78,7 @@ const ( labelsForProps = "labels" descriptionForProps = "description" titleForProps = "title" + attachmentsForProps = "attachments" issueNumberForProps = "issue_number" issueURLForProps = "issue_url" repoOwnerForProps = "repo_owner" @@ -1094,3 +1095,7 @@ func (p *Plugin) getUsername(mmUserID string) (string, error) { return "@" + info.GitHubUsername, nil } + +func (p *Plugin) GetPluginAPIPath() string { + return fmt.Sprintf("%s/plugins/%s/api/v1", *p.client.Configuration.GetConfig().ServiceSettings.SiteURL, Manifest.Id) +} diff --git a/server/plugin/template.go b/server/plugin/template.go index 753abc183..160a59367 100644 --- a/server/plugin/template.go +++ b/server/plugin/template.go @@ -126,7 +126,7 @@ func init() { // The repo template links to the corresponding repository. template.Must(masterTemplate.New("repo").Parse( - `[\[{{.GetFullName}}\]]({{.GetHTMLURL}})`, + `[{{.GetFullName}}]({{.GetHTMLURL}})`, )) // The eventRepoPullRequest links to the corresponding pull request, anchored at the repo. @@ -256,19 +256,7 @@ Assignees: {{range $i, $el := .Assignees -}} {{- if $i}}, {{end}}{{template "use {{.GetPullRequest.GetBody | trimBody | quote | replaceAllGitHubUsernames}}`)) template.Must(masterTemplate.New("newIssue").Funcs(funcMap).Parse(` -{{ if eq .Config.Style "collapsed" -}} -{{template "repo" .Event.GetRepo}} New issue {{template "issue" .Event.GetIssue}} opened by {{template "user" .Event.GetSender}}. -{{- else -}} -#### {{.Event.GetIssue.GetTitle}} -##### {{template "eventRepoIssue" .Event}} -#new-issue by {{template "user" .Event.GetSender}} -{{- if ne .Config.Style "skip-body" -}} -{{- template "labels" dict "Labels" .Event.GetIssue.Labels "RepositoryURL" .Event.GetRepo.GetHTMLURL }} -{{- template "assignee" .Event.GetIssue }} - -{{.Event.GetIssue.GetBody | removeComments | replaceAllGitHubUsernames}} -{{- end -}} -{{- end }} +{{template "user" .Event.GetSender}} created a new issue in {{template "repo" .Event.GetRepo}} `)) template.Must(masterTemplate.New("closedIssue").Funcs(funcMap).Parse(` diff --git a/server/plugin/template_test.go b/server/plugin/template_test.go index e66947ea3..aa651c418 100644 --- a/server/plugin/template_test.go +++ b/server/plugin/template_test.go @@ -115,39 +115,6 @@ var issue = github.Issue{ Body: sToP(`git-get-head sounds like a great feature we should support`), } -var issueWithMentions = github.Issue{ - Number: iToP(1), - HTMLURL: sToP("https://github.com/mattermost/mattermost-plugin-github/issues/1"), - Title: sToP("Implement git-get-head"), - CreatedAt: tToP(time.Date(2019, 04, 01, 02, 03, 04, 0, time.UTC)), - UpdatedAt: tToP(time.Date(2019, 05, 01, 02, 03, 04, 0, time.UTC)), - Body: sToP(`git-get-head sounds like a great feature we should support -` + gitHubMentions), -} - -var issueWithLabelAndAssignee = github.Issue{ - Number: iToP(1), - HTMLURL: sToP("https://github.com/mattermost/mattermost-plugin-github/issues/1"), - Title: sToP("Implement git-get-head"), - CreatedAt: tToP(time.Date(2019, 04, 01, 02, 03, 04, 0, time.UTC)), - UpdatedAt: tToP(time.Date(2019, 05, 01, 02, 03, 04, 0, time.UTC)), - Body: sToP(`git-get-head sounds like a great feature we should support`), - Labels: singleLabel, - Assignee: &user, - Assignees: []*github.User{&user}, -} - -var issueWithMultipleLabelsAndAssignee = github.Issue{ - Number: iToP(1), - HTMLURL: sToP("https://github.com/mattermost/mattermost-plugin-github/issues/1"), - Title: sToP("Implement git-get-head"), - CreatedAt: tToP(time.Date(2019, 04, 01, 02, 03, 04, 0, time.UTC)), - UpdatedAt: tToP(time.Date(2019, 05, 01, 02, 03, 04, 0, time.UTC)), - Body: sToP(`git-get-head sounds like a great feature we should support`), - Labels: labels, - Assignees: []*github.User{&user, &user}, -} - var user = github.User{ Login: sToP("panda"), HTMLURL: sToP("https://github.com/panda"), @@ -324,7 +291,7 @@ git-get-head gets the non-sent upstream heads inside the stashed non-cleaned app t.Run("with collapsed render style", func(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) New pull request [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42) was opened by [panda](https://github.com/panda). +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) New pull request [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42) was opened by [panda](https://github.com/panda). ` actual, err := renderTemplate("newPR", &EventWithRenderConfig{ @@ -366,7 +333,7 @@ git-get-head gets the non-sent upstream heads inside the stashed non-cleaned app func TestClosedPRMessageTemplate(t *testing.T) { t.Run("merged", func(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) Pull request [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42) was merged by [panda](https://github.com/panda). +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) Pull request [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42) was merged by [panda](https://github.com/panda). ` actual, err := renderTemplate("closedPR", &github.PullRequestEvent{ @@ -380,7 +347,7 @@ func TestClosedPRMessageTemplate(t *testing.T) { t.Run("closed", func(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) Pull request [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42) was closed by [panda](https://github.com/panda). +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) Pull request [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42) was closed by [panda](https://github.com/panda). ` actual, err := renderTemplate("closedPR", &github.PullRequestEvent{ @@ -396,7 +363,7 @@ func TestClosedPRMessageTemplate(t *testing.T) { func TestReopenedPRMessageTemplate(t *testing.T) { t.Run("reopened", func(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) Pull request [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42) was reopened by [panda](https://github.com/panda). +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) Pull request [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42) was reopened by [panda](https://github.com/panda). ` actual, err := renderTemplate("reopenedPR", &github.PullRequestEvent{ @@ -429,15 +396,10 @@ func TestPullRequestLabelledTemplate(t *testing.T) { } func TestNewIssueTemplate(t *testing.T) { - t.Run("without mentions", func(t *testing.T) { + t.Run("new issue", func(t *testing.T) { expected := ` -#### Implement git-get-head -##### [mattermost-plugin-github#1](https://github.com/mattermost/mattermost-plugin-github/issues/1) -#new-issue by [panda](https://github.com/panda) - -git-get-head sounds like a great feature we should support +[panda](https://github.com/panda) created a new issue in [mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) ` - actual, err := renderTemplate("newIssue", GetEventWithRenderConfig( &github.IssuesEvent{ Repo: &repo, @@ -449,119 +411,11 @@ git-get-head sounds like a great feature we should support require.NoError(t, err) require.Equal(t, expected, actual) }) - - t.Run("with mentions", withGitHubUserNameMapping(func(t *testing.T) { - expected := ` -#### Implement git-get-head -##### [mattermost-plugin-github#1](https://github.com/mattermost/mattermost-plugin-github/issues/1) -#new-issue by @pandabot - -git-get-head sounds like a great feature we should support -` + usernameMentions + ` -` - - actual, err := renderTemplate("newIssue", GetEventWithRenderConfig( - &github.IssuesEvent{ - Repo: &repo, - Issue: &issueWithMentions, - Sender: &user, - }, - nil, - )) - require.NoError(t, err) - require.Equal(t, expected, actual) - })) - - t.Run("with single label and assignee", func(t *testing.T) { - expected := ` -#### Implement git-get-head -##### [mattermost-plugin-github#1](https://github.com/mattermost/mattermost-plugin-github/issues/1) -#new-issue by [panda](https://github.com/panda) -Labels: ` + "[`Help Wanted`](https://github.com/mattermost/mattermost-plugin-github/labels/Help%20Wanted)" + ` -Assignees: [panda](https://github.com/panda) - -git-get-head sounds like a great feature we should support -` - - actual, err := renderTemplate("newIssue", GetEventWithRenderConfig( - &github.IssuesEvent{ - Repo: &repo, - Issue: &issueWithLabelAndAssignee, - Sender: &user, - }, - nil, - )) - require.NoError(t, err) - require.Equal(t, expected, actual) - }) - - t.Run("with multiple labels and assignees", func(t *testing.T) { - expected := ` -#### Implement git-get-head -##### [mattermost-plugin-github#1](https://github.com/mattermost/mattermost-plugin-github/issues/1) -#new-issue by [panda](https://github.com/panda) -Labels: ` + "[`Help Wanted`](https://github.com/mattermost/mattermost-plugin-github/labels/Help%20Wanted), [`Tech/Go`](https://github.com/mattermost/mattermost-plugin-github/labels/Tech%2FGo)" + ` -Assignees: [panda](https://github.com/panda), [panda](https://github.com/panda) - -git-get-head sounds like a great feature we should support -` - - actual, err := renderTemplate("newIssue", GetEventWithRenderConfig( - &github.IssuesEvent{ - Repo: &repo, - Issue: &issueWithMultipleLabelsAndAssignee, - Sender: &user, - }, - nil, - )) - require.NoError(t, err) - require.Equal(t, expected, actual) - }) - - t.Run("with collapsed render style", func(t *testing.T) { - expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) New issue [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1) opened by [panda](https://github.com/panda). -` - - actual, err := renderTemplate("newIssue", &EventWithRenderConfig{ - Event: &github.IssuesEvent{ - Repo: &repo, - Issue: &issue, - Sender: &user, - }, - Config: RenderConfig{ - Style: "collapsed", - }, - }) - require.NoError(t, err) - require.Equal(t, expected, actual) - }) - - t.Run("with skip-body render style", func(t *testing.T) { - expected := ` -#### Implement git-get-head -##### [mattermost-plugin-github#1](https://github.com/mattermost/mattermost-plugin-github/issues/1) -#new-issue by [panda](https://github.com/panda) -` - - actual, err := renderTemplate("newIssue", &EventWithRenderConfig{ - Event: &github.IssuesEvent{ - Repo: &repo, - Issue: &issue, - Sender: &user, - }, - Config: RenderConfig{ - Style: "skip-body", - }, - }) - require.NoError(t, err) - require.Equal(t, expected, actual) - }) } func TestClosedIssueTemplate(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) Issue [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1) closed by [panda](https://github.com/panda). +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) Issue [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1) closed by [panda](https://github.com/panda). ` actual, err := renderTemplate("closedIssue", GetEventWithRenderConfig( @@ -578,7 +432,7 @@ func TestClosedIssueTemplate(t *testing.T) { func TestReopenedIssueTemplate(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) Issue [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1) reopened by [panda](https://github.com/panda). +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) Issue [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1) reopened by [panda](https://github.com/panda). ` actual, err := renderTemplate("reopenedIssue", GetEventWithRenderConfig( @@ -789,7 +643,7 @@ func TestPushedCommitsTemplate(t *testing.T) { func TestCreateMessageTemplate(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) branch [branchname](https://github.com/mattermost/mattermost-plugin-github/tree/branchname) created by [panda](https://github.com/panda) +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) branch [branchname](https://github.com/mattermost/mattermost-plugin-github/tree/branchname) created by [panda](https://github.com/panda) ` actual, err := renderTemplate("newCreateMessage", &github.CreateEvent{ @@ -804,7 +658,7 @@ func TestCreateMessageTemplate(t *testing.T) { func TestDeletedMessageTemplate(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) branch branchname deleted by [panda](https://github.com/panda) +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) branch branchname deleted by [panda](https://github.com/panda) ` actual, err := renderTemplate("newDeleteMessage", &github.DeleteEvent{ @@ -819,7 +673,7 @@ func TestDeletedMessageTemplate(t *testing.T) { func TestRepoStarTemplate(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) starred by [panda](https://github.com/panda) +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) starred by [panda](https://github.com/panda) It now has **1** stars.` actual, err := renderTemplate("newRepoStar", &github.StarEvent{ @@ -834,7 +688,7 @@ It now has **1** stars.` func TestIssueCommentTemplate(t *testing.T) { t.Run("non-email body without mentions", func(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) New comment by [panda](https://github.com/panda) on [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1): +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) New comment by [panda](https://github.com/panda) on [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1): git-get-head sounds like a great feature we should support ` @@ -853,7 +707,7 @@ git-get-head sounds like a great feature we should support t.Run("email body without mentions", func(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) New comment by [panda](https://github.com/panda) on [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1): +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) New comment by [panda](https://github.com/panda) on [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1): git-get-head sounds like a great feature we should support ` @@ -872,7 +726,7 @@ git-get-head sounds like a great feature we should support t.Run("non-email body with mentions", withGitHubUserNameMapping(func(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) New comment by @pandabot on [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1): +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) New comment by @pandabot on [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1): git-get-head sounds like a great feature we should support ` + usernameMentions + ` @@ -892,7 +746,7 @@ git-get-head sounds like a great feature we should support t.Run("email body with mentions", withGitHubUserNameMapping(func(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) New comment by @pandabot on [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1): +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) New comment by @pandabot on [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1): git-get-head sounds like a great feature we should support ` + usernameMentions + ` @@ -914,7 +768,7 @@ git-get-head sounds like a great feature we should support func TestPullRequestReviewEventTemplate(t *testing.T) { t.Run("approved", func(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) approved [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) approved [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): Excited to see git-get-head land! ` @@ -934,7 +788,7 @@ Excited to see git-get-head land! t.Run("commented", func(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) commented on [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) commented on [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): Excited to see git-get-head land! ` @@ -954,7 +808,7 @@ Excited to see git-get-head land! t.Run("requested changes", func(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) requested changes on [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) requested changes on [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): Excited to see git-get-head land! ` @@ -974,7 +828,7 @@ Excited to see git-get-head land! t.Run("approved with mentions", withGitHubUserNameMapping(func(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) @pandabot approved [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) @pandabot approved [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): Excited to see git-get-head land! ` + usernameMentions + ` @@ -997,7 +851,7 @@ Excited to see git-get-head land! func TestPullRequestReviewCommentEventTemplate(t *testing.T) { t.Run("without mentions", func(*testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) New review comment by [panda](https://github.com/panda) on [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) New review comment by [panda](https://github.com/panda) on [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): Should this be here? ` @@ -1016,7 +870,7 @@ Should this be here? t.Run("with mentions", withGitHubUserNameMapping(func(*testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) New review comment by @pandabot on [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) New review comment by @pandabot on [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): Should this be here? ` + usernameMentions + ` @@ -1406,7 +1260,7 @@ func TestPullRequestReviewNotification(t *testing.T) { func TestReleaseNotification(t *testing.T) { t.Run("created", func(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) created a release [v0.0.1](https://github.com/mattermost/mattermost-plugin-github/releases/tag/v0.0.1)` +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) created a release [v0.0.1](https://github.com/mattermost/mattermost-plugin-github/releases/tag/v0.0.1)` actual, err := renderTemplate("newReleaseEvent", &github.ReleaseEvent{ Repo: &repo, @@ -1423,7 +1277,7 @@ func TestReleaseNotification(t *testing.T) { t.Run("deleted", func(t *testing.T) { expected := ` -[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) deleted a release [v0.0.1](https://github.com/mattermost/mattermost-plugin-github/releases/tag/v0.0.1)` +[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) deleted a release [v0.0.1](https://github.com/mattermost/mattermost-plugin-github/releases/tag/v0.0.1)` actual, err := renderTemplate("newReleaseEvent", &github.ReleaseEvent{ Repo: &repo, diff --git a/server/plugin/utils.go b/server/plugin/utils.go index baedcf91f..dd618db08 100644 --- a/server/plugin/utils.go +++ b/server/plugin/utils.go @@ -7,6 +7,7 @@ import ( "crypto/cipher" "crypto/rand" "encoding/base64" + "encoding/json" "fmt" "io" "net/http" @@ -392,18 +393,46 @@ func (p *Plugin) updatePost(issue *UpdateIssueRequest, w http.ResponseWriter) { return } - post.Props[assigneesForProps] = issue.Assignees - post.Props[labelsForProps] = issue.Labels - post.Props[descriptionForProps] = issue.Body - post.Props[titleForProps] = issue.Title + attachments, err := getAttachmentsFromProps(post.Props) + if err != nil { + p.client.Log.Warn("Error occurred while getting attachments from props", "PostID", post.Id, "error", err.Error()) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("existing attachments format error: %v", err), StatusCode: http.StatusInternalServerError}) + return + } + + attachments[0].Fields = p.CreateFieldsForIssuePost(issue.Assignees, issue.Labels) + attachments[0].Title = fmt.Sprintf("%s #%d", issue.Title, issue.IssueNumber) + attachments[0].Text = issue.Body + + post.Props[attachmentsForProps] = attachments + if _, appErr = p.API.UpdatePost(post); appErr != nil { p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to update the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) } } +func getAttachmentsFromProps(props map[string]interface{}) ([]*model.SlackAttachment, error) { + attachments, ok := props["attachments"] + if !ok { + return nil, fmt.Errorf("no attachments found in props") + } + + attachmentsData, err := json.Marshal(attachments) + if err != nil { + return nil, fmt.Errorf("failed to marshal attachments: %v", err) + } + + var slackAttachments []*model.SlackAttachment + err = json.Unmarshal(attachmentsData, &slackAttachments) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal attachments: %v", err) + } + + return slackAttachments, nil +} + func (p *Plugin) CreateCommentToIssue(c *UserContext, w http.ResponseWriter, comment, owner, repo string, post *model.Post, issueNumber int) { currentUsername := c.GHInfo.GitHubUsername - permalink := p.getPermaLink(post.Id) issueComment := &github.IssueComment{ Body: &comment, } @@ -425,7 +454,7 @@ func (p *Plugin) CreateCommentToIssue(c *UserContext, w http.ResponseWriter, com rootID = post.RootId } - permalinkReplyMessage := fmt.Sprintf("Comment attached to GitHub issue [#%v](%v) from a [message](%v)", issueNumber, result.GetHTMLURL(), permalink) + permalinkReplyMessage := fmt.Sprintf("Comment attached to GitHub issue [#%v](%v)", issueNumber, result.GetHTMLURL()) reply := &model.Post{ Message: permalinkReplyMessage, ChannelId: post.ChannelId, @@ -493,11 +522,36 @@ func (p *Plugin) CloseOrReopenIssue(c *UserContext, w http.ResponseWriter, statu p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create the notification post %s", post.Id), StatusCode: http.StatusInternalServerError}) return } + + var actionButtonTitle string if status == issueClose { post.Props[issueStatus] = statusReopen + actionButtonTitle = statusReopen } else { post.Props[issueStatus] = statusClose + actionButtonTitle = statusClose } + + attachment, err := getAttachmentsFromProps(post.Props) + if err != nil { + p.client.Log.Error("Error occurred while getting attachments from props", "PostID", post.Id, "Error", err.Error()) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("existing attachments format error: %v", err), StatusCode: http.StatusInternalServerError}) + return + } + actions := attachment[0].Actions + for _, action := range actions { + if action.Name == statusClose || action.Name == statusReopen { + action.Name = actionButtonTitle + if status == issueClose { + action.Integration.Context["status"] = "close" + } else { + action.Integration.Context["status"] = "open" + } + } + } + attachment[0].Actions = actions + post.Props[attachmentsForProps] = attachment + if _, appErr := p.API.UpdatePost(post); appErr != nil { p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to update the post %s", post.Id), StatusCode: http.StatusInternalServerError}) } @@ -523,3 +577,24 @@ func lastN(s string, n int) string { return string(out) } + +func (p *Plugin) CreateFieldsForIssuePost(assignees []string, labels []string) []*model.SlackAttachmentField { + fields := []*model.SlackAttachmentField{} + if len(assignees) > 0 { + fields = append(fields, &model.SlackAttachmentField{ + Title: "Assignees", + Value: strings.Join(assignees, ", "), + Short: true, + }) + } + + if len(labels) > 0 { + fields = append(fields, &model.SlackAttachmentField{ + Title: "Labels", + Value: strings.Join(labels, ", "), + Short: true, + }) + } + + return fields +} diff --git a/server/plugin/webhook.go b/server/plugin/webhook.go index 622f38296..4535dc4ac 100644 --- a/server/plugin/webhook.go +++ b/server/plugin/webhook.go @@ -590,20 +590,64 @@ func (p *Plugin) postIssueEvent(event *github.IssuesEvent) { description = *issue.Body } - post := p.makeBotPost(renderedMessage, "") + post := &model.Post{ + UserId: p.BotUserID, + Type: "custom_git_release", + } if action == actionOpened { - post.Type = "custom_git_issue" - post.Props = map[string]interface{}{ - titleForProps: *issue.Title, - issueURLForProps: *issue.HTMLURL, - issueNumberForProps: *issue.Number, - descriptionForProps: description, - assigneesForProps: assignees, - labelsForProps: labels, - repoOwnerForProps: *repo.Owner.Login, - repoNameForProps: *repo.Name, - issueStatus: statusClose, + post.Props = model.StringInterface{ + "attachments": []*model.SlackAttachment{ + { + Pretext: renderedMessage, + Title: fmt.Sprintf("%s #%d", *issue.Title, *issue.Number), + TitleLink: *issue.HTMLURL, + Text: description, + Actions: []*model.PostAction{ + { + Name: "Comment", + Integration: &model.PostActionIntegration{ + Context: map[string]interface{}{ + KeyRepoOwner: repo.GetOwner().GetLogin(), + KeyRepoName: repo.GetName(), + KeyIssueNumber: issue.GetNumber(), + KeyIssueID: issue.GetID(), + KeyStatus: *issue.State, + }, + URL: fmt.Sprintf("%s%s", p.GetPluginAPIPath(), PathOpenIssueCommentModal), + }, + Style: "primary", + }, + { + Name: "Edit", + Integration: &model.PostActionIntegration{ + Context: map[string]interface{}{ + KeyRepoOwner: repo.GetOwner().GetLogin(), + KeyRepoName: repo.GetName(), + KeyIssueNumber: issue.GetNumber(), + KeyIssueID: issue.GetID(), + KeyStatus: *issue.State, + }, + URL: fmt.Sprintf("%s%s", p.GetPluginAPIPath(), PathOpenIssueEditModal), + }, + }, + { + Name: "Close", + Integration: &model.PostActionIntegration{ + Context: map[string]interface{}{ + KeyRepoOwner: repo.GetOwner().GetLogin(), + KeyRepoName: repo.GetName(), + KeyIssueNumber: issue.GetNumber(), + KeyIssueID: issue.GetID(), + KeyStatus: *issue.State, + }, + URL: fmt.Sprintf("%s%s", p.GetPluginAPIPath(), PathOpenIssueStatusModal), + }, + }, + }, + Fields: p.CreateFieldsForIssuePost(assignees, labels), + }, + }, } } repoName := strings.ToLower(repo.GetFullName()) diff --git a/webapp/src/components/github_issue/index.tsx b/webapp/src/components/github_issue/index.tsx deleted file mode 100644 index 1b13fd5b5..000000000 --- a/webapp/src/components/github_issue/index.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import * as React from 'react'; -import {makeStyleFromTheme} from 'mattermost-redux/utils/theme_utils'; -import {Theme} from 'mattermost-redux/types/preferences'; -import {Post} from 'mattermost-redux/types/posts'; -import {useDispatch} from 'react-redux'; - -import {openCreateCommentOnIssueModal, openCreateOrUpdateIssueModal, openCloseOrReopenIssueModal} from '../../actions'; - -type GithubIssueProps = { - theme: Theme, - post: Post, -} - -const GithubIssue = ({theme, post}: GithubIssueProps) => { - const style = getStyle(theme); - const postProps = post.props || {}; - let assignees; - let labels; - const dispatch = useDispatch(); - - const issue = { - repo_owner: postProps.repo_owner, - repo_name: postProps.repo_name, - issue_number: postProps.issue_number, - postId: post.id, - status: postProps.status, - channel_id: post.channel_id, - }; - - const content = ( -
- - - -
- ); - - if (postProps.assignees?.length) { - assignees = ( -
- {'Assignees'} -
- {postProps.assignees.map((assignee: string, index: number) => ( - {(index ? ', ' : '') + assignee} - ))} -
-
- ); - } - - if (postProps.labels?.length) { - labels = ( -
- {'Labels'} -
- {postProps.labels.map((label: string, index: number) => ( - {(index ? ', ' : '') + label} - ))} -
-
- ); - } - - return ( -
-

- - {'#' + postProps.issue_number + ' ' + postProps.title} - -

-

{postProps.description}

-
- {assignees} - {labels} -
- {content} -
- ); -}; - -const getStyle = makeStyleFromTheme((theme) => ({ - button_container: { - margin: '16px 0 8px 0', - }, - issue_description: { - marginBottom: '10px', - }, - assignee: { - marginRight: '20px', - }, - issue_title: { - fontFamily: 'Metropolis', - fontWeight: 600, - }, - assignees_and_labels: { - display: 'inline-block', - verticalAlign: 'top', - width: '30%', - }, -})); - -export default GithubIssue; diff --git a/webapp/src/components/modals/attach_comment_to_issue/attach_comment_to_issue.jsx b/webapp/src/components/modals/attach_comment_to_issue/attach_comment_to_issue.jsx index 2096f60e8..adbb80c4b 100644 --- a/webapp/src/components/modals/attach_comment_to_issue/attach_comment_to_issue.jsx +++ b/webapp/src/components/modals/attach_comment_to_issue/attach_comment_to_issue.jsx @@ -45,13 +45,13 @@ export default class AttachIssueModal extends PureComponent { return; } - const {repo_owner, repo_name, issue_number} = this.props.messageData ?? {}; + const {repo_owner, repo_name, issue_number, postId} = this.props.messageData ?? {}; const issue = { owner: repo_owner, repo: repo_name, number: issue_number, comment: this.state.comment, - post_id: this.props.post.id, + post_id: postId, show_attached_message: false, }; this.setState({submitting: true}); @@ -78,7 +78,7 @@ export default class AttachIssueModal extends PureComponent { repo, number, comment: this.state.comment, - post_id: this.props.post.id, + post_id: this.props.messageData?.postId, show_attached_message: true, }; diff --git a/webapp/src/components/modals/close_reopen_issue/index.tsx b/webapp/src/components/modals/close_reopen_issue/index.tsx index a78c4cb25..f40833453 100644 --- a/webapp/src/components/modals/close_reopen_issue/index.tsx +++ b/webapp/src/components/modals/close_reopen_issue/index.tsx @@ -17,8 +17,7 @@ import Input from '../../input'; const CloseOrReopenIssueModal = ({theme}: {theme: Theme}) => { const dispatch = useDispatch(); - const closeOrReopenIssueModalData = useSelector(getCloseOrReopenIssueModalData); - const {messageData, visible} = closeOrReopenIssueModalData; + const {messageData, visible} = useSelector(getCloseOrReopenIssueModalData); const [statusReason, setStatusReason] = useState('completed'); const [submitting, setSubmitting] = useState(false); const [comment, setComment] = useState(''); @@ -31,18 +30,16 @@ const CloseOrReopenIssueModal = ({theme}: {theme: Theme}) => { e.preventDefault(); } - const currentStatus = messageData?.status === 'Close' ? statusReason : 'reopened'; const issue = { channel_id: messageData.channel_id, issue_comment: comment, - status_reason: currentStatus, + status_reason: messageData?.status === 'open' ? statusReason : 'reopened', // Sending the reason for the issue edit API call repo: messageData.repo_name, number: messageData.issue_number, owner: messageData.repo_owner, - status: messageData.status, + status: messageData.status === 'open' ? 'Close' : 'Reopen', // Sending the state of the issue which we want it to be after the edit API call postId: messageData.postId, }; - setSubmitting(true); await dispatch(closeOrReopenIssue(issue)); setSubmitting(false); @@ -61,12 +58,13 @@ const CloseOrReopenIssueModal = ({theme}: {theme: Theme}) => { const handleIssueCommentChange = (updatedComment: string) => setComment(updatedComment); const style = getStyle(theme); - const modalTitle = messageData.status + ' Issue'; - const savingMessage = messageData.status === 'Close' ? 'Closing' : 'Reopening'; - const status = messageData.status + ' Issue'; + const issueAction = messageData.status === 'open' ? 'Close Issue' : 'Open Issue'; + const modalTitle = issueAction; + const status = issueAction; + const savingMessage = messageData.status === 'open' ? 'Closing' : 'Reopening'; const submitError = null; - const component = (messageData.status === 'Close') ? ( + const component = (messageData.status === 'open') ? (
{ - const {issue_number} = this.props.messageData ?? {}; + const {issue_number, postId} = this.props.messageData ?? {}; if (e && e.preventDefault) { e.preventDefault(); } @@ -105,9 +105,6 @@ export default class CreateOrUpdateIssueModal extends PureComponent { return; } - const {post} = this.props; - const postId = (post) ? post.id : ''; - const issue = { title: this.state.issueTitle, body: this.state.issueDescription, diff --git a/webapp/src/index.js b/webapp/src/index.js index 886db3b91..34101eb08 100644 --- a/webapp/src/index.js +++ b/webapp/src/index.js @@ -11,11 +11,10 @@ import TeamSidebar from './components/team_sidebar'; import UserAttribute from './components/user_attribute'; import SidebarRight from './components/sidebar_right'; import LinkTooltip from './components/link_tooltip'; -import GithubIssue from './components/github_issue'; import Reducer from './reducers'; import Client from './client'; import {getConnected, setShowRHSAction} from './actions'; -import {handleConnect, handleDisconnect, handleConfigurationUpdate, handleOpenCreateOrUpdateIssueModal, handleReconnect, handleRefresh} from './websocket'; +import {handleConnect, handleDisconnect, handleConfigurationUpdate, handleOpenCreateOrUpdateIssueModal, handleOpenCreateCommentOnIssueModal, handleOpenCloseOrReopenIssueModal, handleReconnect, handleRefresh, handleOpenEditIssueModal} from './websocket'; import {getServerRoute} from './selectors'; import manifest from './manifest'; @@ -49,7 +48,9 @@ class PluginClass { registry.registerWebSocketEventHandler(`custom_${pluginId}_config_update`, handleConfigurationUpdate(store)); registry.registerWebSocketEventHandler(`custom_${pluginId}_refresh`, handleRefresh(store)); registry.registerWebSocketEventHandler(`custom_${pluginId}_createIssue`, handleOpenCreateOrUpdateIssueModal(store)); - registry.registerPostTypeComponent('custom_git_issue', GithubIssue); + registry.registerWebSocketEventHandler(`custom_${pluginId}_open_comment_modal`, handleOpenCreateCommentOnIssueModal(store)); + registry.registerWebSocketEventHandler(`custom_${pluginId}_open_edit_modal`, handleOpenEditIssueModal(store)); + registry.registerWebSocketEventHandler(`custom_${pluginId}_open_status_modal`, handleOpenCloseOrReopenIssueModal(store)); registry.registerReconnectHandler(handleReconnect(store)); diff --git a/webapp/src/types/common/index.d.ts b/webapp/src/types/common/index.d.ts new file mode 100644 index 000000000..3b6ea1f0f --- /dev/null +++ b/webapp/src/types/common/index.d.ts @@ -0,0 +1,4 @@ +type WebsocketEventParams = { + event: string, + data: Record, +} diff --git a/webapp/src/websocket/index.js b/webapp/src/websocket/index.js index a969b5b88..24fbba271 100644 --- a/webapp/src/websocket/index.js +++ b/webapp/src/websocket/index.js @@ -6,6 +6,8 @@ import Constants from '../constants'; import { getConnected, openCreateOrUpdateIssueModal, + openCreateCommentOnIssueModal, + openCloseOrReopenIssueModal, getSidebarContent, } from '../actions'; @@ -99,3 +101,54 @@ export function handleOpenCreateOrUpdateIssueModal(store) { store.dispatch(openCreateOrUpdateIssueModal(msg.data)); }; } + +export function handleOpenEditIssueModal(store) { + return (msg) => { + if (!msg.data) { + return; + } + const editIssueModalData = { + repo_owner: msg.data.repo_owner, + repo_name: msg.data.repo_name, + issue_number: msg.data.issue_number, + postId: msg.data.postId, + status: msg.data.status, + channel_id: msg.data.channel_id, + }; + store.dispatch(openCreateOrUpdateIssueModal(editIssueModalData)); + }; +} + +export function handleOpenCreateCommentOnIssueModal(store) { + return (msg) => { + if (!msg.data) { + return; + } + const commmentModalData = { + repo_owner: msg.data.repo_owner, + repo_name: msg.data.repo_name, + issue_number: msg.data.issue_number, + postId: msg.data.postId, + status: msg.data.status, + channel_id: msg.data.channel_id, + }; + store.dispatch(openCreateCommentOnIssueModal(commmentModalData)); + }; +} + +export function handleOpenCloseOrReopenIssueModal(store) { + return (msg) => { + if (!msg.data) { + return; + } + const statusModalData = { + repo_owner: msg.data.repo_owner, + repo_name: msg.data.repo_name, + issue_number: msg.data.issue_number, + postId: msg.data.postId, + status: msg.data.status, + channel_id: msg.data.channel_id, + }; + store.dispatch(openCloseOrReopenIssueModal(statusModalData)); + }; +} From b413828e5b154d16bf2412da6228c3cede7d1316 Mon Sep 17 00:00:00 2001 From: kshitij katiyar Date: Fri, 30 Aug 2024 13:22:58 +0530 Subject: [PATCH 09/18] [MM-618]: Fixed the lint --- server/plugin/plugin.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/server/plugin/plugin.go b/server/plugin/plugin.go index 05d416070..c09ed2164 100644 --- a/server/plugin/plugin.go +++ b/server/plugin/plugin.go @@ -28,13 +28,13 @@ import ( ) const ( - githubTokenKey = "_githubtoken" - githubOauthKey = "githuboauthkey_" - githubUsernameKey = "_githubusername" - githubPrivateRepoKey = "_githubprivate" - githubObjectTypeIssue = "issue" - githubObjectTypeIssueComment = "issue_comment" - githubObjectTypePRReviewComment = "pr_review_comment" + githubTokenKey = "_githubtoken" + githubOauthKey = "githuboauthkey_" + githubUsernameKey = "_githubusername" + githubPrivateRepoKey = "_githubprivate" + githubObjectTypeIssue = "issue" + githubObjectTypeIssueComment = "issue_comment" + githubObjectTypePRReviewComment = "pr_review_comment" githubObjectTypeDiscussionComment = "discussion_comment" mm34646MutexKey = "mm34646_token_reset_mutex" From 9ad7d481bb46939035b100ee37ff3597f4e01f0c Mon Sep 17 00:00:00 2001 From: kshitij katiyar Date: Tue, 21 Jan 2025 20:04:31 +0530 Subject: [PATCH 10/18] fxed testcases --- server/plugin/template.go | 2 +- server/plugin/template_test.go | 192 +++++++++++++++++++++++++++++---- 2 files changed, 170 insertions(+), 24 deletions(-) diff --git a/server/plugin/template.go b/server/plugin/template.go index 16d3bee20..7878c4e64 100644 --- a/server/plugin/template.go +++ b/server/plugin/template.go @@ -136,7 +136,7 @@ func init() { // The repo template links to the corresponding repository. template.Must(masterTemplate.New("repo").Parse( - `[{{.GetFullName}}]({{.GetHTMLURL}})`, + `[\[{{.GetFullName}}\]]({{.GetHTMLURL}})`, )) // The eventRepoPullRequest links to the corresponding pull request, anchored at the repo. diff --git a/server/plugin/template_test.go b/server/plugin/template_test.go index 323603ad7..cc8e59bf0 100644 --- a/server/plugin/template_test.go +++ b/server/plugin/template_test.go @@ -115,6 +115,39 @@ var issue = github.Issue{ Body: sToP(`git-get-head sounds like a great feature we should support`), } +var issueWithMentions = github.Issue{ + Number: iToP(1), + HTMLURL: sToP("https://github.com/mattermost/mattermost-plugin-github/issues/1"), + Title: sToP("Implement git-get-head"), + CreatedAt: tToP(time.Date(2019, 04, 01, 02, 03, 04, 0, time.UTC)), + UpdatedAt: tToP(time.Date(2019, 05, 01, 02, 03, 04, 0, time.UTC)), + Body: sToP(`git-get-head sounds like a great feature we should support +` + gitHubMentions), +} + +var issueWithLabelAndAssignee = github.Issue{ + Number: iToP(1), + HTMLURL: sToP("https://github.com/mattermost/mattermost-plugin-github/issues/1"), + Title: sToP("Implement git-get-head"), + CreatedAt: tToP(time.Date(2019, 04, 01, 02, 03, 04, 0, time.UTC)), + UpdatedAt: tToP(time.Date(2019, 05, 01, 02, 03, 04, 0, time.UTC)), + Body: sToP(`git-get-head sounds like a great feature we should support`), + Labels: singleLabel, + Assignee: &user, + Assignees: []*github.User{&user}, +} + +var issueWithMultipleLabelsAndAssignee = github.Issue{ + Number: iToP(1), + HTMLURL: sToP("https://github.com/mattermost/mattermost-plugin-github/issues/1"), + Title: sToP("Implement git-get-head"), + CreatedAt: tToP(time.Date(2019, 04, 01, 02, 03, 04, 0, time.UTC)), + UpdatedAt: tToP(time.Date(2019, 05, 01, 02, 03, 04, 0, time.UTC)), + Body: sToP(`git-get-head sounds like a great feature we should support`), + Labels: labels, + Assignees: []*github.User{&user, &user}, +} + var user = github.User{ Login: sToP("panda"), HTMLURL: sToP("https://github.com/panda"), @@ -291,7 +324,7 @@ git-get-head gets the non-sent upstream heads inside the stashed non-cleaned app t.Run("with collapsed render style", func(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) New pull request [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42) was opened by [panda](https://github.com/panda). +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) New pull request [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42) was opened by [panda](https://github.com/panda). ` actual, err := renderTemplate("newPR", &EventWithRenderConfig{ @@ -333,7 +366,7 @@ git-get-head gets the non-sent upstream heads inside the stashed non-cleaned app func TestClosedPRMessageTemplate(t *testing.T) { t.Run("merged", func(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) Pull request [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42) was merged by [panda](https://github.com/panda). +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) Pull request [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42) was merged by [panda](https://github.com/panda). ` actual, err := renderTemplate("closedPR", &github.PullRequestEvent{ @@ -347,7 +380,7 @@ func TestClosedPRMessageTemplate(t *testing.T) { t.Run("closed", func(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) Pull request [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42) was closed by [panda](https://github.com/panda). +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) Pull request [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42) was closed by [panda](https://github.com/panda). ` actual, err := renderTemplate("closedPR", &github.PullRequestEvent{ @@ -363,7 +396,7 @@ func TestClosedPRMessageTemplate(t *testing.T) { func TestReopenedPRMessageTemplate(t *testing.T) { t.Run("reopened", func(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) Pull request [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42) was reopened by [panda](https://github.com/panda). +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) Pull request [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42) was reopened by [panda](https://github.com/panda). ` actual, err := renderTemplate("reopenedPR", &github.PullRequestEvent{ @@ -396,10 +429,15 @@ func TestPullRequestLabelledTemplate(t *testing.T) { } func TestNewIssueTemplate(t *testing.T) { - t.Run("new issue", func(t *testing.T) { + t.Run("without mentions", func(t *testing.T) { expected := ` -[panda](https://github.com/panda) created a new issue in [mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) +#### Implement git-get-head +##### [mattermost-plugin-github#1](https://github.com/mattermost/mattermost-plugin-github/issues/1) +#new-issue by [panda](https://github.com/panda) + +git-get-head sounds like a great feature we should support ` + actual, err := renderTemplate("newIssue", GetEventWithRenderConfig( &github.IssuesEvent{ Repo: &repo, @@ -411,11 +449,119 @@ func TestNewIssueTemplate(t *testing.T) { require.NoError(t, err) require.Equal(t, expected, actual) }) + + t.Run("with mentions", withGitHubUserNameMapping(func(t *testing.T) { + expected := ` +#### Implement git-get-head +##### [mattermost-plugin-github#1](https://github.com/mattermost/mattermost-plugin-github/issues/1) +#new-issue by @pandabot + +git-get-head sounds like a great feature we should support +` + usernameMentions + ` +` + + actual, err := renderTemplate("newIssue", GetEventWithRenderConfig( + &github.IssuesEvent{ + Repo: &repo, + Issue: &issueWithMentions, + Sender: &user, + }, + nil, + )) + require.NoError(t, err) + require.Equal(t, expected, actual) + })) + + t.Run("with single label and assignee", func(t *testing.T) { + expected := ` +#### Implement git-get-head +##### [mattermost-plugin-github#1](https://github.com/mattermost/mattermost-plugin-github/issues/1) +#new-issue by [panda](https://github.com/panda) +Labels: ` + "[`Help Wanted`](https://github.com/mattermost/mattermost-plugin-github/labels/Help%20Wanted)" + ` +Assignees: [panda](https://github.com/panda) + +git-get-head sounds like a great feature we should support +` + + actual, err := renderTemplate("newIssue", GetEventWithRenderConfig( + &github.IssuesEvent{ + Repo: &repo, + Issue: &issueWithLabelAndAssignee, + Sender: &user, + }, + nil, + )) + require.NoError(t, err) + require.Equal(t, expected, actual) + }) + + t.Run("with multiple labels and assignees", func(t *testing.T) { + expected := ` +#### Implement git-get-head +##### [mattermost-plugin-github#1](https://github.com/mattermost/mattermost-plugin-github/issues/1) +#new-issue by [panda](https://github.com/panda) +Labels: ` + "[`Help Wanted`](https://github.com/mattermost/mattermost-plugin-github/labels/Help%20Wanted), [`Tech/Go`](https://github.com/mattermost/mattermost-plugin-github/labels/Tech%2FGo)" + ` +Assignees: [panda](https://github.com/panda), [panda](https://github.com/panda) + +git-get-head sounds like a great feature we should support +` + + actual, err := renderTemplate("newIssue", GetEventWithRenderConfig( + &github.IssuesEvent{ + Repo: &repo, + Issue: &issueWithMultipleLabelsAndAssignee, + Sender: &user, + }, + nil, + )) + require.NoError(t, err) + require.Equal(t, expected, actual) + }) + + t.Run("with collapsed render style", func(t *testing.T) { + expected := ` +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) New issue [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1) opened by [panda](https://github.com/panda). +` + + actual, err := renderTemplate("newIssue", &EventWithRenderConfig{ + Event: &github.IssuesEvent{ + Repo: &repo, + Issue: &issue, + Sender: &user, + }, + Config: RenderConfig{ + Style: "collapsed", + }, + }) + require.NoError(t, err) + require.Equal(t, expected, actual) + }) + + t.Run("with skip-body render style", func(t *testing.T) { + expected := ` +#### Implement git-get-head +##### [mattermost-plugin-github#1](https://github.com/mattermost/mattermost-plugin-github/issues/1) +#new-issue by [panda](https://github.com/panda) +` + + actual, err := renderTemplate("newIssue", &EventWithRenderConfig{ + Event: &github.IssuesEvent{ + Repo: &repo, + Issue: &issue, + Sender: &user, + }, + Config: RenderConfig{ + Style: "skip-body", + }, + }) + require.NoError(t, err) + require.Equal(t, expected, actual) + }) } func TestClosedIssueTemplate(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) Issue [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1) closed by [panda](https://github.com/panda). +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) Issue [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1) closed by [panda](https://github.com/panda). ` actual, err := renderTemplate("closedIssue", GetEventWithRenderConfig( @@ -432,7 +578,7 @@ func TestClosedIssueTemplate(t *testing.T) { func TestReopenedIssueTemplate(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) Issue [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1) reopened by [panda](https://github.com/panda). +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) Issue [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1) reopened by [panda](https://github.com/panda). ` actual, err := renderTemplate("reopenedIssue", GetEventWithRenderConfig( @@ -643,7 +789,7 @@ func TestPushedCommitsTemplate(t *testing.T) { func TestCreateMessageTemplate(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) branch [branchname](https://github.com/mattermost/mattermost-plugin-github/tree/branchname) created by [panda](https://github.com/panda) +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) branch [branchname](https://github.com/mattermost/mattermost-plugin-github/tree/branchname) created by [panda](https://github.com/panda) ` actual, err := renderTemplate("newCreateMessage", &github.CreateEvent{ @@ -658,7 +804,7 @@ func TestCreateMessageTemplate(t *testing.T) { func TestDeletedMessageTemplate(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) branch branchname deleted by [panda](https://github.com/panda) +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) branch branchname deleted by [panda](https://github.com/panda) ` actual, err := renderTemplate("newDeleteMessage", &github.DeleteEvent{ @@ -673,7 +819,7 @@ func TestDeletedMessageTemplate(t *testing.T) { func TestRepoStarTemplate(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) starred by [panda](https://github.com/panda) +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) starred by [panda](https://github.com/panda) It now has **1** stars.` actual, err := renderTemplate("newRepoStar", &github.StarEvent{ @@ -688,7 +834,7 @@ It now has **1** stars.` func TestIssueCommentTemplate(t *testing.T) { t.Run("non-email body without mentions", func(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) New comment by [panda](https://github.com/panda) on [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1): +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) New comment by [panda](https://github.com/panda) on [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1): git-get-head sounds like a great feature we should support ` @@ -707,7 +853,7 @@ git-get-head sounds like a great feature we should support t.Run("email body without mentions", func(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) New comment by [panda](https://github.com/panda) on [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1): +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) New comment by [panda](https://github.com/panda) on [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1): git-get-head sounds like a great feature we should support ` @@ -726,7 +872,7 @@ git-get-head sounds like a great feature we should support t.Run("non-email body with mentions", withGitHubUserNameMapping(func(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) New comment by @pandabot on [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1): +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) New comment by @pandabot on [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1): git-get-head sounds like a great feature we should support ` + usernameMentions + ` @@ -746,7 +892,7 @@ git-get-head sounds like a great feature we should support t.Run("email body with mentions", withGitHubUserNameMapping(func(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) New comment by @pandabot on [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1): +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) New comment by @pandabot on [#1 Implement git-get-head](https://github.com/mattermost/mattermost-plugin-github/issues/1): git-get-head sounds like a great feature we should support ` + usernameMentions + ` @@ -768,7 +914,7 @@ git-get-head sounds like a great feature we should support func TestPullRequestReviewEventTemplate(t *testing.T) { t.Run("approved", func(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) approved [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) approved [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): Excited to see git-get-head land! ` @@ -788,7 +934,7 @@ Excited to see git-get-head land! t.Run("commented", func(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) commented on [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) commented on [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): Excited to see git-get-head land! ` @@ -808,7 +954,7 @@ Excited to see git-get-head land! t.Run("requested changes", func(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) requested changes on [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) requested changes on [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): Excited to see git-get-head land! ` @@ -828,7 +974,7 @@ Excited to see git-get-head land! t.Run("approved with mentions", withGitHubUserNameMapping(func(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) @pandabot approved [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) @pandabot approved [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): Excited to see git-get-head land! ` + usernameMentions + ` @@ -851,7 +997,7 @@ Excited to see git-get-head land! func TestPullRequestReviewCommentEventTemplate(t *testing.T) { t.Run("without mentions", func(*testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) New review comment by [panda](https://github.com/panda) on [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) New review comment by [panda](https://github.com/panda) on [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): Should this be here? ` @@ -870,7 +1016,7 @@ Should this be here? t.Run("with mentions", withGitHubUserNameMapping(func(*testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) New review comment by @pandabot on [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) New review comment by @pandabot on [#42 Leverage git-get-head](https://github.com/mattermost/mattermost-plugin-github/pull/42): Should this be here? ` + usernameMentions + ` @@ -1260,7 +1406,7 @@ func TestPullRequestReviewNotification(t *testing.T) { func TestReleaseNotification(t *testing.T) { t.Run("created", func(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) created a release [v0.0.1](https://github.com/mattermost/mattermost-plugin-github/releases/tag/v0.0.1)` +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) created a release [v0.0.1](https://github.com/mattermost/mattermost-plugin-github/releases/tag/v0.0.1)` actual, err := renderTemplate("newReleaseEvent", &github.ReleaseEvent{ Repo: &repo, @@ -1277,7 +1423,7 @@ func TestReleaseNotification(t *testing.T) { t.Run("deleted", func(t *testing.T) { expected := ` -[mattermost-plugin-github](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) deleted a release [v0.0.1](https://github.com/mattermost/mattermost-plugin-github/releases/tag/v0.0.1)` +[\[mattermost-plugin-github\]](https://github.com/mattermost/mattermost-plugin-github) [panda](https://github.com/panda) deleted a release [v0.0.1](https://github.com/mattermost/mattermost-plugin-github/releases/tag/v0.0.1)` actual, err := renderTemplate("newReleaseEvent", &github.ReleaseEvent{ Repo: &repo, From 25c1a3e0dab51e7f34e89662082d96c60897d0c8 Mon Sep 17 00:00:00 2001 From: kshitij katiyar Date: Fri, 31 Jan 2025 18:33:25 +0530 Subject: [PATCH 11/18] fixed lint --- server/plugin/plugin.go | 2 +- webapp/src/components/modals/close_reopen_issue/index.tsx | 2 +- webapp/src/types/common/index.d.ts | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/server/plugin/plugin.go b/server/plugin/plugin.go index 45ebcdb63..6a3f8a623 100644 --- a/server/plugin/plugin.go +++ b/server/plugin/plugin.go @@ -76,7 +76,7 @@ const ( repoQueryParam = "repo" numberQueryParam = "number" postIDQueryParam = "postId" - channelIDParam = "channelId" + channelIDParam = "channelId" issueStatus = "status" assigneesForProps = "assignees" diff --git a/webapp/src/components/modals/close_reopen_issue/index.tsx b/webapp/src/components/modals/close_reopen_issue/index.tsx index f40833453..e488473ca 100644 --- a/webapp/src/components/modals/close_reopen_issue/index.tsx +++ b/webapp/src/components/modals/close_reopen_issue/index.tsx @@ -1,4 +1,4 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React, {useState} from 'react'; diff --git a/webapp/src/types/common/index.d.ts b/webapp/src/types/common/index.d.ts index 3b6ea1f0f..4b7a51a52 100644 --- a/webapp/src/types/common/index.d.ts +++ b/webapp/src/types/common/index.d.ts @@ -1,3 +1,6 @@ +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + type WebsocketEventParams = { event: string, data: Record, From f225e3ac7d625ad17545a0635aff4cddefdc92f4 Mon Sep 17 00:00:00 2001 From: kshitij katiyar Date: Mon, 21 Apr 2025 15:08:40 +0530 Subject: [PATCH 12/18] merge master to bs/MM-618 --- e2e/playwright/package-lock.json | 51 +- e2e/playwright/package.json | 2 +- server/mocks/logger_mock.go | 159 + server/plugin/api.go | 29 +- server/plugin/api_test.go | 385 +- server/plugin/command.go | 44 +- server/plugin/command_test.go | 1555 ++- server/plugin/flows.go | 2 +- server/plugin/subscriptions.go | 37 +- server/plugin/subscriptions_test.go | 102 + server/plugin/template.go | 16 +- server/plugin/test_utils.go | 80 + server/plugin/utils.go | 15 +- server/plugin/webhook.go | 61 +- server/plugin/webhook_test.go | 130 + webapp/package-lock.json | 10186 +++++++++++++--- webapp/package.json | 6 + .../github_repo_selector.jsx | 2 +- .../create_update_issue.test.jsx.snap | 105 + .../create_update_issue.test.jsx | 116 + webapp/tests/setup.tsx | 7 + 21 files changed, 11286 insertions(+), 1804 deletions(-) create mode 100644 server/mocks/logger_mock.go create mode 100644 server/plugin/test_utils.go create mode 100644 server/plugin/webhook_test.go create mode 100644 webapp/src/components/modals/create_update_issue/__snapshots__/create_update_issue.test.jsx.snap create mode 100644 webapp/src/components/modals/create_update_issue/create_update_issue.test.jsx create mode 100644 webapp/tests/setup.tsx diff --git a/e2e/playwright/package-lock.json b/e2e/playwright/package-lock.json index 5efb68f8f..901edf335 100644 --- a/e2e/playwright/package-lock.json +++ b/e2e/playwright/package-lock.json @@ -613,11 +613,6 @@ } } }, - "buffer-writer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", - "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" - }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -2236,11 +2231,6 @@ "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", "dev": true }, - "packet-reader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -2303,23 +2293,28 @@ } }, "pg": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.10.0.tgz", - "integrity": "sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==", - "requires": { - "buffer-writer": "2.0.0", - "packet-reader": "1.0.0", - "pg-connection-string": "^2.5.0", - "pg-pool": "^3.6.0", - "pg-protocol": "^1.6.0", + "version": "8.13.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.1.tgz", + "integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==", + "requires": { + "pg-cloudflare": "^1.1.1", + "pg-connection-string": "^2.7.0", + "pg-pool": "^3.7.0", + "pg-protocol": "^1.7.0", "pg-types": "^2.1.0", "pgpass": "1.x" } }, + "pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "optional": true + }, "pg-connection-string": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", - "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz", + "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==" }, "pg-int8": { "version": "1.0.1", @@ -2327,14 +2322,14 @@ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" }, "pg-pool": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.0.tgz", - "integrity": "sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==" + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.1.tgz", + "integrity": "sha512-xIOsFoh7Vdhojas6q3596mXFsR8nwBQBXX5JiV7p9buEVAGqYL4yFzclON5P9vFrpu1u7Zwl2oriyDa89n0wbw==" }, "pg-protocol": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", - "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.1.tgz", + "integrity": "sha512-gjTHWGYWsEgy9MsY0Gp6ZJxV24IjDqdpTW7Eh0x+WfJLFsm/TJx1MzL6T0D88mBvkpxotCQ6TwW6N+Kko7lhgQ==" }, "pg-types": { "version": "2.2.0", diff --git a/e2e/playwright/package.json b/e2e/playwright/package.json index 07b14421d..a80401b0a 100644 --- a/e2e/playwright/package.json +++ b/e2e/playwright/package.json @@ -25,6 +25,6 @@ }, "dependencies": { "express": "^4.21.2", - "pg": "^8.10.0" + "pg": "^8.13.1" } } diff --git a/server/mocks/logger_mock.go b/server/mocks/logger_mock.go new file mode 100644 index 000000000..8c3d2ea75 --- /dev/null +++ b/server/mocks/logger_mock.go @@ -0,0 +1,159 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/mattermost/mattermost/server/public/pluginapi/experimental/bot/logger (interfaces: Logger) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + logger "github.com/mattermost/mattermost/server/public/pluginapi/experimental/bot/logger" +) + +// MockLogger is a mock of Logger interface. +type MockLogger struct { + ctrl *gomock.Controller + recorder *MockLoggerMockRecorder +} + +// MockLoggerMockRecorder is the mock recorder for MockLogger. +type MockLoggerMockRecorder struct { + mock *MockLogger +} + +// NewMockLogger creates a new mock instance. +func NewMockLogger(ctrl *gomock.Controller) *MockLogger { + mock := &MockLogger{ctrl: ctrl} + mock.recorder = &MockLoggerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLogger) EXPECT() *MockLoggerMockRecorder { + return m.recorder +} + +// Context mocks base method. +func (m *MockLogger) Context() logger.LogContext { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Context") + ret0, _ := ret[0].(logger.LogContext) + return ret0 +} + +// Context indicates an expected call of Context. +func (mr *MockLoggerMockRecorder) Context() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockLogger)(nil).Context)) +} + +// Debugf mocks base method. +func (m *MockLogger) Debugf(arg0 string, arg1 ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Debugf", varargs...) +} + +// Debugf indicates an expected call of Debugf. +func (mr *MockLoggerMockRecorder) Debugf(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debugf", reflect.TypeOf((*MockLogger)(nil).Debugf), varargs...) +} + +// Errorf mocks base method. +func (m *MockLogger) Errorf(arg0 string, arg1 ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Errorf", varargs...) +} + +// Errorf indicates an expected call of Errorf. +func (mr *MockLoggerMockRecorder) Errorf(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Errorf", reflect.TypeOf((*MockLogger)(nil).Errorf), varargs...) +} + +// Infof mocks base method. +func (m *MockLogger) Infof(arg0 string, arg1 ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Infof", varargs...) +} + +// Infof indicates an expected call of Infof. +func (mr *MockLoggerMockRecorder) Infof(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Infof", reflect.TypeOf((*MockLogger)(nil).Infof), varargs...) +} + +// Timed mocks base method. +func (m *MockLogger) Timed() logger.Logger { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Timed") + ret0, _ := ret[0].(logger.Logger) + return ret0 +} + +// Timed indicates an expected call of Timed. +func (mr *MockLoggerMockRecorder) Timed() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Timed", reflect.TypeOf((*MockLogger)(nil).Timed)) +} + +// Warnf mocks base method. +func (m *MockLogger) Warnf(arg0 string, arg1 ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Warnf", varargs...) +} + +// Warnf indicates an expected call of Warnf. +func (mr *MockLoggerMockRecorder) Warnf(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warnf", reflect.TypeOf((*MockLogger)(nil).Warnf), varargs...) +} + +// With mocks base method. +func (m *MockLogger) With(arg0 logger.LogContext) logger.Logger { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "With", arg0) + ret0, _ := ret[0].(logger.Logger) + return ret0 +} + +// With indicates an expected call of With. +func (mr *MockLoggerMockRecorder) With(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "With", reflect.TypeOf((*MockLogger)(nil).With), arg0) +} + +// WithError mocks base method. +func (m *MockLogger) WithError(arg0 error) logger.Logger { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WithError", arg0) + ret0, _ := ret[0].(logger.Logger) + return ret0 +} + +// WithError indicates an expected call of WithError. +func (mr *MockLoggerMockRecorder) WithError(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithError", reflect.TypeOf((*MockLogger)(nil).WithError), arg0) +} diff --git a/server/plugin/api.go b/server/plugin/api.go index 55c78d18f..394afd3ec 100644 --- a/server/plugin/api.go +++ b/server/plugin/api.go @@ -840,6 +840,12 @@ func (p *Plugin) searchIssues(c *UserContext, w http.ResponseWriter, r *http.Req searchTerm := r.FormValue("term") orgsList := p.configuration.getOrganizations() allIssues := []*github.Issue{} + + if len(orgsList) == 0 { + orgsList = []string{""} + } + + hasFetchedIssues := false for _, org := range orgsList { query := getIssuesSearchQuery(org, searchTerm) var result *github.IssuesSearchResult @@ -853,11 +859,17 @@ func (p *Plugin) searchIssues(c *UserContext, w http.ResponseWriter, r *http.Req }) if cErr != nil { c.Log.WithError(cErr).With(logger.LogContext{"query": query}).Warnf("Failed to search for issues") - p.writeJSON(w, make([]*github.Issue, 0)) - return } - allIssues = append(allIssues, result.Issues...) + if result != nil && len(result.Issues) > 0 { + allIssues = append(allIssues, result.Issues...) + hasFetchedIssues = true + } + } + + if !hasFetchedIssues { + p.writeJSON(w, make([]*github.Issue, 0)) + return } p.writeJSON(w, allIssues) @@ -1486,6 +1498,7 @@ func (p *Plugin) getRepositories(c *UserContext, w http.ResponseWriter, r *http. } } else { orgsList := p.configuration.getOrganizations() + hasFetchedRepos := false for _, org := range orgsList { orgRepos, statusCode, err := p.getRepositoryListByOrg(c.Ctx, c.GHInfo, org, githubClient, opt) if err != nil { @@ -1493,20 +1506,22 @@ func (p *Plugin) getRepositories(c *UserContext, w http.ResponseWriter, r *http. orgRepos, err = p.getRepositoryList(c.Ctx, c.GHInfo, org, githubClient, opt) if err != nil { c.Log.WithError(err).Warnf("Failed to list repositories", "Organization", org) - p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError}) - return } } else { c.Log.WithError(err).Warnf("Failed to list repositories", "Organization", org) - p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError}) - return } } if len(orgRepos) > 0 { allRepos = append(allRepos, orgRepos...) + hasFetchedRepos = true } } + + if !hasFetchedRepos { + p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError}) + return + } } repoResp := make([]RepoResponse, len(allRepos)) diff --git a/server/plugin/api_test.go b/server/plugin/api_test.go index 17fceda4c..232e0ee0f 100644 --- a/server/plugin/api_test.go +++ b/server/plugin/api_test.go @@ -4,13 +4,19 @@ package plugin import ( + "encoding/json" "io" "net/http" "net/http/httptest" + "strings" "testing" + "github.com/golang/mock/gomock" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "golang.org/x/oauth2" "github.com/mattermost/mattermost/server/public/plugin" "github.com/mattermost/mattermost/server/public/plugin/plugintest" @@ -129,52 +135,129 @@ func TestPlugin_ServeHTTP(t *testing.T) { } } -func TestGetToken(t *testing.T) { - httpTestString := testutils.HTTPTest{ - T: t, - Encoder: testutils.EncodeString, +func TestCheckPluginRequest(t *testing.T) { + tests := []struct { + name string + headers map[string]string + setup func() + assertions func(t *testing.T, rec *httptest.ResponseRecorder) + }{ + { + name: "Missing Mattermost-Plugin-ID header", + headers: map[string]string{}, + setup: func() {}, + assertions: func(t *testing.T, rec *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusUnauthorized, rec.Result().StatusCode) + body, _ := io.ReadAll(rec.Body) + assert.Equal(t, "Not authorized\n", string(body)) + }, + }, + { + name: "Valid Mattermost-Plugin-ID header", + headers: map[string]string{ + "Mattermost-Plugin-ID": "validPluginID", + }, + setup: func() {}, + assertions: func(t *testing.T, rec *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusOK, rec.Result().StatusCode) + body, _ := io.ReadAll(rec.Body) + assert.Equal(t, "Success\n", string(body)) + }, + }, } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tc.setup() - for name, test := range map[string]struct { - httpTest testutils.HTTPTest - request testutils.Request - context *plugin.Context - expectedResponse testutils.ExpectedResponse + nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("Success\n")) + assert.NoError(t, err) + }) + + handler := checkPluginRequest(nextHandler) + + req := httptest.NewRequest(http.MethodGet, "/test", nil) + for key, value := range tc.headers { + req.Header.Set(key, value) + } + rec := httptest.NewRecorder() + + handler(rec, req) + + tc.assertions(t, rec) + }) + } +} + +func TestGetToken(t *testing.T) { + mockKvStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + + tests := []struct { + name string + userID string + setup func() + assertions func(t *testing.T, rec *httptest.ResponseRecorder) }{ - "not authorized": { - httpTest: httpTestString, - request: testutils.Request{ - Method: http.MethodGet, - URL: "/api/v1/token", - Body: nil, + { + name: "Missing userID", + userID: "", + setup: func() { + mockAPI.On("LogError", "UserID not found.") }, - context: &plugin.Context{}, - expectedResponse: testutils.ExpectedResponse{ - StatusCode: http.StatusUnauthorized, - ResponseType: testutils.ContentTypePlain, - Body: "Not authorized\n", + assertions: func(t *testing.T, rec *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusBadRequest, rec.Result().StatusCode) + body, _ := io.ReadAll(rec.Body) + assert.Contains(t, string(body), "please provide a userID") }, }, - } { - t.Run(name, func(t *testing.T) { - p := NewPlugin() - p.setConfiguration( - &Configuration{ - GitHubOrg: "mockOrg", - GitHubOAuthClientID: "mockID", - GitHubOAuthClientSecret: "mockSecret", - EncryptionKey: "mockKey", - }) - p.initializeAPI() - - p.SetAPI(&plugintest.API{}) + { + name: "User info not found in store", + userID: "mockUserID", + setup: func() { + mockAPI.On("LogError", "error occurred while getting the github user info", "UserID", MockUserID, "error", &APIErrorResponse{Message: "Unable to get user info.", StatusCode: http.StatusInternalServerError}) + mockKvStore.EXPECT().Get("mockUserID"+githubTokenKey, gomock.Any()).Return(errors.New("not found")).Times(1) + }, + assertions: func(t *testing.T, rec *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusInternalServerError, rec.Result().StatusCode) + body, _ := io.ReadAll(rec.Body) + assert.Contains(t, string(body), "Unable to get user info.") + }, + }, + { + name: "Successful token retrieval", + userID: "mockUserID", + setup: func() { + encryptedToken, err := encrypt([]byte("dummyEncryptKey1"), MockAccessToken) + assert.NoError(t, err) + mockKvStore.EXPECT().Get("mockUserID"+githubTokenKey, gomock.Any()).DoAndReturn(func(key string, value **GitHubUserInfo) error { + *value = &GitHubUserInfo{ + Token: &oauth2.Token{ + AccessToken: encryptedToken, + }, + } + return nil + }).Times(1) + p.setConfiguration(&Configuration{EncryptionKey: "dummyEncryptKey1"}) + }, + assertions: func(t *testing.T, rec *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusOK, rec.Result().StatusCode) + body, _ := io.ReadAll(rec.Body) + assert.Contains(t, string(body), MockAccessToken) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tc.setup() - req := test.httpTest.CreateHTTPRequest(test.request) - rr := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/get/token?userID="+tc.userID, nil) + rec := httptest.NewRecorder() - p.ServeHTTP(test.context, rr, req) + p.getToken(rec, req) - test.httpTest.CompareHTTPResponse(rr, test.expectedResponse) + tc.assertions(t, rec) }) } } @@ -249,3 +332,231 @@ func TestGetConfig(t *testing.T) { }) } } + +func TestGetGitHubUser(t *testing.T) { + mockKvStore, mockAPI, mockLogger, mockLoggerWith, mockContext := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + + tests := []struct { + name string + requestBody string + setup func() + expectedStatusCode int + assertions func(t *testing.T, rec *httptest.ResponseRecorder) + }{ + { + name: "Invalid JSON Request Body", + requestBody: "invalid-json", + setup: func() { + mockLogger.EXPECT().WithError(gomock.Any()).Return(mockLoggerWith).Times(1) + mockLoggerWith.EXPECT().Warnf("Error decoding GitHubUserRequest from JSON body").Times(1) + }, + assertions: func(t *testing.T, rec *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusBadRequest, rec.Result().StatusCode) + + var response APIErrorResponse + _ = json.NewDecoder(rec.Body).Decode(&response) + assert.Contains(t, response.Message, "Please provide a valid JSON object.") + }, + }, + { + name: "Blank user_id field", + requestBody: `{"user_id": ""}`, + setup: func() {}, + assertions: func(t *testing.T, rec *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusBadRequest, rec.Result().StatusCode) + var response APIErrorResponse + _ = json.NewDecoder(rec.Body).Decode(&response) + assert.Contains(t, response.Message, "non-blank user_id field") + }, + }, + { + name: "Error to getting user info", + requestBody: `{"user_id": "mockUserID"}`, + setup: func() { + mockKvStore.EXPECT().Get(gomock.Any(), gomock.Any()).Return(errors.New("Error getting user details")).Times(1) + }, + assertions: func(t *testing.T, rec *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusInternalServerError, rec.Result().StatusCode) + var response APIErrorResponse + _ = json.NewDecoder(rec.Body).Decode(&response) + assert.Contains(t, response.Message, "Unable to get user info") + }, + }, + { + name: "User is not connected to a GitHub account.", + requestBody: `{"user_id": "mockUserID"}`, + setup: func() { + mockKvStore.EXPECT().Get(gomock.Any(), gomock.Any()).Return(nil).Times(1) + }, + assertions: func(t *testing.T, rec *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusNotFound, rec.Result().StatusCode) + + var response APIErrorResponse + _ = json.NewDecoder(rec.Body).Decode(&response) + assert.Contains(t, response.Message, "User is not connected to a GitHub account.") + }, + }, + { + name: "Successfully get github user", + requestBody: `{"user_id": "mockUserID"}`, + setup: func() { + dummyUserInfo, err := GetMockGHUserInfo(p) + assert.NoError(t, err) + mockKvStore.EXPECT().Get("mockUserID"+githubTokenKey, gomock.Any()).DoAndReturn(func(key string, value **GitHubUserInfo) error { + *value = dummyUserInfo + return nil + }).Times(1) + }, + assertions: func(t *testing.T, rec *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusOK, rec.Result().StatusCode) + var response GitHubUserResponse + err := json.NewDecoder(rec.Body).Decode(&response) + assert.NoError(t, err) + assert.Equal(t, MockUsername, response.Username) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tc.setup() + + req := httptest.NewRequest(http.MethodPost, "/github/user", strings.NewReader(tc.requestBody)) + rec := httptest.NewRecorder() + + p.getGitHubUser(mockContext, rec, req) + + tc.assertions(t, rec) + }) + } +} + +func TestParseRepo(t *testing.T) { + tests := []struct { + name string + repoParam string + setup func() + assertions func(t *testing.T, owner, repo string, err error) + }{ + { + name: "Empty repository parameter", + repoParam: "", + setup: func() {}, + assertions: func(t *testing.T, owner, repo string, err error) { + assert.Equal(t, "", owner) + assert.Equal(t, "", repo) + assert.EqualError(t, err, "repository cannot be blank") + }, + }, + { + name: "Invalid repository format", + repoParam: "owner", + setup: func() {}, + assertions: func(t *testing.T, owner, repo string, err error) { + assert.Equal(t, "", owner) + assert.Equal(t, "", repo) + assert.EqualError(t, err, "invalid repository") + }, + }, + { + name: "Valid repository format", + repoParam: "owner/repo", + setup: func() {}, + assertions: func(t *testing.T, owner, repo string, err error) { + assert.NoError(t, err) + assert.Equal(t, "owner", owner) + assert.Equal(t, "repo", repo) + }, + }, + { + name: "Extra slashes in repository parameter", + repoParam: "owner/repo/", + setup: func() {}, + assertions: func(t *testing.T, owner, repo string, err error) { + assert.Equal(t, "", owner) + assert.Equal(t, "", repo) + assert.EqualError(t, err, "invalid repository") + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tc.setup() + + owner, repo, err := parseRepo(tc.repoParam) + + tc.assertions(t, owner, repo, err) + }) + } +} + +func TestUpdateSettings(t *testing.T) { + mockKvStore, mockAPI, mockLogger, mockLoggerWith, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + mockGHContext, err := GetMockUserContext(p, mockLogger) + assert.NoError(t, err) + + tests := []struct { + name string + requestBody string + setup func() + expectedStatusCode int + assertions func(t *testing.T, rec *httptest.ResponseRecorder) + }{ + { + name: "Invalid JSON Request Body", + requestBody: "invalid-json", + setup: func() { + mockLogger.EXPECT().WithError(gomock.Any()).Return(mockLoggerWith).Times(1) + mockLoggerWith.EXPECT().Warnf("Error decoding settings from JSON body").Times(1) + }, + expectedStatusCode: http.StatusBadRequest, + assertions: func(t *testing.T, rec *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusBadRequest, rec.Result().StatusCode) + }, + }, + { + name: "Error Storing User Info", + requestBody: `{"access_token": "mockAccessToken"}`, + setup: func() { + p.setConfiguration(&Configuration{ + EncryptionKey: "dummyEncryptKey1", + }) + mockKvStore.EXPECT().Set(gomock.Any(), gomock.Any()).Return(false, errors.New("store error")).Times(1) + mockLogger.EXPECT().WithError(gomock.Any()).Return(mockLoggerWith).Times(1) + mockLoggerWith.EXPECT().Warnf("Failed to store GitHub user info").Times(1) + }, + expectedStatusCode: http.StatusInternalServerError, + assertions: func(t *testing.T, rec *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusInternalServerError, rec.Result().StatusCode) + }, + }, + { + name: "Successful Update", + requestBody: `{"access_token": "mockAccessToken"}`, + setup: func() { + mockKvStore.EXPECT().Set(gomock.Any(), gomock.Any()).Return(true, nil).Times(1) + }, + expectedStatusCode: http.StatusOK, + assertions: func(t *testing.T, rec *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusOK, rec.Result().StatusCode) + var settings UserSettings + err := json.NewDecoder(rec.Body).Decode(&settings) + assert.NoError(t, err) + assert.Equal(t, mockGHContext.GHInfo.Settings, &settings) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tc.setup() + + req := httptest.NewRequest(http.MethodPost, "/update/settings", strings.NewReader(tc.requestBody)) + rec := httptest.NewRecorder() + + p.updateSettings(mockGHContext, rec, req) + + tc.assertions(t, rec) + }) + } +} diff --git a/server/plugin/command.go b/server/plugin/command.go index cd3d020f4..455422912 100644 --- a/server/plugin/command.go +++ b/server/plugin/command.go @@ -146,23 +146,28 @@ func (p *Plugin) postCommandResponse(args *model.CommandArgs, text string) { p.client.Post.SendEphemeralPost(args.UserId, post) } -func (p *Plugin) getMutedUsernames(userInfo *GitHubUserInfo) []string { +func (p *Plugin) getMutedUsernames(userInfo *GitHubUserInfo) ([]string, error) { var mutedUsernameBytes []byte err := p.store.Get(userInfo.UserID+"-muted-users", &mutedUsernameBytes) if err != nil { - return nil + return nil, err } mutedUsernames := string(mutedUsernameBytes) var mutedUsers []string if len(mutedUsernames) == 0 { - return mutedUsers + return mutedUsers, nil } mutedUsers = strings.Split(mutedUsernames, ",") - return mutedUsers + return mutedUsers, nil } func (p *Plugin) handleMuteList(_ *model.CommandArgs, userInfo *GitHubUserInfo) string { - mutedUsernames := p.getMutedUsernames(userInfo) + mutedUsernames, err := p.getMutedUsernames(userInfo) + if err != nil { + p.client.Log.Error("error occurred getting muted users.", "UserID", userInfo.UserID, "Error", err) + return "An error occurred getting muted users. Please try again later" + } + var mutedUsers string for _, user := range mutedUsernames { mutedUsers += fmt.Sprintf("- %v\n", user) @@ -183,7 +188,12 @@ func contains(s []string, e string) bool { } func (p *Plugin) handleMuteAdd(_ *model.CommandArgs, username string, userInfo *GitHubUserInfo) string { - mutedUsernames := p.getMutedUsernames(userInfo) + mutedUsernames, err := p.getMutedUsernames(userInfo) + if err != nil { + p.client.Log.Error("error occurred getting muted users.", "UserID", userInfo.UserID, "Error", err) + return "An error occurred getting muted users. Please try again later" + } + if contains(mutedUsernames, username) { return username + " is already muted" } @@ -200,7 +210,7 @@ func (p *Plugin) handleMuteAdd(_ *model.CommandArgs, username string, userInfo * mutedUsers = username } - _, err := p.store.Set(userInfo.UserID+"-muted-users", []byte(mutedUsers)) + _, err = p.store.Set(userInfo.UserID+"-muted-users", []byte(mutedUsers)) if err != nil { return "Error occurred saving list of muted users" } @@ -209,11 +219,16 @@ func (p *Plugin) handleMuteAdd(_ *model.CommandArgs, username string, userInfo * } func (p *Plugin) handleUnmute(_ *model.CommandArgs, username string, userInfo *GitHubUserInfo) string { - mutedUsernames := p.getMutedUsernames(userInfo) + mutedUsernames, err := p.getMutedUsernames(userInfo) + if err != nil { + p.client.Log.Error("error occurred getting muted users.", "UserID", userInfo.UserID, "Error", err) + return "An error occurred getting muted users. Please try again later" + } + userToMute := []string{username} newMutedList := arrayDifference(mutedUsernames, userToMute) - _, err := p.store.Set(userInfo.UserID+"-muted-users", []byte(strings.Join(newMutedList, ","))) + _, err = p.store.Set(userInfo.UserID+"-muted-users", []byte(strings.Join(newMutedList, ","))) if err != nil { return "Error occurred unmuting users" } @@ -569,6 +584,7 @@ func (p *Plugin) getSubscribedFeatures(channelID, owner, repo string) (Features, return previousFeatures, nil } + func (p *Plugin) handleUnsubscribe(_ *plugin.Context, args *model.CommandArgs, parameters []string, _ *GitHubUserInfo) string { if len(parameters) == 0 { return "Please specify a repository." @@ -1058,6 +1074,16 @@ func getAutocompleteData(config *Configuration) *model.AutocompleteData { HelpText: "Include posts from members of the configured organization", }, }) + subscriptionsAdd.AddNamedStaticListArgument("include-only-org-members", "Events triggered only by organization members will be delivered (the organization config should be set, otherwise this flag has not effect)", false, []model.AutocompleteListItem{ + { + Item: "true", + HelpText: "Include posts only from members of the configured organization", + }, + { + Item: "false", + HelpText: "Include posts from members and collaborators of the configured organization", + }, + }) } subscriptionsAdd.AddNamedStaticListArgument("render-style", "Determine the rendering style of various notifications.", false, []model.AutocompleteListItem{ diff --git a/server/plugin/command_test.go b/server/plugin/command_test.go index f9ee3544c..555081033 100644 --- a/server/plugin/command_test.go +++ b/server/plugin/command_test.go @@ -5,6 +5,8 @@ package plugin import ( "fmt" + "os" + "path/filepath" "testing" "github.com/golang/mock/gomock" @@ -12,6 +14,7 @@ import ( "github.com/mattermost/mattermost/server/public/plugin" "github.com/mattermost/mattermost/server/public/plugin/plugintest" "github.com/mattermost/mattermost/server/public/pluginapi" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -31,11 +34,8 @@ func getPluginTest(api *plugintest.API, mockKvStore *mocks.MockKvStore) *Plugin EncryptionKey: "mockKey123456789", }) p.initializeAPI() - p.store = mockKvStore - - p.BotUserID = "mockBotID" - + p.BotUserID = MockBotID p.SetAPI(api) p.client = pluginapi.NewClient(api, p.Driver) @@ -336,3 +336,1550 @@ func TestExecuteCommand(t *testing.T) { }) } } + +func TestGetMutedUsernames(t *testing.T) { + mockKvStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + userInfo, err := GetMockGHUserInfo(p) + assert.NoError(t, err) + + tests := []struct { + name string + setup func() + assertions func(t *testing.T, result []string, err error) + }{ + { + name: "Error retrieving muted usernames", + setup: func() { + mockKvStore.EXPECT().Get("mockUserID-muted-users", gomock.Any()).Return(errors.New("error retrieving muted users")).Times(1) + }, + assertions: func(t *testing.T, result []string, err error) { + assert.Nil(t, result) + assert.ErrorContains(t, err, "error retrieving muted users") + }, + }, + { + name: "No muted usernames set for user", + setup: func() { + mockKvStore.EXPECT().Get("mockUserID-muted-users", gomock.Any()).DoAndReturn(func(key string, value *[]byte) error { + *value = []byte("") + return nil + }).Times(1) + }, + assertions: func(t *testing.T, result []string, _ error) { + assert.Equal(t, []string(nil), result) + }, + }, + { + name: "Successfully retrieves muted usernames", + setup: func() { + mutedUsernames := []byte("user1,user2,user3") + mockKvStore.EXPECT().Get("mockUserID-muted-users", gomock.Any()).DoAndReturn(func(key string, value *[]byte) error { + *value = mutedUsernames + return nil + }).Times(1) + }, + assertions: func(t *testing.T, result []string, _ error) { + assert.Equal(t, []string{"user1", "user2", "user3"}, result) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tc.setup() + + mutedUsernames, err := p.getMutedUsernames(userInfo) + + tc.assertions(t, mutedUsernames, err) + }) + } +} + +func TestHandleMuteList(t *testing.T) { + mockKvStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + userInfo, err := GetMockGHUserInfo(p) + assert.NoError(t, err) + + tests := []struct { + name string + setup func() + assertions func(t *testing.T, result string) + }{ + { + name: "Error retrieving muted usernames", + setup: func() { + mockAPI.On("LogError", "error occurred getting muted users.", "UserID", userInfo.UserID, "Error", mock.Anything) + mockKvStore.EXPECT().Get("mockUserID-muted-users", gomock.Any()).Return(errors.New("error retrieving muted users")).Times(1) + }, + assertions: func(t *testing.T, result string) { + assert.Equal(t, "An error occurred getting muted users. Please try again later", result) + }, + }, + { + name: "No muted usernames set for user", + setup: func() { + mockKvStore.EXPECT().Get("mockUserID-muted-users", gomock.Any()).DoAndReturn(func(key string, value *[]byte) error { + *value = []byte("") + return nil + }).Times(1) + }, + assertions: func(t *testing.T, result string) { + assert.Equal(t, "You have no muted users", result) + }, + }, + { + name: "Successfully retrieves and formats muted usernames", + setup: func() { + mutedUsernames := []byte("user1,user2,user3") + mockKvStore.EXPECT().Get("mockUserID-muted-users", gomock.Any()).DoAndReturn(func(key string, value *[]byte) error { + *value = mutedUsernames + return nil + }).Times(1) + }, + assertions: func(t *testing.T, result string) { + expectedOutput := "Your muted users:\n- user1\n- user2\n- user3\n" + assert.Equal(t, expectedOutput, result) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tc.setup() + + result := p.handleMuteList(nil, userInfo) + + tc.assertions(t, result) + }) + } +} + +func TestContains(t *testing.T) { + tests := []struct { + name string + slice []string + element string + assertions func(t *testing.T, result bool) + }{ + { + name: "Element is present in slice", + slice: []string{"expectedElement1", "expectedElement2", "expectedElement3"}, + element: "expectedElement2", + assertions: func(t *testing.T, result bool) { + assert.True(t, result) + }, + }, + { + name: "Element is not present in slice", + slice: []string{"expectedElement1", "expectedElement2", "expectedElement3"}, + element: "expectedElement4", + assertions: func(t *testing.T, result bool) { + assert.False(t, result) + }, + }, + { + name: "Empty slice", + slice: []string{}, + element: "expectedElement1", + assertions: func(t *testing.T, result bool) { + assert.False(t, result) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := contains(tc.slice, tc.element) + tc.assertions(t, result) + }) + } +} + +func TestHandleMuteAdd(t *testing.T) { + mockKvStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + userInfo, err := GetMockGHUserInfo(p) + assert.NoError(t, err) + + tests := []struct { + name string + username string + setup func() + assertions func(t *testing.T, result string) + }{ + { + name: "Error retrieving muted usernames", + setup: func() { + mockAPI.On("LogError", "error occurred getting muted users.", "UserID", userInfo.UserID, "Error", mock.Anything) + mockKvStore.EXPECT().Get("mockUserID-muted-users", gomock.Any()).Return(errors.New("error retrieving muted users")).Times(1) + }, + assertions: func(t *testing.T, result string) { + assert.Equal(t, "An error occurred getting muted users. Please try again later", result) + }, + }, + { + name: "Error saving the new muted username", + username: "errorUser", + setup: func() { + mockKvStore.EXPECT().Get(userInfo.UserID+"-muted-users", gomock.Any()).DoAndReturn(func(key string, value *[]byte) error { + *value = []byte("existingUser") + return nil + }).Times(1) + mockKvStore.EXPECT().Set(userInfo.UserID+"-muted-users", []byte("existingUser,errorUser")).Return(false, errors.New("store error")).Times(1) + }, + assertions: func(t *testing.T, result string) { + assert.Equal(t, "Error occurred saving list of muted users", result) + }, + }, + { + name: "Username is already muted", + username: "alreadyMutedUser", + setup: func() { + mockKvStore.EXPECT().Get(userInfo.UserID+"-muted-users", gomock.Any()).DoAndReturn(func(key string, value *[]byte) error { + *value = []byte("alreadyMutedUser") + return nil + }).Times(1) + }, + assertions: func(t *testing.T, result string) { + assert.Equal(t, "alreadyMutedUser is already muted", result) + }, + }, + { + name: "Invalid username with comma", + username: "invalid,user", + setup: func() { + mockKvStore.EXPECT().Get(userInfo.UserID+"-muted-users", gomock.Any()).DoAndReturn(func(key string, value *[]byte) error { + *value = []byte("") + return nil + }).Times(1) + }, + assertions: func(t *testing.T, result string) { + assert.Equal(t, "Invalid username provided", result) + }, + }, + { + name: "Successfully adds first muted username", + username: "firstUser", + setup: func() { + mockKvStore.EXPECT().Get(userInfo.UserID+"-muted-users", gomock.Any()).DoAndReturn(func(key string, value *[]byte) error { + *value = []byte("") + return nil + }).Times(1) + mockKvStore.EXPECT().Set(userInfo.UserID+"-muted-users", []byte("firstUser")).Return(true, nil).Times(1) + }, + assertions: func(t *testing.T, result string) { + expectedMessage := "`firstUser` is now muted. You'll no longer receive notifications for comments in your PRs and issues." + assert.Equal(t, expectedMessage, result) + }, + }, + { + name: "Successfully adds new muted username", + username: "newUser", + setup: func() { + mockKvStore.EXPECT().Get(userInfo.UserID+"-muted-users", gomock.Any()).DoAndReturn(func(key string, value *[]byte) error { + *value = []byte("existingUser") + return nil + }).Times(1) + mockKvStore.EXPECT().Set(userInfo.UserID+"-muted-users", []byte("existingUser,newUser")).Return(true, nil).Times(1) + }, + assertions: func(t *testing.T, result string) { + expectedMessage := "`newUser` is now muted. You'll no longer receive notifications for comments in your PRs and issues." + assert.Equal(t, expectedMessage, result) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tc.setup() + result := p.handleMuteAdd(nil, tc.username, userInfo) + tc.assertions(t, result) + }) + } +} + +func TestHandleUnmute(t *testing.T) { + mockKvStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + userInfo, err := GetMockGHUserInfo(p) + assert.NoError(t, err) + + tests := []struct { + name string + username string + setup func() + expectedResult string + }{ + { + name: "Error retrieving muted usernames", + setup: func() { + mockAPI.On("LogError", "error occurred getting muted users.", "UserID", userInfo.UserID, "Error", mock.Anything) + mockKvStore.EXPECT().Get("mockUserID-muted-users", gomock.Any()).Return(errors.New("error retrieving muted users")).Times(1) + }, + expectedResult: "An error occurred getting muted users. Please try again later", + }, + { + name: "Error occurred while unmuting the user", + username: "user1", + setup: func() { + mutedUsernames := []byte("user1,user2,user3") + mockKvStore.EXPECT().Get("mockUserID-muted-users", gomock.Any()).DoAndReturn(func(key string, value *[]byte) error { + *value = mutedUsernames + return nil + }).Times(1) + mockKvStore.EXPECT().Set(userInfo.UserID+"-muted-users", gomock.Any()).Return(false, errors.New("error saving muted users")).Times(1) + }, + expectedResult: "Error occurred unmuting users", + }, + { + name: "Successfully unmute a user", + username: "user1", + setup: func() { + mutedUsernames := []byte("user1,user2,user3") + mockKvStore.EXPECT().Get("mockUserID-muted-users", gomock.Any()).DoAndReturn(func(key string, value *[]byte) error { + *value = mutedUsernames + return nil + }).Times(1) + mockKvStore.EXPECT().Set(userInfo.UserID+"-muted-users", gomock.Any()).Return(true, nil).Times(1) + }, + expectedResult: "`user1` is no longer muted", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tc.setup() + result := p.handleUnmute(nil, tc.username, userInfo) + assert.Equal(t, tc.expectedResult, result) + }) + } +} + +func TestHandleUnmuteAll(t *testing.T) { + mockKvStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + userInfo, err := GetMockGHUserInfo(p) + assert.NoError(t, err) + + tests := []struct { + name string + setup func() + assertions func(string) + expectedResult string + }{ + { + name: "Error occurred while unmuting all users", + setup: func() { + mockKvStore.EXPECT().Set(userInfo.UserID+"-muted-users", []byte("")).Return(false, errors.New("error saving muted users")).Times(1) + }, + assertions: func(expectedResult string) { + assert.Equal(t, expectedResult, "Error occurred unmuting users") + }, + }, + { + name: "Successfully unmute all users", + setup: func() { + mockKvStore.EXPECT().Set(userInfo.UserID+"-muted-users", []byte("")).Return(true, nil).Times(1) + }, + assertions: func(expectedResult string) { + assert.Equal(t, expectedResult, "Unmuted all users") + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tc.setup() + result := p.handleUnmuteAll(nil, userInfo) + tc.assertions(result) + }) + } +} + +func TestHandleMuteCommand(t *testing.T) { + mockKvStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + userInfo, err := GetMockGHUserInfo(p) + assert.NoError(t, err) + + tests := []struct { + name string + parameters []string + setup func() + assertions func(*testing.T, string) + }{ + { + name: "Success - list muted users", + parameters: []string{"list"}, + setup: func() { + mutedUsernames := []byte("user1,user2,user3") + mockKvStore.EXPECT().Get("mockUserID-muted-users", gomock.Any()).DoAndReturn(func(key string, value *[]byte) error { + *value = mutedUsernames + return nil + }).Times(1) + }, + assertions: func(t *testing.T, response string) { + assert.Equal(t, "Your muted users:\n- user1\n- user2\n- user3\n", response) + }, + }, + { + name: "Success - add new muted user", + parameters: []string{"add", "newUser"}, + setup: func() { + mockKvStore.EXPECT().Get(userInfo.UserID+"-muted-users", gomock.Any()).DoAndReturn(func(key string, value *[]byte) error { + *value = []byte("existingUser") + return nil + }).Times(1) + mockKvStore.EXPECT().Set(userInfo.UserID+"-muted-users", []byte("existingUser,newUser")).Return(true, nil).Times(1) + }, + assertions: func(t *testing.T, response string) { + assert.Equal(t, "`newUser` is now muted. You'll no longer receive notifications for comments in your PRs and issues.", response) + }, + }, + { + name: "Error - invalid number of parameters for add", + parameters: []string{"add"}, + setup: func() {}, + assertions: func(t *testing.T, response string) { + assert.Equal(t, "Invalid number of parameters supplied to add", response) + }, + }, + { + name: "Success - delete muted user", + parameters: []string{"delete", "user1"}, + setup: func() { + mutedUsernames := []byte("user1,user2,user3") + mockKvStore.EXPECT().Get("mockUserID-muted-users", gomock.Any()).DoAndReturn(func(key string, value *[]byte) error { + *value = mutedUsernames + return nil + }).Times(1) + mockKvStore.EXPECT().Set(userInfo.UserID+"-muted-users", gomock.Any()).Return(true, nil).Times(1) + }, + assertions: func(t *testing.T, response string) { + assert.Equal(t, "`user1` is no longer muted", response) + }, + }, + { + name: "Error - invalid number of parameters for delete", + parameters: []string{"delete"}, + setup: func() {}, + assertions: func(t *testing.T, response string) { + assert.Equal(t, "Invalid number of parameters supplied to delete", response) + }, + }, + { + name: "Success - delete all muted users", + parameters: []string{"delete-all"}, + setup: func() { + mockKvStore.EXPECT().Set(userInfo.UserID+"-muted-users", []byte("")).Return(true, nil).Times(1) + }, + assertions: func(t *testing.T, response string) { + assert.Equal(t, "Unmuted all users", response) + }, + }, + { + name: "Error - unknown subcommand", + parameters: []string{"unknown"}, + setup: func() {}, + assertions: func(t *testing.T, response string) { + assert.Equal(t, "Unknown subcommand unknown", response) + }, + }, + { + name: "Error - no parameters provided", + parameters: []string{}, + setup: func() {}, + assertions: func(t *testing.T, response string) { + assert.Equal(t, "Invalid mute command. Available commands are 'list', 'add' and 'delete'.", response) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tc.setup() + result := p.handleMuteCommand(nil, nil, tc.parameters, userInfo) + tc.assertions(t, result) + }) + } +} + +func TestArrayDifference(t *testing.T) { + tests := []struct { + name string + arr1 []string + arr2 []string + expected []string + }{ + { + name: "No difference - all elements in a are in b", + arr1: []string{"apple", "banana", "cherry"}, + arr2: []string{"apple", "banana", "cherry"}, + expected: []string{}, + }, + { + name: "Difference - some elements in a are not in b", + arr1: []string{"apple", "banana", "cherry", "date"}, + arr2: []string{"apple", "banana"}, + expected: []string{"cherry", "date"}, + }, + { + name: "All elements different - no elements in a are in b", + arr1: []string{"apple", "banana"}, + arr2: []string{"cherry", "date"}, + expected: []string{"apple", "banana"}, + }, + { + name: "Empty a - no elements to compare", + arr1: []string{}, + arr2: []string{"apple", "banana"}, + expected: []string{}, + }, + { + name: "Empty b - all elements in a should be returned", + arr1: []string{"apple", "banana"}, + arr2: []string{}, + expected: []string{"apple", "banana"}, + }, + { + name: "Both a and b empty - no elements to compare", + arr1: []string{}, + arr2: []string{}, + expected: []string{}, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := arrayDifference(tc.arr1, tc.arr2) + assert.ElementsMatch(t, tc.expected, result) + }) + } +} + +func TestHandleSubscriptionsList(t *testing.T) { + mockKvStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + + tests := []struct { + name string + channelID string + setup func() + assertions func(t *testing.T, result string) + }{ + { + name: "Error retrieving subscriptions", + channelID: "channel1", + setup: func() { + mockKvStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).Return(errors.New("store error")).Times(1) + }, + assertions: func(t *testing.T, result string) { + assert.Contains(t, result, "could not get subscriptions from KVStore: store error") + }, + }, + { + name: "No subscriptions in the channel", + channelID: "channel2", + setup: func() { + mockKvStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).DoAndReturn(func(key string, value **Subscriptions) error { + *value = &Subscriptions{Repositories: map[string][]*Subscription{}} + return nil + }).Times(1) + }, + assertions: func(t *testing.T, result string) { + assert.Equal(t, "Currently there are no subscriptions in this channel", result) + }, + }, + { + name: "Multiple subscriptions in the channel", + channelID: "channel3", + setup: func() { + mockKvStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).DoAndReturn(func(key string, value **Subscriptions) error { + *value = &Subscriptions{ + Repositories: map[string][]*Subscription{ + "repo1": { + { + ChannelID: "channel3", + Repository: "repo1", + }, + { + ChannelID: "channel4", + Repository: "repo1", + }, + }, + "repo2": { + { + ChannelID: "channel3", + Repository: "repo2", + }, + }, + }, + } + return nil + }).Times(1) + }, + assertions: func(t *testing.T, result string) { + expected := "### Subscriptions in this channel\n" + + "* `repo1` - \n" + + "* `repo2` - \n" + assert.Equal(t, expected, result) + }, + }, + { + name: "Subscriptions with flags", + channelID: "channel4", + setup: func() { + mockKvStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).DoAndReturn(func(key string, value **Subscriptions) error { + *value = &Subscriptions{ + Repositories: map[string][]*Subscription{ + "repo3": { + { + ChannelID: "channel4", + Repository: "repo3", + Flags: SubscriptionFlags{ + ExcludeOrgMembers: true, + RenderStyle: "compact", + ExcludeRepository: []string{"repoA", "repoB"}, + }, + }, + }, + }, + } + return nil + }).Times(1) + }, + assertions: func(t *testing.T, result string) { + expected := "### Subscriptions in this channel\n* `repo3` - --exclude-org-member true,--render-style compact,--exclude repoA,repoB\n" + assert.Equal(t, expected, result) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tc.setup() + result := p.handleSubscriptionsList(nil, &model.CommandArgs{ChannelId: tc.channelID}, nil, nil) + tc.assertions(t, result) + }) + } +} + +func TestGetSubscribedFeatures(t *testing.T) { + mockKvStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + + tests := []struct { + name string + channelID string + owner string + repo string + setup func() + assertions func(t *testing.T, features Features, err error) + }{ + { + name: "Error retrieving subscriptions", + channelID: "channel1", + owner: "owner1", + repo: "repo1", + setup: func() { + mockKvStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).Return(errors.New("store error")).Times(1) + }, + assertions: func(t *testing.T, features Features, err error) { + assert.Error(t, err) + assert.ErrorContains(t, err, "store error") + assert.Empty(t, features) + }, + }, + { + name: "No subscriptions in the channel", + channelID: "channel2", + owner: "owner2", + repo: "repo2", + setup: func() { + mockKvStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).DoAndReturn(func(key string, value **Subscriptions) error { + *value = &Subscriptions{Repositories: map[string][]*Subscription{}} + return nil + }).Times(1) + }, + assertions: func(t *testing.T, features Features, err error) { + assert.NoError(t, err) + assert.Empty(t, features) + }, + }, + { + name: "Subscribed features found for repo", + channelID: "channel3", + owner: "owner3", + repo: "repo3", + setup: func() { + mockKvStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).DoAndReturn(func(key string, value **Subscriptions) error { + *value = &Subscriptions{ + Repositories: map[string][]*Subscription{ + "owner3/repo3": { + { + ChannelID: "channel3", + Repository: "owner3/repo3", + Features: Features("FeatureA"), + }, + }, + "owner4/repo4": { + { + ChannelID: "channel4", + Repository: "owner4/repo4", + Features: Features("FeatureB"), + }, + }, + }, + } + return nil + }).Times(1) + }, + assertions: func(t *testing.T, features Features, err error) { + assert.NoError(t, err) + expectedFeatures := Features("FeatureA") + assert.Equal(t, expectedFeatures, features) + }, + }, + { + name: "Subscribed features not found for repo", + channelID: "channel4", + owner: "owner5", + repo: "repo5", + setup: func() { + mockKvStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).DoAndReturn(func(key string, value **Subscriptions) error { + *value = &Subscriptions{ + Repositories: map[string][]*Subscription{ + "owner6/repo6": { + { + ChannelID: "channel4", + Repository: "owner6/repo6", + Features: Features("FeatureC"), + }, + }, + }, + } + return nil + }).Times(1) + }, + assertions: func(t *testing.T, features Features, err error) { + assert.NoError(t, err) + assert.Empty(t, features) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tc.setup() + features, err := p.getSubscribedFeatures(tc.channelID, tc.owner, tc.repo) + tc.assertions(t, features, err) + }) + } +} + +func TestCreatePost(t *testing.T) { + mockKvStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + post := &model.Post{ + ChannelId: MockChannelID, + UserId: MockUserID, + Message: MockPostMessage, + } + + tests := []struct { + name string + setup func() + assertions func(t *testing.T, err error) + }{ + { + name: "Error creating a post", + setup: func() { + mockAPI.On("CreatePost", post).Return(nil, &model.AppError{Message: "error creating post"}).Times(1) + mockAPI.On("LogWarn", "Error while creating post", "post", post, "error", "error creating post").Times(1) + }, + assertions: func(t *testing.T, err error) { + assert.EqualError(t, err, "error creating post") + }, + }, + { + name: "Successfully create a post", + setup: func() { + mockAPI.On("CreatePost", post).Return(post, nil).Times(1) + }, + assertions: func(t *testing.T, err error) { + assert.NoError(t, err) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + mockAPI.ExpectedCalls = nil + tc.setup() + err := p.createPost(MockChannelID, MockUserID, MockPostMessage) + tc.assertions(t, err) + }) + } +} + +func TestHandleUnsubscribe(t *testing.T) { + mockKVStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKVStore) + p.setConfiguration(&Configuration{}) + post := &model.Post{ + ChannelId: MockChannelID, + UserId: MockBotID, + } + + tests := []struct { + name string + parameters []string + setup func() + assertions func(result string) + }{ + { + name: "No repository specified", + parameters: []string{}, + setup: func() {}, + assertions: func(result string) { + assert.Equal(t, "Please specify a repository.", result) + }, + }, + { + name: "Invalid repository format", + parameters: []string{""}, + setup: func() { + }, + assertions: func(result string) { + assert.Equal(t, "invalid repository", result) + }, + }, + { + name: "Failed to unsubscribe", + parameters: []string{"owner/repo"}, + setup: func() { + mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).Return(errors.New("error occurred getting subscriptions")) + mockAPI.On("LogWarn", "Failed to unsubscribe", "repo", "repo", "error", "could not get subscriptions: could not get subscriptions from KVStore: error occurred getting subscriptions") + }, + assertions: func(result string) { + assert.Equal(t, "Encountered an error trying to unsubscribe. Please try again.", result) + }, + }, + { + name: "Error getting user details", + parameters: []string{"owner/repo"}, + setup: func() { + mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).DoAndReturn(func(key string, value **Subscriptions) error { + *value = &Subscriptions{Repositories: map[string][]*Subscription{ + "owner/repo": {{ChannelID: "dummyChannelID", CreatorID: MockCreatorID, Repository: "owner/repo"}}}} + return nil + }).Times(1) + mockAPI.On("GetUser", MockUserID).Return(nil, &model.AppError{Message: "error getting user"}).Times(1) + mockAPI.On("LogWarn", "Error while fetching user details", "error", "error getting user").Times(1) + }, + assertions: func(result string) { + assert.Equal(t, "error while fetching user details: error getting user", result) + }, + }, + { + name: "Error creating post of unsubscribe with no repo", + parameters: []string{"owner"}, + setup: func() { + mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).DoAndReturn(func(key string, value **Subscriptions) error { + *value = &Subscriptions{Repositories: map[string][]*Subscription{ + "owner": {{ChannelID: "dummyChannelID", CreatorID: MockCreatorID, Repository: ""}}}} + return nil + }).Times(1) + mockAPI.On("GetUser", MockUserID).Return(&model.User{Username: MockUsername}, nil).Times(1) + mockAPI.On("CreatePost", mock.Anything).Return(nil, &model.AppError{Message: "error creating post"}).Times(1) + post.Message = "@mockUsername unsubscribed this channel from [owner](https://github.com/owner)" + mockAPI.On("LogWarn", "Error while creating post", "post", post, "error", "error creating post").Times(1) + }, + assertions: func(result string) { + assert.Equal(t, "@mockUsername unsubscribed this channel from [owner](https://github.com/owner) error creating the public post: error creating post", result) + }, + }, + { + name: "Success unsubscribing with no repo", + parameters: []string{"owner"}, + setup: func() { + mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).DoAndReturn(func(key string, value **Subscriptions) error { + *value = &Subscriptions{Repositories: map[string][]*Subscription{ + "owner": {{ChannelID: "dummyChannelID", CreatorID: MockCreatorID, Repository: ""}}}} + return nil + }).Times(1) + mockAPI.On("GetUser", MockUserID).Return(&model.User{Username: MockUsername}, nil).Times(1) + mockAPI.On("CreatePost", mock.Anything).Return(post, nil).Times(1) + }, + assertions: func(result string) { + assert.Empty(t, result) + }, + }, + { + name: "Error creating post of unsubscribe with no repo", + parameters: []string{"owner/repo"}, + setup: func() { + mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).DoAndReturn(func(key string, value **Subscriptions) error { + *value = &Subscriptions{Repositories: map[string][]*Subscription{ + "owner/repo": {{ChannelID: "dummyChannelID", CreatorID: MockCreatorID, Repository: "owner/repo"}}}} + return nil + }).Times(1) + mockAPI.On("GetUser", MockUserID).Return(&model.User{Username: MockUsername}, nil).Times(1) + mockAPI.On("CreatePost", mock.Anything).Return(nil, &model.AppError{Message: "error creating post"}).Times(1) + post.Message = "@mockUsername Unsubscribed this channel from [owner/repo](https://github.com/owner/repo)\n Please delete the [webhook](https://github.com/owner/repo/settings/hooks) for this subscription unless it's required for other subscriptions." + mockAPI.On("LogWarn", "Error while creating post", "post", post, "error", "error creating post").Times(1) + }, + assertions: func(result string) { + assert.Equal(t, "@mockUsername Unsubscribed this channel from [owner/repo](https://github.com/owner/repo)\n Please delete the [webhook](https://github.com/owner/repo/settings/hooks) for this subscription unless it's required for other subscriptions. error creating the public post: error creating post", result) + }, + }, + { + name: "Success unsubscribing with repo", + parameters: []string{"owner/repo"}, + setup: func() { + mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).DoAndReturn(func(key string, value **Subscriptions) error { + *value = &Subscriptions{Repositories: map[string][]*Subscription{ + "owner/repo": {{ChannelID: "dummyChannelID", CreatorID: MockCreatorID, Repository: "owner/repo"}}}} + return nil + }).Times(1) + mockAPI.ExpectedCalls = nil + mockAPI.On("GetUser", MockUserID).Return(&model.User{Username: MockUsername}, nil).Times(1) + mockAPI.On("CreatePost", mock.Anything).Return(post, nil).Times(1) + post.Message = "" + }, + assertions: func(result string) { + assert.Empty(t, result) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + mockAPI.ExpectedCalls = nil + tc.setup() + + args := &model.CommandArgs{ + UserId: MockUserID, + ChannelId: MockChannelID, + } + + result := p.handleUnsubscribe(nil, args, tc.parameters, nil) + + tc.assertions(result) + }) + } +} + +func TestHandleSettings(t *testing.T) { + mockKvStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + userInfo, err := GetMockGHUserInfo(p) + assert.NoError(t, err) + + tests := []struct { + name string + parameters []string + setup func() + assertions func(string) + expectedResult string + }{ + { + name: "Error: Not enough parameters", + parameters: []string{ + settingNotifications, + }, + setup: func() {}, + assertions: func(result string) { + assert.Equal(t, result, "Please specify both a setting and value. Use `/github help` for more usage information.") + }, + expectedResult: "Please specify both a setting and value. Use `/github help` for more usage information.", + }, + { + name: "Invalid setting value for notifications", + parameters: []string{ + settingNotifications, "invalid", + }, + setup: func() {}, + assertions: func(result string) { + assert.Equal(t, result, "Invalid value. Accepted values are: \"on\" or \"off\".") + }, + expectedResult: "Invalid value. Accepted values are: \"on\" or \"off\".", + }, + { + name: "Successfully enable notifications", + parameters: []string{ + settingNotifications, settingOn, + }, + setup: func() { + mockKvStore.EXPECT().Set(userInfo.GitHubUsername+githubUsernameKey, gomock.Any()).Return(true, nil).Times(1) + mockKvStore.EXPECT().Set(userInfo.UserID+githubTokenKey, gomock.Any()).Return(true, nil).Times(1) + }, + assertions: func(result string) { + assert.Equal(t, result, "Settings updated.") + }, + expectedResult: "Settings updated.", + }, + { + name: "Error enabling notifications", + parameters: []string{ + settingNotifications, settingOn, + }, + setup: func() { + mockKvStore.EXPECT().Set(userInfo.GitHubUsername+githubUsernameKey, gomock.Any()).Return(false, errors.New("error setting notification")).Times(1) + mockAPI.On("LogWarn", "Failed to store GitHub to userID mapping", "userID", "mockUserID", "GitHub username", "mockUsername", "error", "encountered error saving github username mapping: error setting notification").Times(1) + mockKvStore.EXPECT().Set(userInfo.UserID+githubTokenKey, gomock.Any()).Return(true, nil).Times(1) + }, + assertions: func(result string) { + assert.Equal(t, result, "Settings updated.") + }, + expectedResult: "Settings updated.", + }, + { + name: "Successfully disable notifications", + parameters: []string{ + settingNotifications, settingOff, + }, + setup: func() { + mockKvStore.EXPECT().Set(userInfo.GitHubUsername+githubUsernameKey, gomock.Any()).Return(true, nil).Times(1) + mockKvStore.EXPECT().Set(userInfo.UserID+githubTokenKey, gomock.Any()).Return(true, nil).Times(1) + mockKvStore.EXPECT().Delete(userInfo.GitHubUsername + githubUsernameKey).Return(nil).Times(1) + }, + assertions: func(result string) { + assert.Equal(t, result, "Settings updated.") + }, + expectedResult: "Settings updated.", + }, + { + name: "Error disabling notifications", + parameters: []string{ + settingNotifications, settingOff, + }, + setup: func() { + mockKvStore.EXPECT().Set(userInfo.GitHubUsername+githubUsernameKey, gomock.Any()).Return(true, nil).Times(1) + mockKvStore.EXPECT().Set(userInfo.UserID+githubTokenKey, gomock.Any()).Return(true, nil).Times(1) + mockKvStore.EXPECT().Delete(userInfo.GitHubUsername + githubUsernameKey).Return(errors.New("error setting notification")).Times(1) + mockAPI.On("LogWarn", "Failed to delete GitHub to userID mapping", "userID", "mockUserID", "GitHub username", "mockUsername", "error", "error setting notification").Times(1) + }, + assertions: func(result string) { + assert.Equal(t, result, "Settings updated.") + }, + expectedResult: "Settings updated.", + }, + { + name: "Successfully set reminders to on", + parameters: []string{ + settingReminders, settingOn, + }, + setup: func() { + mockKvStore.EXPECT().Set(userInfo.UserID+githubTokenKey, gomock.Any()).Return(true, nil).Times(1) + }, + assertions: func(result string) { + assert.Equal(t, result, "Settings updated.") + }, + expectedResult: "Settings updated.", + }, + { + name: "Successfully set reminders to off", + parameters: []string{ + settingReminders, settingOff, + }, + setup: func() { + mockKvStore.EXPECT().Set(userInfo.UserID+githubTokenKey, gomock.Any()).Return(true, nil).Times(1) + }, + assertions: func(result string) { + assert.Equal(t, result, "Settings updated.") + }, + expectedResult: "Settings updated.", + }, + { + name: "Successfully set reminders to on-change", + parameters: []string{ + settingReminders, settingOnChange, + }, + setup: func() { + mockKvStore.EXPECT().Set(userInfo.UserID+githubTokenKey, gomock.Any()).Return(true, nil).Times(1) + }, + assertions: func(result string) { + assert.Equal(t, result, "Settings updated.") + }, + expectedResult: "Settings updated.", + }, + { + name: "Invalid setting value for reminders", + parameters: []string{ + settingReminders, "invalid", + }, + setup: func() {}, + assertions: func(result string) { + assert.Equal(t, result, "Invalid value. Accepted values are: \"on\" or \"off\" or \"on-change\" .") + }, + expectedResult: "Invalid value. Accepted values are: \"on\" or \"off\" or \"on-change\" .", + }, + { + name: "Unknown setting", + parameters: []string{ + "unknownSetting", settingOn, + }, + setup: func() {}, + assertions: func(result string) { + assert.Equal(t, result, "Unknown setting unknownSetting") + }, + expectedResult: "Unknown setting unknownSetting", + }, + { + name: "Error while storing settings", + parameters: []string{ + settingReminders, settingOnChange, + }, + setup: func() { + mockKvStore.EXPECT().Set(userInfo.UserID+githubTokenKey, gomock.Any()).Return(false, errors.New("error storing user info")).Times(1) + mockAPI.On("LogWarn", "Failed to store github user info", "error", "error occurred while trying to store user info into KV store: error storing user info").Times(1) + }, + assertions: func(result string) { + assert.Equal(t, result, "Failed to store settings") + }, + expectedResult: "Failed to store settings", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + mockAPI.ExpectedCalls = nil + tc.setup() + + result := p.handleSettings(nil, nil, tc.parameters, userInfo) + + tc.assertions(result) + }) + } +} + +func TestHandleIssue(t *testing.T) { + mockKVStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKVStore) + userInfo, err := GetMockGHUserInfo(p) + assert.NoError(t, err) + + tests := []struct { + name string + parameters []string + setup func() + assertions func(result string) + }{ + { + name: "Invalid command: no parameters", + parameters: []string{}, + setup: func() {}, + assertions: func(result string) { + assert.Equal(t, "Invalid issue command. Available command is 'create'.", result) + }, + }, + { + name: "Unknown subcommand", + parameters: []string{"delete"}, + setup: func() {}, + assertions: func(result string) { + assert.Equal(t, "Unknown subcommand delete", result) + }, + }, + { + name: "Create issue with title", + parameters: []string{"create", "Test issue title"}, + setup: func() { + mockAPI.On("PublishWebSocketEvent", wsEventCreateIssue, + map[string]interface{}{ + "title": "Test issue title", + "channel_id": "testChannelID", + }, + &model.WebsocketBroadcast{UserId: "testUserID"}, + ).Return(nil).Once() + }, + assertions: func(result string) { + assert.Equal(t, "", result) + mockAPI.AssertCalled(t, "PublishWebSocketEvent", wsEventCreateIssue, + map[string]interface{}{ + "title": "Test issue title", + "channel_id": "testChannelID", + }, + &model.WebsocketBroadcast{UserId: "testUserID"}, + ) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + mockAPI.ExpectedCalls = nil + tc.setup() + + args := &model.CommandArgs{ + UserId: "testUserID", + ChannelId: "testChannelID", + } + + result := p.handleIssue(nil, args, tc.parameters, userInfo) + + tc.assertions(result) + }) + } +} + +func TestIsAuthorizedSysAdmin(t *testing.T) { + mockKVStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKVStore) + + tests := []struct { + name string + setup func() + assertions func(bool, error) + }{ + { + name: "Error getting user", + setup: func() { + mockAPI.On("GetUser", MockUserID).Return(nil, &model.AppError{Message: "error getting user"}).Times(1) + }, + assertions: func(result bool, err error) { + assert.False(t, result) + assert.EqualError(t, err, "error getting user") + }, + }, + { + name: "User is not a system admin", + setup: func() { + mockAPI.On("GetUser", MockUserID).Return(&model.User{Roles: "user"}, nil).Times(1) + }, + assertions: func(result bool, err error) { + assert.NoError(t, err) + assert.False(t, result) + }, + }, + { + name: "Successfully authorized as system admin", + setup: func() { + mockAPI.On("GetUser", MockUserID).Return(&model.User{Roles: "system_admin"}, nil).Times(1) + }, + assertions: func(result bool, err error) { + assert.NoError(t, err) + assert.True(t, result) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + mockAPI.ExpectedCalls = nil + tc.setup() + + result, err := p.isAuthorizedSysAdmin(MockUserID) + + tc.assertions(result, err) + }) + } +} + +func TestHandleSubscribe(t *testing.T) { + mockKVStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKVStore) + userInfo, err := GetMockGHUserInfo(p) + assert.NoError(t, err) + + tests := []struct { + name string + parameters []string + setup func() + assertions func(result string) + }{ + { + name: "No parameters provided", + parameters: []string{}, + setup: func() {}, + assertions: func(result string) { + assert.Equal(t, "Please specify a repository or 'list' command.", result) + }, + }, + { + name: "List command provided", + parameters: []string{"list"}, + setup: func() { + mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).Return(errors.New("error getting subscription")).Times(1) + }, + assertions: func(result string) { + assert.Equal(t, "could not get subscriptions: could not get subscriptions from KVStore: error getting subscription", result) + }, + }, + { + name: "default case, handleSubscribesAdd called", + parameters: []string{"invalid_parameter_1", "invalid_parameter_2", "invalid_parameter_3"}, + setup: func() { + + }, + assertions: func(result string) { + assert.Equal(t, "Please use the correct format for flags: -- ", result) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tc.setup() + + args := &model.CommandArgs{ + UserId: "test-user-id", + ChannelId: "test-channel-id", + } + + result := p.handleSubscribe(nil, args, tc.parameters, userInfo) + + tc.assertions(result) + }) + } +} + +func TestHandleSubscriptions(t *testing.T) { + mockKVStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKVStore) + userInfo, err := GetMockGHUserInfo(p) + assert.NoError(t, err) + + tests := []struct { + name string + parameters []string + setup func() + assertions func(result string) + }{ + { + name: "No parameters provided", + parameters: []string{}, + setup: func() {}, + assertions: func(result string) { + assert.Equal(t, "Invalid subscribe command. Available commands are 'list', 'add' and 'delete'.", result) + }, + }, + { + name: "List command provided", + parameters: []string{"list"}, + setup: func() { + mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).Return(errors.New("error getting subscription")).Times(1) + }, + assertions: func(result string) { + assert.Equal(t, "could not get subscriptions: could not get subscriptions from KVStore: error getting subscription", result) + }, + }, + { + name: "Add command provided", + parameters: []string{"add", "invalid_parameter_1", "invalid_parameter_2", "invalid_parameter_3"}, + setup: func() {}, + assertions: func(result string) { + assert.Equal(t, "Please use the correct format for flags: -- ", result) + }, + }, + { + name: "Delete command provided", + parameters: []string{"delete"}, + setup: func() {}, + assertions: func(result string) { + assert.Equal(t, "Please specify a repository.", result) + }, + }, + { + name: "Unknown subcommand", + parameters: []string{"unknown"}, + setup: func() {}, + assertions: func(result string) { + assert.Equal(t, "Unknown subcommand unknown", result) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tc.setup() + + args := &model.CommandArgs{ + UserId: "test-user-id", + ChannelId: "test-channel-id", + } + + result := p.handleSubscriptions(nil, args, tc.parameters, userInfo) + + tc.assertions(result) + }) + } +} + +func TestGetCommand(t *testing.T) { + mockKVStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKVStore) + + // Creating a mock SVG file with dummy content. + tempDir := t.TempDir() + assetsDir := filepath.Join(tempDir, "assets") + err := os.Mkdir(assetsDir, 0755) + require.NoError(t, err) + tempFilePath := filepath.Join(assetsDir, "icon-bg.svg") + err = os.WriteFile(tempFilePath, []byte("icon data"), 0600) + require.NoError(t, err) + + tests := []struct { + name string + setup func() + assertions func(*model.Command, error) + }{ + { + name: "Error getting icon data", + setup: func() { + mockAPI.On("GetBundlePath").Return("", errors.New("error getting bundle path")).Times(1) + }, + assertions: func(cmd *model.Command, err error) { + assert.Nil(t, cmd) + assert.EqualError(t, err, "failed to get icon data: couldn't get bundle path: error getting bundle path") + }, + }, + { + name: "Successfully retrieves command", + setup: func() { + mockAPI.On("GetBundlePath").Return(tempDir, nil).Times(1) + }, + assertions: func(cmd *model.Command, err error) { + assert.NoError(t, err) + assert.Contains(t, cmd.AutocompleteIconData, "data:image/svg+xml;base64,") + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + mockAPI.ExpectedCalls = nil + tc.setup() + + cmd, err := p.getCommand(&Configuration{}) + + tc.assertions(cmd, err) + }) + } +} + +func TestHandleHelp(t *testing.T) { + mockKVStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKVStore) + + t.Run("Successfully get help text", func(t *testing.T) { + response := p.handleHelp(&plugin.Context{}, &model.CommandArgs{}, []string{}, &GitHubUserInfo{}) + assert.Contains(t, response, "###### Mattermost GitHub Plugin - Slash Command Help\n") + }) +} + +func TestFormattedString(t *testing.T) { + tests := []struct { + name string + features Features + expectedString string + }{ + { + name: "Single feature", + features: "feature1", + expectedString: "`feature1`", + }, + { + name: "Multiple features", + features: "feature1,feature2,feature3", + expectedString: "`feature1`, `feature2`, `feature3`", + }, + { + name: "Empty features", + features: "", + expectedString: "``", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := tc.features.FormattedString() + assert.Equal(t, tc.expectedString, result) + }) + } +} + +func TestToSlice(t *testing.T) { + tests := []struct { + name string + features Features + expectedSlice []string + }{ + { + name: "Single feature", + features: "feature1", + expectedSlice: []string{"feature1"}, + }, + { + name: "Multiple features", + features: "feature1,feature2,feature3", + expectedSlice: []string{"feature1", "feature2", "feature3"}, + }, + { + name: "Empty features", + features: "", + expectedSlice: []string{""}, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := tc.features.ToSlice() + assert.Equal(t, tc.expectedSlice, result) + }) + } +} + +func TestSliceContainsString(t *testing.T) { + tests := []struct { + name string + slice []string + searchString string + expectedResult bool + }{ + { + name: "Empty slice", + slice: []string{}, + searchString: "testString1", + expectedResult: false, + }, + { + name: "String exists in slice", + slice: []string{"testString1", "testString2", "testString3"}, + searchString: "testString2", + expectedResult: true, + }, + { + name: "String does not exist in slice", + slice: []string{"testString1", "testString2", "testString3"}, + searchString: "testString4", + expectedResult: false, + }, + { + name: "String is the first element in the slice", + slice: []string{"testString2", "testString1", "testString3"}, + searchString: "testString1", + expectedResult: true, + }, + { + name: "String is the last element in the slice", + slice: []string{"testString1", "testString3", "testString2"}, + searchString: "testString2", + expectedResult: true, + }, + { + name: "String with different case", + slice: []string{"testString1", "testString2", "TestString3"}, + searchString: "testString3", + expectedResult: false, + }, + { + name: "Search string is empty", + slice: []string{"testString1", "testString2", "testString3"}, + searchString: "", + expectedResult: false, + }, + { + name: "Slice contains empty string", + slice: []string{"testString1", "testString2", ""}, + searchString: "", + expectedResult: true, + }, + { + name: "Slice with multiple occurrences of the search string", + slice: []string{"testString2", "testString1", "testString2", "testString3"}, + searchString: "testString2", + expectedResult: true, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := SliceContainsString(tc.slice, tc.searchString) + assert.Equal(t, tc.expectedResult, result) + }) + } +} diff --git a/server/plugin/flows.go b/server/plugin/flows.go index 411d70e87..226bbca85 100644 --- a/server/plugin/flows.go +++ b/server/plugin/flows.go @@ -698,7 +698,7 @@ func (fm *FlowManager) submitWebhook(f *flow.Flow, submitted map[string]interfac return "", nil, nil, errors.New("invalid format") } - webhookEvents := []string{"create", "delete", "issue_comment", "issues", "pull_request", "pull_request_review", "pull_request_review_comment", "push", "star"} + webhookEvents := []string{"create", "delete", "issue_comment", "issues", "pull_request", "pull_request_review", "pull_request_review_comment", "push", "star", "workflow_job", "workflow_run", "discussion", "discussion_comment", "release"} webHookURL, err := buildPluginURL(fm.client, "webhook") if err != nil { diff --git a/server/plugin/subscriptions.go b/server/plugin/subscriptions.go index aeb4787ea..85c67489b 100644 --- a/server/plugin/subscriptions.go +++ b/server/plugin/subscriptions.go @@ -17,17 +17,19 @@ import ( ) const ( - SubscriptionsKey = "subscriptions" - flagExcludeOrgMember = "exclude-org-member" - flagRenderStyle = "render-style" - flagFeatures = "features" - flagExcludeRepository = "exclude" + SubscriptionsKey = "subscriptions" + flagExcludeOrgMember = "exclude-org-member" + flagIncludeOnlyOrgMembers = "include-only-org-members" + flagRenderStyle = "render-style" + flagFeatures = "features" + flagExcludeRepository = "exclude" ) type SubscriptionFlags struct { - ExcludeOrgMembers bool - RenderStyle string - ExcludeRepository []string + ExcludeOrgMembers bool + IncludeOnlyOrgMembers bool + RenderStyle string + ExcludeRepository []string } func (s *SubscriptionFlags) AddFlag(flag string, value string) error { @@ -38,6 +40,12 @@ func (s *SubscriptionFlags) AddFlag(flag string, value string) error { return err } s.ExcludeOrgMembers = parsed + case flagIncludeOnlyOrgMembers: + parsed, err := strconv.ParseBool(value) + if err != nil { + return err + } + s.IncludeOnlyOrgMembers = parsed case flagRenderStyle: s.RenderStyle = value case flagExcludeRepository: @@ -59,6 +67,11 @@ func (s SubscriptionFlags) String() string { flags = append(flags, flag) } + if s.IncludeOnlyOrgMembers { + flag := "--" + flagIncludeOnlyOrgMembers + " true" + flags = append(flags, flag) + } + if s.RenderStyle != "" { flag := "--" + flagRenderStyle + " " + s.RenderStyle flags = append(flags, flag) @@ -161,6 +174,10 @@ func (s *Subscription) ExcludeOrgMembers() bool { return s.Flags.ExcludeOrgMembers } +func (s *Subscription) IncludeOnlyOrgMembers() bool { + return s.Flags.IncludeOnlyOrgMembers +} + func (s *Subscription) RenderStyle() string { return s.Flags.RenderStyle } @@ -190,6 +207,10 @@ func (p *Plugin) Subscribe(ctx context.Context, githubClient *github.Client, use return errors.New("Unable to set --exclude-org-member flag. The GitHub plugin is not locked to a single organization.") } + if flags.IncludeOnlyOrgMembers && !p.isOrganizationLocked() { + return errors.New("Unable to set --include-only-org-members flag. The GitHub plugin is not locked to a single organization.") + } + var err, cErr error if repo == "" { diff --git a/server/plugin/subscriptions_test.go b/server/plugin/subscriptions_test.go index dfca197fe..56e12e479 100644 --- a/server/plugin/subscriptions_test.go +++ b/server/plugin/subscriptions_test.go @@ -4,8 +4,11 @@ package plugin import ( + "context" "testing" + "github.com/google/go-github/v54/github" + "github.com/mattermost/mattermost/server/public/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -121,3 +124,102 @@ func TestPlugin_GetSubscriptionsByChannel(t *testing.T) { }) } } + +func TestAddFlag(t *testing.T) { + tests := []struct { + name string + flags SubscriptionFlags + flag string + value string + want bool + wantErr bool + }{ + { + name: "IncludeOnlyOrgMembers flag is parsed", + flags: SubscriptionFlags{}, + flag: "include-only-org-members", + value: "true", + want: true, + wantErr: false, + }, + { + name: "IncludeOnlyOrgMembers flag cannot be parsed", + flags: SubscriptionFlags{}, + flag: "include-only-org-members", + value: "test", + want: false, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Helper() + + err := tt.flags.AddFlag(tt.flag, tt.value) + CheckError(t, tt.wantErr, err) + assert.Equal(t, tt.flags.IncludeOnlyOrgMembers, tt.want) + }) + } +} + +func TestString(t *testing.T) { + tests := []struct { + name string + flags SubscriptionFlags + flag string + value string + want string + }{ + { + name: "Return --include-only-org-members string", + flags: SubscriptionFlags{}, + flag: "include-only-org-members", + value: "true", + want: "--include-only-org-members true", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Helper() + _ = tt.flags.AddFlag(tt.flag, tt.value) + got := tt.flags.String() + assert.Equal(t, tt.want, got) + }) + } +} + +func TestSubscribe(t *testing.T) { + tests := []struct { + name string + flags SubscriptionFlags + plugin *Plugin + errMsg string + }{ + { + name: "Return error if GitHub organization is not set when --include-only-org-members flag is true", + flags: SubscriptionFlags{IncludeOnlyOrgMembers: true}, + plugin: NewPlugin(), + errMsg: "Unable to set --include-only-org-members flag. The GitHub plugin is not locked to a single organization.", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Helper() + err := tt.plugin.Subscribe( + context.Background(), + github.NewClient(nil), + model.NewId(), + "test-owner", + "test-repo", + model.NewId(), + "test-features", + tt.flags, + ) + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errMsg) + }) + } +} diff --git a/server/plugin/template.go b/server/plugin/template.go index 441bfbf4f..372f56d21 100644 --- a/server/plugin/template.go +++ b/server/plugin/template.go @@ -451,6 +451,7 @@ Reviewers: {{range $i, $el := .RequestedReviewers -}} {{- if $i}}, {{end}}{{temp " * `discussion_comments` - includes new discussion comments\n" + " * Defaults to `pulls,issues,creates,deletes`\n\n" + " * `--exclude-org-member` - events triggered by organization members will not be delivered (the GitHub organization config should be set, otherwise this flag has not effect)\n" + + " * `--include-only-org-members` - events triggered only by organization members will be delivered (the GitHub organization config should be set, otherwise this flag has not effect)\n" + " * `--render-style` - notifications will be delivered in the specified style (for example, the body of a pull request will not be displayed). Supported values are `collapsed`, `skip-body` or `default` (same as omitting the flag).\n" + "* `/github subscriptions delete owner[/repo]` - Unsubscribe the current channel from a repository\n" + "* `/github me` - Display the connected GitHub account\n" + @@ -486,13 +487,22 @@ Step failed: {{.GetWorkflowJob.Steps | workflowJobFailedStep}} {{- end -}}`)) template.Must(masterTemplate.New("newDiscussion").Funcs(funcMap).Parse(` -{{template "user" .GetSender}} started a new discussion [#{{.GetDiscussion.GetNumber}} {{.GetDiscussion.GetTitle}}]({{.GetDiscussion.GetHTMLURL}}) on {{template "repo" .GetRepo}} +{{template "user" .GetSender}} +{{- if eq .GetAction "created" }} started a new +{{- else if eq .GetAction "closed" }} closed a +{{- else if eq .GetAction "reopened" }} reopened a +{{- else if eq .GetAction "edited" }} edited a +{{- end }} discussion [#{{.GetDiscussion.GetNumber}} {{.GetDiscussion.GetTitle}}]({{.GetDiscussion.GetHTMLURL}}) on {{template "repo" .GetRepo}} `)) template.Must(masterTemplate.New("newDiscussionComment").Funcs(funcMap).Parse(` -{{template "repo" .GetRepo}} New comment by {{template "user" .GetSender}} on discussion [#{{.GetDiscussion.GetNumber}} {{.GetDiscussion.GetTitle}}]({{.GetDiscussion.GetHTMLURL}}): +{{template "repo" .GetRepo}} +{{- if eq .GetAction "created" }} New comment +{{- else if eq .GetAction "edited" }} Comment edited +{{- else if eq .GetAction "deleted" }} Comment deleted +{{- end }} by {{template "user" .GetSender}} on discussion [#{{.GetDiscussion.GetNumber}} {{.GetDiscussion.GetTitle}}]({{.GetDiscussion.GetHTMLURL}}): -{{.GetComment.GetBody | trimBody | replaceAllGitHubUsernames}} +> {{.GetComment.GetBody | trimBody | replaceAllGitHubUsernames}} `)) } diff --git a/server/plugin/test_utils.go b/server/plugin/test_utils.go new file mode 100644 index 000000000..cc0b2e97d --- /dev/null +++ b/server/plugin/test_utils.go @@ -0,0 +1,80 @@ +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package plugin + +import ( + "context" + "testing" + + "github.com/golang/mock/gomock" + "golang.org/x/oauth2" + + "github.com/mattermost/mattermost-plugin-github/server/mocks" + + "github.com/mattermost/mattermost/server/public/plugin/plugintest" +) + +const ( + MockUserID = "mockUserID" + MockUsername = "mockUsername" + MockAccessToken = "mockAccessToken" + MockChannelID = "mockChannelID" + MockCreatorID = "mockCreatorID" + MockBotID = "mockBotID" + MockPostMessage = "mockPostMessage" +) + +func GetMockGHUserInfo(p *Plugin) (*GitHubUserInfo, error) { + encryptionKey := "dummyEncryptKey1" + p.setConfiguration(&Configuration{EncryptionKey: encryptionKey}) + encryptedToken, err := encrypt([]byte(encryptionKey), MockAccessToken) + if err != nil { + return nil, err + } + gitHubUserInfo := &GitHubUserInfo{ + UserID: MockUserID, + GitHubUsername: MockUsername, + Token: &oauth2.Token{AccessToken: encryptedToken}, + Settings: &UserSettings{}, + } + + return gitHubUserInfo, nil +} + +func GetTestSetup(t *testing.T) (*mocks.MockKvStore, *plugintest.API, *mocks.MockLogger, *mocks.MockLogger, *Context) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockKvStore := mocks.NewMockKvStore(mockCtrl) + mockAPI := &plugintest.API{} + mockLogger := mocks.NewMockLogger(mockCtrl) + mockLoggerWith := mocks.NewMockLogger(mockCtrl) + mockContext := GetMockContext(mockLogger) + + return mockKvStore, mockAPI, mockLogger, mockLoggerWith, &mockContext +} + +func GetMockContext(mockLogger *mocks.MockLogger) Context { + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + defer cancel() + + return Context{ + Ctx: ctx, + UserID: MockUserID, + Log: mockLogger, + } +} + +func GetMockUserContext(p *Plugin, mockLogger *mocks.MockLogger) (*UserContext, error) { + mockGHUserInfo, err := GetMockGHUserInfo(p) + if err != nil { + return nil, err + } + + mockUserContext := &UserContext{ + GetMockContext(mockLogger), + mockGHUserInfo, + } + + return mockUserContext, nil +} diff --git a/server/plugin/utils.go b/server/plugin/utils.go index 21784c0ad..283a3ec9c 100644 --- a/server/plugin/utils.go +++ b/server/plugin/utils.go @@ -44,14 +44,19 @@ func getYourAssigneeSearchQuery(username string, orgs []string) string { return buildSearchQuery("is:open assignee:%v archived:false %v", username, orgs) } -func getIssuesSearchQuery(org, searchTerm string) string { +func getIssuesSearchQuery(searchValue, searchTerm string) string { query := "is:open is:issue archived:false %v %v" - orgField := "" - if len(org) != 0 { - orgField = fmt.Sprintf("org:%v", org) + searchField := "" + if len(searchValue) != 0 { + searchField = fmt.Sprintf("org:%v", searchValue) } - return fmt.Sprintf(query, orgField, searchTerm) + // get all the issues which involve the user in case no organizational lock is set + // else { + // searchField = "involves:@me" + // } + + return fmt.Sprintf(query, searchField, searchTerm) } func buildSearchQuery(query, username string, orgs []string) string { diff --git a/server/plugin/webhook.go b/server/plugin/webhook.go index 9a7dc6150..1b4ac7779 100644 --- a/server/plugin/webhook.go +++ b/server/plugin/webhook.go @@ -193,6 +193,7 @@ func (wb *WebhookBroker) Close() { } func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) { + p.client.Log.Info("Webhook event received") config := p.getConfiguration() body, err := io.ReadAll(r.Body) if err != nil { @@ -378,6 +379,26 @@ func (p *Plugin) excludeConfigOrgMember(user *github.User, subscription *Subscri return p.isUserOrganizationMember(githubClient, user, info, organization) } +func (p *Plugin) shouldDenyEventDueToNotOrgMember(user *github.User, subscription *Subscription) bool { + if !subscription.IncludeOnlyOrgMembers() { + return false + } + + githubClient, err := p.GetGitHubClient(context.Background(), subscription.CreatorID) + if err != nil { + p.client.Log.Warn("Failed to get user info", "error", err.Error()) + return false + } + + info, nErr := p.getGitHubUserInfo(subscription.CreatorID) + if nErr != nil { + p.client.Log.Warn("Failed to exclude org member", "error", nErr.Message) + return false + } + + return !p.isUserOrganizationMember(githubClient, user, info, p.getConfiguration().GitHubOrg) +} + func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { repo := event.GetRepo() @@ -434,6 +455,10 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { continue } + if p.shouldDenyEventDueToNotOrgMember(event.GetSender(), sub) { + continue + } + label := sub.Label() contained := false @@ -629,6 +654,10 @@ func (p *Plugin) postIssueEvent(event *github.IssuesEvent) { continue } + if p.shouldDenyEventDueToNotOrgMember(event.GetSender(), sub) { + continue + } + renderedMessage, err := renderTemplate(issueTemplate, GetEventWithRenderConfig(event, sub)) if err != nil { p.client.Log.Warn("Failed to render template", "error", err.Error()) @@ -768,6 +797,10 @@ func (p *Plugin) postPushEvent(event *github.PushEvent) { continue } + if p.shouldDenyEventDueToNotOrgMember(event.GetSender(), sub) { + continue + } + post := p.makeBotPost(pushedCommitsMessage, "custom_git_push") post.ChannelId = sub.ChannelID @@ -805,6 +838,10 @@ func (p *Plugin) postCreateEvent(event *github.CreateEvent) { continue } + if p.shouldDenyEventDueToNotOrgMember(event.GetSender(), sub) { + continue + } + post := p.makeBotPost(newCreateMessage, "custom_git_create") post.ChannelId = sub.ChannelID @@ -844,6 +881,10 @@ func (p *Plugin) postDeleteEvent(event *github.DeleteEvent) { continue } + if p.shouldDenyEventDueToNotOrgMember(event.GetSender(), sub) { + continue + } + post := p.makeBotPost(newDeleteMessage, "custom_git_delete") post.ChannelId = sub.ChannelID if err = p.client.Post.CreatePost(post); err != nil { @@ -885,6 +926,10 @@ func (p *Plugin) postIssueCommentEvent(event *github.IssueCommentEvent) { continue } + if p.shouldDenyEventDueToNotOrgMember(event.GetSender(), sub) { + continue + } + label := sub.Label() contained := false @@ -972,6 +1017,10 @@ func (p *Plugin) postPullRequestReviewEvent(event *github.PullRequestReviewEvent continue } + if p.shouldDenyEventDueToNotOrgMember(event.GetSender(), sub) { + continue + } + label := sub.Label() contained := false @@ -1022,6 +1071,10 @@ func (p *Plugin) postPullRequestReviewCommentEvent(event *github.PullRequestRevi continue } + if p.shouldDenyEventDueToNotOrgMember(event.GetSender(), sub) { + continue + } + label := sub.Label() contained := false @@ -1430,6 +1483,10 @@ func (p *Plugin) postStarEvent(event *github.StarEvent) { continue } + if p.shouldDenyEventDueToNotOrgMember(event.GetSender(), sub) { + continue + } + post := p.makeBotPost(newStarMessage, "custom_git_star") post.ChannelId = sub.ChannelID @@ -1570,10 +1627,6 @@ func (p *Plugin) postDiscussionCommentEvent(event *github.DiscussionCommentEvent return } - if event.GetAction() != actionCreated { - return - } - newDiscussionCommentMessage, err := renderTemplate("newDiscussionComment", event) if err != nil { p.client.Log.Warn("Failed to render template", "error", err.Error()) diff --git a/server/plugin/webhook_test.go b/server/plugin/webhook_test.go new file mode 100644 index 000000000..4fb41396f --- /dev/null +++ b/server/plugin/webhook_test.go @@ -0,0 +1,130 @@ +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package plugin + +import ( + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/mattermost/mattermost/server/public/model" + "github.com/mattermost/mattermost/server/public/plugin/plugintest" + "github.com/mattermost/mattermost/server/public/plugin/plugintest/mock" + "github.com/mattermost/mattermost/server/public/pluginapi" + "golang.org/x/oauth2" + + "github.com/google/go-github/v54/github" + "github.com/stretchr/testify/assert" +) + +const webhookSecret = "whsecret" +const orgMember = "org-member" +const orgCollaborator = "org-collaborator" +const gitHubOrginization = "test-org" + +func newPlugin(userID string, gitHubURL string) *Plugin { + p := NewPlugin() + p.initializeAPI() + p.SetDriver(&plugintest.Driver{}) + p.store = &pluginapi.MemoryStore{} + token, _ := generateSecret() + encryptionKey, _ := generateSecret() + encryptedToken, _ := encrypt([]byte(encryptionKey), token) + _, _ = p.store.Set(userID+githubTokenKey, GitHubUserInfo{ + UserID: userID, + Token: &oauth2.Token{ + AccessToken: encryptedToken, + }, + }) + p.setConfiguration(&Configuration{ + EncryptionKey: encryptionKey, + GitHubOrg: gitHubOrginization, + WebhookSecret: webhookSecret, + EnterpriseBaseURL: gitHubURL, + EnterpriseUploadURL: gitHubURL, + }) + + _ = p.AddSubscription( + gitHubOrginization+"/test-repo", + &Subscription{ + ChannelID: "1", + CreatorID: userID, + Features: Features(strings.Join([]string{featureIssues, featureIssueCreation}, ",")), + Flags: SubscriptionFlags{IncludeOnlyOrgMembers: true}, + }, + ) + + return p +} + +func mockGitHubServer(user string) *httptest.Server { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != fmt.Sprintf("/api/v3/orgs/%v/members/%v", gitHubOrginization, user) { + http.Error(w, "Not found", http.StatusNotFound) + return + } + + if user == orgMember { + w.WriteHeader(http.StatusNoContent) + } else if user == orgCollaborator { + w.WriteHeader(http.StatusFound) + } + })) + + return ts +} + +func TestIncludeOnlyOrgMembers(t *testing.T) { + tests := []struct { + name string + user github.User + subscription Subscription + expectWarn bool + want bool + }{ + { + name: "IncludeOnlyOrgMembers flag is false", + user: github.User{ + Login: github.String(orgMember), + }, + subscription: Subscription{ + Flags: SubscriptionFlags{IncludeOnlyOrgMembers: false}, + }, + expectWarn: false, + want: false, + }, + { + name: "Failed to get GitHub Client", + user: github.User{ + Login: github.String(orgMember), + }, + subscription: Subscription{ + CreatorID: model.NewId(), + Flags: SubscriptionFlags{IncludeOnlyOrgMembers: true}, + }, + expectWarn: true, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Helper() + user := *tt.user.Login + server := mockGitHubServer(user) + gitHubPlugin := newPlugin(model.NewId(), server.URL) + api := plugintest.NewAPI(t) + if tt.expectWarn { + api.On("LogWarn", mock.AnythingOfType("string"), "error", mock.AnythingOfType("string")).Return(nil) + } + gitHubPlugin.SetAPI(api) + gitHubPlugin.client = pluginapi.NewClient(gitHubPlugin.API, gitHubPlugin.Driver) + + got := gitHubPlugin.shouldDenyEventDueToNotOrgMember(&tt.user, &tt.subscription) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 49b51da95..c0ad9e243 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -15,6 +15,7 @@ "react": "16.13.1", "react-bootstrap": "1.3.0", "react-custom-scrollbars": "4.2.1", + "react-dom": "16.13.1", "react-markdown": "^8.0.5", "react-redux": "7.2.1", "react-select": "3.1.0", @@ -35,6 +36,7 @@ "@emotion/babel-preset-css-prop": "10.0.27", "@emotion/core": "10.0.35", "@types/enzyme": "3.10.6", + "@types/enzyme-adapter-react-16": "1.0.9", "@types/jest": "26.0.14", "@types/node": "14.11.1", "@types/react": "16.9.49", @@ -78,7 +80,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -117,11 +118,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.7", + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", "picocolors": "^1.0.0" }, "engines": { @@ -129,10 +132,10 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", - "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", - "dev": true, + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -141,7 +144,6 @@ "version": "7.11.6", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/generator": "^7.11.6", @@ -169,26 +171,28 @@ } }, "node_modules/@babel/generator": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", - "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.25.6", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", - "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -198,7 +202,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", - "dev": true, "dependencies": { "@babel/traverse": "^7.24.7", "@babel/types": "^7.24.7" @@ -208,14 +211,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", - "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", - "dev": true, + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", + "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", + "@babel/compat-data": "^7.26.8", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -227,23 +230,22 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz", - "integrity": "sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==", - "dev": true, + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", + "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/helper-replace-supers": "^7.25.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/traverse": "^7.25.4", + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.27.0", "semver": "^6.3.1" }, "engines": { @@ -257,7 +259,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -266,7 +267,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz", "integrity": "sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==", - "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.24.7", "regexpu-core": "^5.3.1", @@ -283,30 +283,32 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "dev": true, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", + "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", + "license": "MIT", + "peer": true, "dependencies": { - "@babel/types": "^7.24.7" + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-function-name": { + "node_modules/@babel/helper-environment-visitor": { "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", - "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", - "dev": true, + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", "dependencies": { - "@babel/template": "^7.24.7", "@babel/types": "^7.24.7" }, "engines": { @@ -317,7 +319,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", - "dev": true, "dependencies": { "@babel/types": "^7.24.7" }, @@ -326,40 +327,40 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", - "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.8", - "@babel/types": "^7.24.8" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", - "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", - "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", - "dev": true, + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.2" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -369,35 +370,35 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", - "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", - "dev": true, + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", - "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-wrap-function": "^7.25.0", - "@babel/traverse": "^7.25.0" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -407,14 +408,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", - "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", - "dev": true, + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", + "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/traverse": "^7.25.0" + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.26.5" }, "engines": { "node": ">=6.9.0" @@ -423,116 +424,80 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", - "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", - "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", - "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", - "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "license": "MIT", "dependencies": { - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", - "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", - "dev": true, - "dependencies": { - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", - "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.25.6" + "@babel/types": "^7.27.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -546,7 +511,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.", - "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-plugin-utils": "^7.20.2", @@ -565,7 +529,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", - "dev": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.10.4", "@babel/helper-plugin-utils": "^7.10.4" @@ -579,7 +542,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-dynamic-import instead.", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-dynamic-import": "^7.8.3" @@ -591,12 +553,27 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-proposal-export-default-from": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.25.9.tgz", + "integrity": "sha512-ykqgwNfSnNOB+C8fV5X4mG3AVmvu+WVxcaU9xHHtBb7PCrPeweMmPjGsn8eMaeJg6SJuoUuZENeeSWaarWqonQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-proposal-export-namespace-from": { "version": "7.18.9", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.9", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" @@ -613,7 +590,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-json-strings instead.", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-json-strings": "^7.8.3" @@ -630,7 +606,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead.", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.20.2", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" @@ -647,7 +622,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" @@ -664,7 +638,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-numeric-separator": "^7.10.4" @@ -681,7 +654,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz", "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", @@ -696,7 +668,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" @@ -713,7 +684,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz", "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0", @@ -728,7 +698,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", - "dev": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -745,7 +714,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead.", - "dev": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -761,7 +729,6 @@ "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -773,7 +740,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -785,7 +751,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -797,7 +762,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -805,11 +769,26 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-export-default-from": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.25.9.tgz", + "integrity": "sha512-9MhJ/SMTsVqsd69GyQg89lYR4o9T+oDGv5F6IsigxxqFVOyR/IflDLYP8WDI1l8fkhNGGktqkvL5qwNCtGEpgQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-export-namespace-from": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.3" }, @@ -817,11 +796,26 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.26.0.tgz", + "integrity": "sha512-B+O2DnPc0iG+YXFqOxv2WNuNU97ToWjOomUQ78DouOENWUaM5sVrmet9mcomUGQFwpJd//gvUagXBSdzO1fRKg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -833,7 +827,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -842,12 +835,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", - "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -860,7 +853,6 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -872,7 +864,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -884,7 +875,6 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -896,7 +886,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -908,7 +897,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -920,7 +908,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -932,7 +919,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -944,12 +930,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", - "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -962,7 +948,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, @@ -973,11 +958,28 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", + "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.26.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-async-to-generator": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", - "dev": true, "dependencies": { "@babel/helper-module-imports": "^7.24.7", "@babel/helper-plugin-utils": "^7.24.7", @@ -994,7 +996,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, @@ -1006,12 +1007,29 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz", - "integrity": "sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==", - "dev": true, + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.0.tgz", + "integrity": "sha512-u1jGphZ8uDI2Pj/HJj6YQ6XQLZCNjOlprjxB5SVz6rq2T6SwAR+CdrWK0CP7F+9rDVMXdB0+r6Am5G5aobOjAQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1021,18 +1039,16 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.7.tgz", - "integrity": "sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", "globals": "^11.1.0" }, "engines": { @@ -1046,7 +1062,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7", "@babel/template": "^7.24.7" @@ -1059,12 +1074,12 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.7.tgz", - "integrity": "sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1077,7 +1092,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", - "dev": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.24.7", "@babel/helper-plugin-utils": "^7.24.7" @@ -1093,7 +1107,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, @@ -1108,7 +1121,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", - "dev": true, "dependencies": { "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", "@babel/helper-plugin-utils": "^7.24.7" @@ -1120,11 +1132,27 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.26.5.tgz", + "integrity": "sha512-eGK26RsbIkYUns3Y8qKl362juDDYK+wEdPGHGrhzUl6CewZFo55VZ7hg+CyMFU4dd5QQakBN86nBMpRsFpRvbQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/plugin-syntax-flow": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-for-of": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" @@ -1137,14 +1165,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz", - "integrity": "sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1154,12 +1182,28 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz", - "integrity": "sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1172,7 +1216,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, @@ -1187,7 +1230,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", - "dev": true, "dependencies": { "@babel/helper-module-transforms": "^7.24.7", "@babel/helper-plugin-utils": "^7.24.7" @@ -1200,14 +1242,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz", - "integrity": "sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==", - "dev": true, + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7" + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1220,7 +1261,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz", "integrity": "sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==", - "dev": true, "dependencies": { "@babel/helper-hoist-variables": "^7.24.7", "@babel/helper-module-transforms": "^7.24.7", @@ -1238,7 +1278,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", - "dev": true, "dependencies": { "@babel/helper-module-transforms": "^7.24.7", "@babel/helper-plugin-utils": "^7.24.7" @@ -1254,7 +1293,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", - "dev": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.24.7", "@babel/helper-plugin-utils": "^7.24.7" @@ -1270,7 +1308,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, @@ -1281,11 +1318,60 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.26.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", + "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-object-super": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7", "@babel/helper-replace-supers": "^7.24.7" @@ -1297,13 +1383,81 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", - "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1316,7 +1470,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, @@ -1331,7 +1484,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz", "integrity": "sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, @@ -1343,16 +1495,16 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.24.7.tgz", - "integrity": "sha512-+Dj06GDZEFRYvclU6k4bme55GKBEWUmByM/eoKuqg4zTNQHiApWRhQph5fxQB2wAEFvRzL1tOEj1RJ19wJrhoA==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", + "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-jsx": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1380,7 +1532,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, @@ -1395,7 +1546,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, @@ -1426,7 +1576,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7", "regenerator-transform": "^0.15.2" @@ -1442,7 +1591,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, @@ -1453,11 +1601,41 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz", + "integrity": "sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/plugin-transform-shorthand-properties": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, @@ -1472,7 +1650,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" @@ -1488,7 +1665,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, @@ -1503,7 +1679,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, @@ -1518,7 +1693,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.7.tgz", "integrity": "sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, @@ -1530,15 +1704,16 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.7.tgz", - "integrity": "sha512-iLD3UNkgx2n/HrjBesVbYX6j0yqn/sJktvbtKKgcaLIQ4bTTQ8obAypc1VpyHPD2y4Phh9zHOaAt8e/L14wCpw==", - "dev": true, + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.0.tgz", + "integrity": "sha512-fRGGjO2UEGPjvEcyAZXRXAS8AfdaQoq7HnxAbJoAoW10B9xOKesmmndJv+Sym2a+9FHWZ9KbyyLCe9s0Sn5jtg==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-typescript": "^7.24.7" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.27.0", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-syntax-typescript": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1551,7 +1726,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.24.7" }, @@ -1566,7 +1740,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", - "dev": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.24.7", "@babel/helper-plugin-utils": "^7.24.7" @@ -1601,7 +1774,6 @@ "version": "7.11.5", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz", "integrity": "sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA==", - "dev": true, "dependencies": { "@babel/compat-data": "^7.11.0", "@babel/helper-compilation-targets": "^7.10.4", @@ -1676,11 +1848,28 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-flow": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.25.9.tgz", + "integrity": "sha512-EASHsAhE+SSlEzJ4bzfusnXSHiU+JfAYzj+jbw2vgQKgq5HrUr8qs+vgtiEL5dOH6sEweI+PNt2D7AqrDSHyqQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-transform-flow-strip-types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/preset-modules": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6.tgz", "integrity": "sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", @@ -1723,11 +1912,30 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/register": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.25.9.tgz", + "integrity": "sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA==", + "license": "MIT", + "peer": true, + "dependencies": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/regjsgen": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "dev": true + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { "version": "7.11.2", @@ -1738,28 +1946,50 @@ } }, "node_modules/@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", - "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", - "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.6", - "@babel/parser": "^7.25.6", - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.6", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse--for-generate-function-map": { + "name": "@babel/traverse", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1768,13 +1998,13 @@ } }, "node_modules/@babel/types": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", - "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2057,11 +2287,20 @@ "tslib": "^2.4.0" } }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -2077,7 +2316,6 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, "engines": { "node": ">=8" } @@ -2312,6 +2550,123 @@ "node": ">=8" } }, + "node_modules/@jest/create-cache-key-function": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/create-cache-key-function/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/create-cache-key-function/node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/create-cache-key-function/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/create-cache-key-function/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/create-cache-key-function/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/create-cache-key-function/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "peer": true + }, + "node_modules/@jest/create-cache-key-function/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==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/create-cache-key-function/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==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@jest/environment": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", @@ -2508,6 +2863,19 @@ "node": ">=8" } }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jest/source-map": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", @@ -2791,6 +3159,17 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", @@ -2875,6 +3254,591 @@ "react-native": ">=0.59" } }, + "node_modules/@react-native/assets-registry": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.78.1.tgz", + "integrity": "sha512-SegfYQFuut05EQIQIVB/6QMGaxJ29jEtPmzFWJdIp/yc2mmhIq7MfWRjwOe6qbONzIdp6Ca8p835hiGiAGyeKQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.78.1.tgz", + "integrity": "sha512-rD0tnct/yPEtoOc8eeFHIf8ZJJJEzLkmqLs8HZWSkt3w9VYWngqLXZxiDGqv0ngXjunAlC/Hpq+ULMVOvOnByw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/traverse": "^7.25.3", + "@react-native/codegen": "0.78.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/babel-preset": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.78.1.tgz", + "integrity": "sha512-yTVcHmEdNQH4Ju7lhvbiQaGxBpMcalgkBy/IvHowXKk/ex3nY1PolF16/mBG1BrefcUA/rtJpqTtk2Ii+7T/Lw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/template": "^7.25.0", + "@react-native/babel-plugin-codegen": "0.78.1", + "babel-plugin-syntax-hermes-parser": "0.25.1", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/babel-preset/node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@react-native/babel-preset/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==", + "license": "MIT", + "peer": true + }, + "node_modules/@react-native/babel-preset/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@react-native/codegen": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.78.1.tgz", + "integrity": "sha512-kGG5qAM9JdFtxzUwe7c6CyJbsU2PnaTrtCHA2dF8VEiNX1K3yd9yKPzfkxA7HPvmHoAn3ga1941O79BStWcM3A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/parser": "^7.25.3", + "glob": "^7.1.1", + "hermes-parser": "0.25.1", + "invariant": "^2.2.4", + "jscodeshift": "^17.0.0", + "nullthrows": "^1.1.1", + "yargs": "^17.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + } + }, + "node_modules/@react-native/codegen/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@react-native/codegen/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@react-native/codegen/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@react-native/codegen/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "peer": true + }, + "node_modules/@react-native/codegen/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "peer": true + }, + "node_modules/@react-native/codegen/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/codegen/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/codegen/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@react-native/codegen/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native/codegen/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "peer": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@react-native/codegen/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@react-native/community-cli-plugin": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.78.1.tgz", + "integrity": "sha512-S6vF4oWpFqThpt/dBLrqLQw5ED2M1kg5mVtiL6ZqpoYIg+/e0vg7LZ8EXNbcdMDH4obRnm2xbOd+qlC7mOzNBg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@react-native/dev-middleware": "0.78.1", + "@react-native/metro-babel-transformer": "0.78.1", + "chalk": "^4.0.0", + "debug": "^2.2.0", + "invariant": "^2.2.4", + "metro": "^0.81.0", + "metro-config": "^0.81.0", + "metro-core": "^0.81.0", + "readline": "^1.3.0", + "semver": "^7.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@react-native-community/cli": "*" + }, + "peerDependenciesMeta": { + "@react-native-community/cli": { + "optional": true + } + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "peer": true + }, + "node_modules/@react-native/community-cli-plugin/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/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==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, + "node_modules/@react-native/community-cli-plugin/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native/community-cli-plugin/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==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/debugger-frontend": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.78.1.tgz", + "integrity": "sha512-xev/B++QLxSDpEBWsc74GyCuq9XOHYTBwcGSpsuhOJDUha6WZIbEEvZe3LpVW+OiFso4oGIdnVSQntwippZdWw==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/dev-middleware": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.78.1.tgz", + "integrity": "sha512-l8p7/dXa1vWPOdj0iuACkex8lgbLpYyPZ3QXGkocMcpl0bQ24K7hf3Bj02tfptP5PAm16b2RuEi04sjIGHUzzg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.78.1", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.2.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "selfsigned": "^2.4.1", + "serve-static": "^1.16.2", + "ws": "^6.2.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, + "node_modules/@react-native/dev-middleware/node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "license": "MIT", + "peer": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/@react-native/gradle-plugin": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.78.1.tgz", + "integrity": "sha512-v8GJU+8DzQDWO3iuTFI1nbuQ/kzuqbXv07VVtSIMLbdofHzuuQT14DGBacBkrIDKBDTVaBGAc/baDNsyxCghng==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/js-polyfills": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.78.1.tgz", + "integrity": "sha512-Ogcv4QOA1o3IyErrf/i4cDnP+nfNcIfGTgw6iNQyAPry1xjPOz4ziajskLpWG/3ADeneIZuyZppKB4A28rZSvg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.78.1.tgz", + "integrity": "sha512-jQWf69D+QTMvSZSWLR+cr3VUF16rGB6sbD+bItD8Czdfn3hajzfMoHJTkVFP7991cjK5sIVekNiQIObou8JSQw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@react-native/babel-preset": "0.78.1", + "hermes-parser": "0.25.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@react-native/metro-babel-transformer/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==", + "license": "MIT", + "peer": true + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@react-native/normalize-colors": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.78.1.tgz", + "integrity": "sha512-h4wARnY4iBFgigN1NjnaKFtcegWwQyE9+CEBVG4nHmwMtr8lZBmc7ZKIM6hUc6lxqY/ugHg48aSQSynss7mJUg==", + "license": "MIT", + "peer": true + }, "node_modules/@restart/context": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz", @@ -2894,6 +3858,13 @@ "react": ">=16.8.0" } }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "license": "MIT", + "peer": true + }, "node_modules/@sinonjs/commons": { "version": "1.8.6", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", @@ -2925,7 +3896,6 @@ "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -2938,7 +3908,6 @@ "version": "7.6.8", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", - "dev": true, "dependencies": { "@babel/types": "^7.0.0" } @@ -2947,7 +3916,6 @@ "version": "7.4.4", "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -2957,7 +3925,6 @@ "version": "7.20.6", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", - "dev": true, "dependencies": { "@babel/types": "^7.20.7" } @@ -2998,11 +3965,19 @@ "@types/react": "*" } }, + "node_modules/@types/enzyme-adapter-react-16": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.9.tgz", + "integrity": "sha512-z24MMxGtUL8HhXdye3tWzjp+19QTsABqLaX2oOZpxMPHRJgLfahQmOeTTrEBQd9ogW20+UmPBXD9j+XOasFHvw==", + "dev": true, + "dependencies": { + "@types/enzyme": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -3043,14 +4018,12 @@ "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, "dependencies": { "@types/istanbul-lib-coverage": "*" } @@ -3059,7 +4032,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, "dependencies": { "@types/istanbul-lib-report": "*" } @@ -3102,8 +4074,17 @@ "node_modules/@types/node": { "version": "14.11.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.1.tgz", - "integrity": "sha512-oTQgnd0hblfLsJ6BvJzzSL+Inogp3lq9fGgqRkMB/ziKMgEUaFl801OncOzUmalfzt14N0oPHMK47ipl+wbTIw==", - "dev": true + "integrity": "sha512-oTQgnd0hblfLsJ6BvJzzSL+Inogp3lq9fGgqRkMB/ziKMgEUaFl801OncOzUmalfzt14N0oPHMK47ipl+wbTIw==" + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", @@ -3216,8 +4197,7 @@ "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, "node_modules/@types/unist": { "version": "2.0.10", @@ -3241,8 +4221,7 @@ "node_modules/@types/yargs-parser": { "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "4.1.1", @@ -3619,6 +4598,33 @@ "deprecated": "Use your platform's native atob() and btoa() methods instead", "dev": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "peer": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -3734,6 +4740,13 @@ "ajv": "^6.9.1" } }, + "node_modules/anser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", + "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "license": "MIT", + "peer": true + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -3762,7 +4775,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -3771,6 +4783,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -3782,7 +4795,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -3801,7 +4813,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -3990,6 +5001,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT", + "peer": true + }, "node_modules/asn1.js": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", @@ -4041,6 +5059,19 @@ "node": ">=0.10.0" } }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", @@ -4063,6 +5094,13 @@ ], "optional": true }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "license": "MIT", + "peer": true + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -4267,7 +5305,6 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -4304,11 +5341,83 @@ "resolve": "^1.12.0" } }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", + "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.4", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", + "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.4" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.25.1.tgz", + "integrity": "sha512-IVNpGzboFLfXZUAwkLFcI/bnqVbwky0jP3eBno4HKtqvQJAHBLdgxiG6lQ4to0+Q/YCN3PO0od5NZwIKyY4REQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "hermes-parser": "0.25.1" + } + }, "node_modules/babel-plugin-syntax-jsx": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" }, + "node_modules/babel-plugin-transform-flow-enums": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", + "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/plugin-syntax-flow": "^7.12.1" + } + }, "node_modules/babel-plugin-typescript-to-proptypes": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/babel-plugin-typescript-to-proptypes/-/babel-plugin-typescript-to-proptypes-1.4.1.tgz", @@ -4332,7 +5441,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -4400,8 +5508,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base": { "version": "0.11.2", @@ -4505,7 +5612,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4515,7 +5621,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -4613,10 +5718,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", - "dev": true, + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "funding": [ { "type": "opencollective", @@ -4631,11 +5735,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -4648,7 +5753,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, "dependencies": { "node-int64": "^0.4.0" } @@ -4667,8 +5771,7 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "node_modules/buffer-xor": { "version": "1.0.3", @@ -4744,6 +5847,42 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "callsites": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-callsite/node_modules/callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "license": "MIT", + "peer": true, + "dependencies": { + "caller-callsite": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4756,16 +5895,14 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, "engines": { "node": ">=6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001660", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz", - "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==", - "dev": true, + "version": "1.0.30001707", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz", + "integrity": "sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==", "funding": [ { "type": "opencollective", @@ -4779,7 +5916,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/capture-exit": { "version": "2.0.0", @@ -4797,6 +5935,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -4892,6 +6031,38 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true }, + "node_modules/chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/chrome-launcher/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", @@ -4901,11 +6072,68 @@ "node": ">=6.0" } }, + "node_modules/chromium-edge-launcher": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz", + "integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "node_modules/chromium-edge-launcher/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chromium-edge-launcher/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "peer": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/chromium-edge-launcher/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" }, "node_modules/cipher-base": { "version": "1.0.4", @@ -5016,6 +6244,21 @@ "node": ">=0.8" } }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -5049,6 +6292,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -5056,7 +6300,8 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "node_modules/combined-stream": { "version": "1.0.8", @@ -5090,8 +6335,7 @@ "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" }, "node_modules/component-emitter": { "version": "1.2.1", @@ -5101,8 +6345,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/concat-stream": { "version": "1.6.2", @@ -5119,6 +6362,39 @@ "typedarray": "^0.0.6" } }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, "node_modules/console-browserify": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", @@ -5181,12 +6457,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.38.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", - "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", - "dev": true, + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz", + "integrity": "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==", + "license": "MIT", "dependencies": { - "browserslist": "^4.23.3" + "browserslist": "^4.24.4" }, "funding": { "type": "opencollective", @@ -5634,6 +6910,16 @@ "node": ">=0.4.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -5652,6 +6938,17 @@ "minimalistic-assert": "^1.0.0" } }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", @@ -5852,17 +7149,25 @@ "stream-shift": "^1.0.0" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT", + "peer": true + }, "node_modules/electron-to-chromium": { - "version": "1.5.23", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.23.tgz", - "integrity": "sha512-mBhODedOXg4v5QWwl21DjM5amzjmI1zw9EPrPK/5Wx7C8jt33bpZNrC7OhHUG3pxRtbLpr3W2dXT+Ph1SsfRZA==", - "dev": true + "version": "1.5.123", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.123.tgz", + "integrity": "sha512-refir3NlutEZqlKaBLK0tzlVLe5P2wDKS7UQt/3SpibizgsRAPOsqQC3ffw1nlv3ze5gjRQZYHoPymgVZkplFA==", + "license": "ISC" }, "node_modules/elliptic": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.0.tgz", - "integrity": "sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", "dev": true, + "license": "MIT", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -5906,6 +7211,16 @@ "node": ">= 4" } }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -6102,6 +7417,16 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "stackframe": "^1.3.4" + } + }, "node_modules/es-abstract": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", @@ -6242,14 +7567,21 @@ } }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true, + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT", + "peer": true + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -6783,7 +8115,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -6847,11 +8178,30 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -7050,6 +8400,13 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/exponential-backoff": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", + "integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -7145,8 +8502,7 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -7167,7 +8523,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, "dependencies": { "bser": "2.1.1" } @@ -7236,7 +8591,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -7244,11 +8598,46 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, "node_modules/find-cache-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, "dependencies": { "commondir": "^1.0.1", "make-dir": "^2.0.0", @@ -7267,7 +8656,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -7447,6 +8835,23 @@ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "license": "MIT", + "peer": true + }, + "node_modules/flow-parser": { + "version": "0.265.3", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.265.3.tgz", + "integrity": "sha512-YH50TTYgnzDnuaZlAxLYQ0UZtXSbbizMO3OCpoY8obvLReJmvQ5UUW22efsC3SZJmze/tATfQ0PtkKul2XwWBw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", @@ -7501,6 +8906,16 @@ "node": ">=0.10.0" } }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -7533,14 +8948,12 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -7595,7 +9008,6 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -7604,7 +9016,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -7632,7 +9043,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, "engines": { "node": ">=8.0.0" } @@ -7708,7 +9118,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7888,6 +9297,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, "engines": { "node": ">=4" } @@ -8055,6 +9465,23 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "license": "MIT", + "peer": true + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "license": "MIT", + "peer": true, + "dependencies": { + "hermes-estree": "0.25.1" + } + }, "node_modules/history": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", @@ -8151,6 +9578,33 @@ "entities": "^4.4.0" } }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/http-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", @@ -8263,6 +9717,22 @@ "node": ">= 4" } }, + "node_modules/image-size": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "license": "MIT", + "peer": true, + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, "node_modules/immutable": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", @@ -8342,7 +9812,6 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -8351,8 +9820,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "1.3.8", @@ -8596,12 +10064,20 @@ "node": ">= 0.4" } }, + "node_modules/is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "optional": true, "bin": { "is-docker": "cli.js" }, @@ -8679,7 +10155,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -8714,7 +10189,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "dependencies": { "isobject": "^3.0.1" }, @@ -8853,8 +10327,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "optional": true, "dependencies": { "is-docker": "^2.0.0" }, @@ -8878,7 +10350,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -8887,7 +10358,6 @@ "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, "engines": { "node": ">=8" } @@ -8896,7 +10366,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -8912,7 +10381,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", - "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", @@ -8941,14 +10409,12 @@ "node_modules/istanbul-lib-instrument/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 + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/istanbul-lib-instrument/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -11042,7 +12508,6 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -11056,6 +12521,149 @@ "resolved": "https://registry.npmjs.org/jsan/-/jsan-3.1.14.tgz", "integrity": "sha512-wStfgOJqMv4QKktuH273f5fyi3D3vy2pHOiSDGPvpcS/q+wb/M7AK3vkCcaHbkZxDOlDU/lDJgccygKSG2OhtA==" }, + "node_modules/jsc-safe-url": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", + "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", + "license": "0BSD", + "peer": true + }, + "node_modules/jscodeshift": { + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-17.3.0.tgz", + "integrity": "sha512-LjFrGOIORqXBU+jwfC9nbkjmQfFldtMIoS6d9z2LG/lkmyNXsJAySPT+2SWXJEoE68/bCWcxKpXH37npftgmow==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/preset-flow": "^7.24.7", + "@babel/preset-typescript": "^7.24.7", + "@babel/register": "^7.24.6", + "flow-parser": "0.*", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.7", + "neo-async": "^2.5.0", + "picocolors": "^1.0.1", + "recast": "^0.23.11", + "tmp": "^0.2.3", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "jscodeshift": "bin/jscodeshift.js" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + }, + "peerDependenciesMeta": { + "@babel/preset-env": { + "optional": true + } + } + }, + "node_modules/jscodeshift/node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/jscodeshift/node_modules/@babel/preset-typescript": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.0.tgz", + "integrity": "sha512-vxaPFfJtHhgeOVXRKuHpHPAOgymmy8V8I65T1q53R7GCZlefKeCaTyDs3zOPHTTbmquvNlQYC5klEvWsBAtrBQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.26.3", + "@babel/plugin-transform-typescript": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/jscodeshift/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==", + "license": "MIT", + "peer": true + }, + "node_modules/jscodeshift/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/jscodeshift/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jscodeshift/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "license": "ISC", + "peer": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/jsdom": { "version": "16.7.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", @@ -11121,21 +12729,21 @@ "dev": true }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -11163,7 +12771,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "bin": { "json5": "lib/cli.js" }, @@ -11188,7 +12795,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -11215,7 +12821,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, "engines": { "node": ">=6" } @@ -11224,7 +12829,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", - "dev": true, "dependencies": { "leven": "^3.1.0" }, @@ -11245,6 +12849,34 @@ "node": ">= 0.8.0" } }, + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/lighthouse-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -11339,7 +12971,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -11357,6 +12988,13 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT", + "peer": true + }, "node_modules/lodash.escape": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", @@ -11375,6 +13013,13 @@ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", "dev": true }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "license": "MIT", + "peer": true + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -11390,7 +13035,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "dependencies": { "yallist": "^3.0.2" } @@ -11399,7 +13043,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, "dependencies": { "pify": "^4.0.1", "semver": "^5.6.0" @@ -11412,7 +13055,6 @@ "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, "dependencies": { "tmpl": "1.0.5" } @@ -11438,6 +13080,13 @@ "node": ">=0.10.0" } }, + "node_modules/marky": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", + "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/mattermost-redux": { "version": "5.33.1", "resolved": "https://registry.npmjs.org/mattermost-redux/-/mattermost-redux-5.33.1.tgz", @@ -11577,8 +13226,7 @@ "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "node_modules/merge2": { "version": "1.4.1", @@ -11589,262 +13237,1542 @@ "node": ">= 8" } }, - "node_modules/micromark": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", - "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/metro": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.81.4.tgz", + "integrity": "sha512-78f0aBNPuwXW7GFnSc+Y0vZhbuQorXxdgqQfvSRqcSizqwg9cwF27I05h47tL8AzQcizS1JZncvq4xf5u/Qykw==", + "license": "MIT", + "peer": true, "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "micromark-core-commonmark": "^1.0.1", - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-combine-extensions": "^1.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-sanitize-uri": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" + "@babel/code-frame": "^7.24.7", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.3", + "@babel/types": "^7.25.2", + "accepts": "^1.3.7", + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "error-stack-parser": "^2.0.6", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.25.1", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.81.4", + "metro-cache": "0.81.4", + "metro-cache-key": "0.81.4", + "metro-config": "0.81.4", + "metro-core": "0.81.4", + "metro-file-map": "0.81.4", + "metro-resolver": "0.81.4", + "metro-runtime": "0.81.4", + "metro-source-map": "0.81.4", + "metro-symbolicate": "0.81.4", + "metro-transform-plugins": "0.81.4", + "metro-transform-worker": "0.81.4", + "mime-types": "^2.1.27", + "nullthrows": "^1.1.1", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "throat": "^5.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "bin": { + "metro": "src/cli.js" + }, + "engines": { + "node": ">=18.18" } }, - "node_modules/micromark-core-commonmark": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", - "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/metro-babel-transformer": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.81.4.tgz", + "integrity": "sha512-WW0yswWrW+eTVK9sYD+b1HwWOiUlZlUoomiw9TIOk0C+dh2V90Wttn/8g62kYi0Y4i+cJfISerB2LbV4nuRGTA==", + "license": "MIT", + "peer": true, "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-factory-destination": "^1.0.0", - "micromark-factory-label": "^1.0.0", - "micromark-factory-space": "^1.0.0", - "micromark-factory-title": "^1.0.0", - "micromark-factory-whitespace": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-classify-character": "^1.0.0", - "micromark-util-html-tag-name": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" + "@babel/core": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "hermes-parser": "0.25.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18.18" } }, - "node_modules/micromark-factory-destination": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", - "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/metro-babel-transformer/node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "license": "MIT", + "peer": true, "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/micromark-factory-label": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", - "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" + "node_modules/metro-babel-transformer/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==", + "license": "MIT", + "peer": true + }, + "node_modules/metro-babel-transformer/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/micromark-factory-space": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", - "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/metro-cache": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.81.4.tgz", + "integrity": "sha512-sxCPH3gowDxazSaZZrwdNPEpnxR8UeXDnvPjBF9+5btDBNN2DpWvDAXPvrohkYkFImhc0LajS2V7eOXvu9PnvQ==", + "license": "MIT", + "peer": true, "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-types": "^1.0.0" + "exponential-backoff": "^3.1.1", + "flow-enums-runtime": "^0.0.6", + "metro-core": "0.81.4" + }, + "engines": { + "node": ">=18.18" } }, - "node_modules/micromark-factory-title": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", - "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/metro-cache-key": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.81.4.tgz", + "integrity": "sha512-3SaWQybvf1ivasjBegIxzVKLJzOpcz+KsnGwXFOYADQq0VN4cnM7tT+u2jkOhk6yJiiO1WIjl68hqyMOQJRRLg==", + "license": "MIT", + "peer": true, "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=18.18" } }, - "node_modules/micromark-factory-whitespace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", - "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/metro-config": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.81.4.tgz", + "integrity": "sha512-QnhMy3bRiuimCTy7oi5Ug60javrSa3lPh0gpMAspQZHY9h6y86jwHtZPLtlj8hdWQESIlrbeL8inMSF6qI/i9Q==", + "license": "MIT", + "peer": true, "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" + "connect": "^3.6.5", + "cosmiconfig": "^5.0.5", + "flow-enums-runtime": "^0.0.6", + "jest-validate": "^29.7.0", + "metro": "0.81.4", + "metro-cache": "0.81.4", + "metro-core": "0.81.4", + "metro-runtime": "0.81.4" + }, + "engines": { + "node": ">=18.18" } }, - "node_modules/micromark-util-character": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", - "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/metro-config/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", + "peer": true, "dependencies": { - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/micromark-util-chunked": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", - "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/metro-config/node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "license": "MIT", + "peer": true, "dependencies": { - "micromark-util-symbol": "^1.0.0" + "@types/yargs-parser": "*" } }, - "node_modules/micromark-util-classify-character": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", - "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/metro-config/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "peer": true, "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/micromark-util-combine-extensions": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", - "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-types": "^1.0.0" + "node_modules/metro-config/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", - "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", + "node_modules/metro-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/metro-config/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/metro-config/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "peer": true + }, + "node_modules/metro-config/node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "license": "MIT", + "peer": true, + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/metro-config/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==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/metro-config/node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "license": "MIT", + "peer": true, + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/metro-config/node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/metro-config/node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/metro-config/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "license": "MIT", + "peer": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/metro-config/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/metro-config/node_modules/pretty-format/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==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/metro-config/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT", + "peer": true + }, + "node_modules/metro-config/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/metro-config/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==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/metro-core": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.81.4.tgz", + "integrity": "sha512-GdL4IgmgJhrMA/rTy2lRqXKeXfC77Rg+uvhUEkbhyfj/oz7PrdSgvIFzziapjdHwk1XYq0KyFh/CcVm8ZawG6A==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.81.4" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-file-map": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.81.4.tgz", + "integrity": "sha512-qUIBzkiqOi3qEuscu4cJ83OYQ4hVzjON19FAySWqYys9GKCmxlKa7LkmwqdpBso6lQl+JXZ7nCacX90w5wQvPA==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^2.2.0", + "fb-watchman": "^2.0.0", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-file-map/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/metro-file-map/node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/metro-file-map/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/metro-file-map/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/metro-file-map/node_modules/chalk/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==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/metro-file-map/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/metro-file-map/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/metro-file-map/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "peer": true + }, + "node_modules/metro-file-map/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/metro-file-map/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==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/metro-file-map/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/metro-file-map/node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/metro-file-map/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, + "node_modules/metro-file-map/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/metro-minify-terser": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.81.4.tgz", + "integrity": "sha512-oVvq/AGvqmbhuijJDZZ9npeWzaVyeBwQKtdlnjcQ9fH7nR15RiBr5y2zTdgTEdynqOIb1Kc16l8CQIUSzOWVFA==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "terser": "^5.15.0" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-minify-terser/node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/metro-minify-terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT", + "peer": true + }, + "node_modules/metro-minify-terser/node_modules/terser": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", + "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/metro-resolver": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.81.4.tgz", + "integrity": "sha512-Ng7G2mXjSExMeRzj6GC19G6IJ0mfIbOLgjArsMWJgtt9ViZiluCwgWsMW9juBC5NSwjJxUMK2x6pC5NIMFLiHA==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-runtime": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.81.4.tgz", + "integrity": "sha512-fBoRgqkF69CwyPtBNxlDi5ha26Zc8f85n2THXYoh13Jn/Bkg8KIDCdKPp/A1BbSeNnkH/++H2EIIfnmaff4uRg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.25.0", + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-runtime/node_modules/@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", + "peer": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/metro-runtime/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT", + "peer": true + }, + "node_modules/metro-source-map": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.81.4.tgz", + "integrity": "sha512-IOwVQ7mLqoqvsL70RZtl1EyE3f9jp43kVsAsb/B/zoWmu0/k4mwEhGLTxmjdXRkLJqPqPrh7WmFChAEf9trW4Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/traverse": "^7.25.3", + "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", + "@babel/types": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-symbolicate": "0.81.4", + "nullthrows": "^1.1.1", + "ob1": "0.81.4", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-symbolicate": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.81.4.tgz", + "integrity": "sha512-rWxTmYVN6/BOSaMDUHT8HgCuRf6acd0AjHkenYlHpmgxg7dqdnAG1hLq999q2XpW5rX+cMamZD5W5Ez2LqGaag==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-source-map": "0.81.4", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-transform-plugins": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.81.4.tgz", + "integrity": "sha512-nlP069nDXm4v28vbll4QLApAlvVtlB66rP6h+ml8Q/CCQCPBXu2JLaoxUmkIOJQjLhMRUcgTyQHq+TXWJhydOQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.3", + "flow-enums-runtime": "^0.0.6", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-transform-plugins/node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/metro-transform-plugins/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==", + "license": "MIT", + "peer": true + }, + "node_modules/metro-transform-plugins/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/metro-transform-worker": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.81.4.tgz", + "integrity": "sha512-lKAeRZ8EUMtx2cA/Y4KvICr9bIr5SE03iK3lm+l9wyn2lkjLUuPjYVep159inLeDqC6AtSubsA8MZLziP7c03g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/types": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "metro": "0.81.4", + "metro-babel-transformer": "0.81.4", + "metro-cache": "0.81.4", + "metro-cache-key": "0.81.4", + "metro-minify-terser": "0.81.4", + "metro-source-map": "0.81.4", + "metro-transform-plugins": "0.81.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-transform-worker/node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/metro-transform-worker/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==", + "license": "MIT", + "peer": true + }, + "node_modules/metro-transform-worker/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/metro/node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/metro/node_modules/@babel/core/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/metro/node_modules/@babel/core/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", + "peer": true + }, + "node_modules/metro/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/metro/node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/metro/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/metro/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/metro/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/metro/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/metro/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "peer": true + }, + "node_modules/metro/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==", + "license": "MIT", + "peer": true + }, + "node_modules/metro/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/metro/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "peer": true + }, + "node_modules/metro/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==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/metro/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/metro/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/metro/node_modules/jest-util/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/metro/node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/metro/node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/metro/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, + "node_modules/metro/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/metro/node_modules/serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/metro/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==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/metro/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/metro/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/metro/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "peer": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/metro/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], @@ -12014,7 +14942,6 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", - "dev": true, "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -12042,6 +14969,19 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "peer": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -12086,7 +15026,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -12270,11 +15209,20 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "node_modules/nice-try": { "version": "1.0.5", @@ -12320,11 +15268,20 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "peer": true, + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" }, "node_modules/node-libs-browser": { "version": "2.2.1", @@ -12417,10 +15374,10 @@ } }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "dev": true + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" }, "node_modules/normalize-package-data": { "version": "2.5.0", @@ -12438,7 +15395,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -12467,12 +15423,32 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "license": "MIT", + "peer": true + }, "node_modules/nwsapi": { "version": "2.2.10", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz", "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==", "dev": true }, + "node_modules/ob1": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.81.4.tgz", + "integrity": "sha512-EZLYM8hfPraC2SYOR5EWLFAPV5e6g+p83m2Jth9bzCpFxP1NDQJYXdmXRB2bfbaWQSmm6NkIQlbzk7uU5lLfgg==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=18.18" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -12666,11 +15642,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "peer": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -12690,6 +15678,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -12738,7 +15743,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "dependencies": { "p-try": "^2.0.0" }, @@ -12753,7 +15757,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -12765,7 +15768,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, "engines": { "node": ">=6" } @@ -12881,6 +15883,16 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", @@ -12907,7 +15919,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "engines": { "node": ">=8" } @@ -12916,7 +15927,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -12965,15 +15975,15 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -12985,7 +15995,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, "engines": { "node": ">=6" } @@ -12994,7 +16003,6 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, "engines": { "node": ">= 6" } @@ -13003,7 +16011,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, "dependencies": { "find-up": "^3.0.0" }, @@ -13015,7 +16022,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, "dependencies": { "locate-path": "^3.0.0" }, @@ -13027,7 +16033,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -13040,7 +16045,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, "dependencies": { "p-limit": "^2.0.0" }, @@ -13052,7 +16056,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, "engines": { "node": ">=4" } @@ -13321,6 +16324,16 @@ "node": ">=0.4.0" } }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "license": "MIT", + "peer": true, + "dependencies": { + "asap": "~2.0.6" + } + }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -13419,292 +16432,1027 @@ "safe-buffer": "^5.1.2" } }, - "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/pump": { + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/pumpify/node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true + }, + "node_modules/qs": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", + "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystring": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", + "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "~2.0.3" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==", + "dev": true + }, + "node_modules/randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dev": true, + "dependencies": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/react": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", + "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-bootstrap": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.3.0.tgz", + "integrity": "sha512-GYj0c6FO9mx7DaO8Xyz2zs0IcQ6CGCtM3O6/feIoCaG4N8B0+l4eqL7stlMcLpqO4d8NG2PoMO/AbUOD+MO7mg==", + "dependencies": { + "@babel/runtime": "^7.4.2", + "@restart/context": "^2.1.4", + "@restart/hooks": "^0.3.21", + "@types/classnames": "^2.2.10", + "@types/invariant": "^2.2.33", + "@types/prop-types": "^15.7.3", + "@types/react": "^16.9.35", + "@types/react-transition-group": "^4.4.0", + "@types/warning": "^3.0.0", + "classnames": "^2.2.6", + "dom-helpers": "^5.1.2", + "invariant": "^2.2.4", + "prop-types": "^15.7.2", + "prop-types-extra": "^1.1.0", + "react-overlays": "^4.1.0", + "react-transition-group": "^4.4.1", + "uncontrollable": "^7.0.0", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/react-custom-scrollbars": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/react-custom-scrollbars/-/react-custom-scrollbars-4.2.1.tgz", + "integrity": "sha512-VtJTUvZ7kPh/auZWIbBRceGPkE30XBYe+HktFxuMWBR2eVQQ+Ur6yFJMoaYcNpyGq22uYJ9Wx4UAEcC0K+LNPQ==", + "dependencies": { + "dom-css": "^2.0.0", + "prop-types": "^15.5.10", + "raf": "^3.1.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16.0.0", + "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/react-devtools-core": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.1.tgz", + "integrity": "sha512-TFo1MEnkqE6hzAbaztnyR5uLTMoz6wnEWwWBsCUzNt+sVXJycuRJdDqvL078M4/h65BI/YO5XWTaxZDWVsW0fw==", + "license": "MIT", + "peer": true, + "dependencies": { + "shell-quote": "^1.6.1", + "ws": "^7" + } + }, + "node_modules/react-dom": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", + "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + }, + "peerDependencies": { + "react": "^16.13.1" + } + }, + "node_modules/react-input-autosize": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz", + "integrity": "sha512-jQJgYCA3S0j+cuOwzuCd1OjmBmnZLdqQdiLKRYrsMMzbjUrVDS5RvJUDwJqA7sKuksDuzFtm6hZGKFu7Mjk5aw==", + "dependencies": { + "prop-types": "^15.5.8" + }, + "peerDependencies": { + "react": "^0.14.9 || ^15.3.0 || ^16.0.0-rc || ^16.0" + } + }, + "node_modules/react-intl": { + "version": "6.6.8", + "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.8.tgz", + "integrity": "sha512-M0pkhzcgV31h++2901BiRXWl69hp2zPyLxRrSwRjd1ErXbNoubz/f4M6DrRTd4OiSUrT4ajRQzrmtS5plG4FtA==", + "dev": true, + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/icu-messageformat-parser": "2.7.8", + "@formatjs/intl": "2.10.4", + "@formatjs/intl-displaynames": "6.6.8", + "@formatjs/intl-listformat": "7.5.7", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/react": "16 || 17 || 18", + "hoist-non-react-statics": "^3.3.2", + "intl-messageformat": "10.5.14", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "react": "^16.6.0 || 17 || 18", + "typescript": "^4.7 || 5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-markdown": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz", + "integrity": "sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/prop-types": "^15.0.0", + "@types/unist": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "prop-types": "^15.0.0", + "property-information": "^6.0.0", + "react-is": "^18.0.0", + "remark-parse": "^10.0.0", + "remark-rehype": "^10.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, + "node_modules/react-markdown/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/react-native": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.78.1.tgz", + "integrity": "sha512-3CK/xxX02GeeVFyrXbsHvREZFVaXwHW43Km/EdYITn5G32cccWTGaqY9QdPddEBLw5O3BPip3LHbR1SywE0cpA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/create-cache-key-function": "^29.6.3", + "@react-native/assets-registry": "0.78.1", + "@react-native/codegen": "0.78.1", + "@react-native/community-cli-plugin": "0.78.1", + "@react-native/gradle-plugin": "0.78.1", + "@react-native/js-polyfills": "0.78.1", + "@react-native/normalize-colors": "0.78.1", + "@react-native/virtualized-lists": "0.78.1", + "abort-controller": "^3.0.0", + "anser": "^1.4.9", + "ansi-regex": "^5.0.0", + "babel-jest": "^29.7.0", + "babel-plugin-syntax-hermes-parser": "0.25.1", + "base64-js": "^1.5.1", + "chalk": "^4.0.0", + "commander": "^12.0.0", + "event-target-shim": "^5.0.1", + "flow-enums-runtime": "^0.0.6", + "glob": "^7.1.1", + "invariant": "^2.2.4", + "jest-environment-node": "^29.6.3", + "memoize-one": "^5.0.0", + "metro-runtime": "^0.81.0", + "metro-source-map": "^0.81.0", + "nullthrows": "^1.1.1", + "pretty-format": "^29.7.0", + "promise": "^8.3.0", + "react-devtools-core": "^6.0.1", + "react-refresh": "^0.14.0", + "regenerator-runtime": "^0.13.2", + "scheduler": "0.25.0", + "semver": "^7.1.3", + "stacktrace-parser": "^0.1.10", + "whatwg-fetch": "^3.0.0", + "ws": "^6.2.3", + "yargs": "^17.6.2" + }, + "bin": { + "react-native": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/react": "^19.0.0", + "react": "^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-native/node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/react-native/node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/react-native/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/react-native/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/react-native/node_modules/@react-native/virtualized-lists": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.78.1.tgz", + "integrity": "sha512-v0jqDNMFXpnRnSlkDVvwNxXgPhifzzTFlxTSnHj9erKJsKpE26gSU5qB4hmJkEsscLG/ygdJ1c88aqinSh/wRA==", + "license": "MIT", + "peer": true, + "dependencies": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/react": "^19.0.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-native/node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/react-native/node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/react-native/node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/react-native/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/react-native/node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/react-native/node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/react-native/node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "license": "MIT", + "peer": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/react-native/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/react-native/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/react-native/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/react-native/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/react-native/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "peer": true + }, + "node_modules/react-native/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/react-native/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==", + "license": "MIT", + "peer": true + }, + "node_modules/react-native/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "peer": true + }, + "node_modules/react-native/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==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/react-native/node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" } }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, + "node_modules/react-native/node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "license": "MIT", + "peer": true, "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/pumpify/node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, + "node_modules/react-native/node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "license": "MIT", + "peer": true, "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dev": true - }, - "node_modules/qs": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", - "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", - "dev": true, + "node_modules/react-native/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "license": "MIT", + "peer": true, "dependencies": { - "side-channel": "^1.0.6" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/querystring": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", - "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "node_modules/react-native/node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, "engines": { - "node": ">=0.4.x" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", - "dev": true, + "node_modules/react-native/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "license": "MIT", + "peer": true, "engines": { - "node": ">=0.4.x" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "node_modules/react-native/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "license": "MIT", + "peer": true, "dependencies": { - "performance-now": "^2.1.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/railroad-diagrams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", - "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==", - "dev": true - }, - "node_modules/randexp": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", - "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", - "dev": true, + "node_modules/react-native/node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "license": "MIT", + "peer": true, "dependencies": { - "discontinuous-range": "1.0.0", - "ret": "~0.1.10" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">=0.12" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, + "node_modules/react-native/node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "peer": true, "dependencies": { - "safe-buffer": "^5.1.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, + "node_modules/react-native/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "license": "MIT", + "peer": true, "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/react": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", - "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" + "node_modules/react-native/node_modules/pretty-format/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==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/react-native/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT", + "peer": true + }, + "node_modules/react-native/node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT", + "peer": true + }, + "node_modules/react-native/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/react-bootstrap": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.3.0.tgz", - "integrity": "sha512-GYj0c6FO9mx7DaO8Xyz2zs0IcQ6CGCtM3O6/feIoCaG4N8B0+l4eqL7stlMcLpqO4d8NG2PoMO/AbUOD+MO7mg==", + "node_modules/react-native/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/react-native/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "peer": true, "dependencies": { - "@babel/runtime": "^7.4.2", - "@restart/context": "^2.1.4", - "@restart/hooks": "^0.3.21", - "@types/classnames": "^2.2.10", - "@types/invariant": "^2.2.33", - "@types/prop-types": "^15.7.3", - "@types/react": "^16.9.35", - "@types/react-transition-group": "^4.4.0", - "@types/warning": "^3.0.0", - "classnames": "^2.2.6", - "dom-helpers": "^5.1.2", - "invariant": "^2.2.4", - "prop-types": "^15.7.2", - "prop-types-extra": "^1.1.0", - "react-overlays": "^4.1.0", - "react-transition-group": "^4.4.1", - "uncontrollable": "^7.0.0", - "warning": "^4.0.3" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "engines": { + "node": ">=8" } }, - "node_modules/react-custom-scrollbars": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/react-custom-scrollbars/-/react-custom-scrollbars-4.2.1.tgz", - "integrity": "sha512-VtJTUvZ7kPh/auZWIbBRceGPkE30XBYe+HktFxuMWBR2eVQQ+Ur6yFJMoaYcNpyGq22uYJ9Wx4UAEcC0K+LNPQ==", + "node_modules/react-native/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==", + "license": "MIT", + "peer": true, "dependencies": { - "dom-css": "^2.0.0", - "prop-types": "^15.5.10", - "raf": "^3.1.0" + "has-flag": "^4.0.0" }, - "peerDependencies": { - "react": "^0.14.0 || ^15.0.0 || ^16.0.0", - "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/react-input-autosize": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz", - "integrity": "sha512-jQJgYCA3S0j+cuOwzuCd1OjmBmnZLdqQdiLKRYrsMMzbjUrVDS5RvJUDwJqA7sKuksDuzFtm6hZGKFu7Mjk5aw==", + "node_modules/react-native/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "peer": true, "dependencies": { - "prop-types": "^15.5.8" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, - "peerDependencies": { - "react": "^0.14.9 || ^15.3.0 || ^16.0.0-rc || ^16.0" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/react-intl": { - "version": "6.6.8", - "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.8.tgz", - "integrity": "sha512-M0pkhzcgV31h++2901BiRXWl69hp2zPyLxRrSwRjd1ErXbNoubz/f4M6DrRTd4OiSUrT4ajRQzrmtS5plG4FtA==", - "dev": true, + "node_modules/react-native/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "license": "ISC", + "peer": true, "dependencies": { - "@formatjs/ecma402-abstract": "2.0.0", - "@formatjs/icu-messageformat-parser": "2.7.8", - "@formatjs/intl": "2.10.4", - "@formatjs/intl-displaynames": "6.6.8", - "@formatjs/intl-listformat": "7.5.7", - "@types/hoist-non-react-statics": "^3.3.1", - "@types/react": "16 || 17 || 18", - "hoist-non-react-statics": "^3.3.2", - "intl-messageformat": "10.5.14", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "react": "^16.6.0 || 17 || 18", - "typescript": "^4.7 || 5" + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "node_modules/react-native/node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "license": "MIT", + "peer": true, + "dependencies": { + "async-limiter": "~1.0.0" + } }, - "node_modules/react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + "node_modules/react-native/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=10" + } }, - "node_modules/react-markdown": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz", - "integrity": "sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==", + "node_modules/react-native/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "peer": true, "dependencies": { - "@types/hast": "^2.0.0", - "@types/prop-types": "^15.0.0", - "@types/unist": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^2.0.0", - "prop-types": "^15.0.0", - "property-information": "^6.0.0", - "react-is": "^18.0.0", - "remark-parse": "^10.0.0", - "remark-rehype": "^10.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-object": "^0.4.0", - "unified": "^10.0.0", - "unist-util-visit": "^4.0.0", - "vfile": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, - "peerDependencies": { - "@types/react": ">=16", - "react": ">=16" + "engines": { + "node": ">=12" } }, - "node_modules/react-markdown/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + "node_modules/react-native/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } }, "node_modules/react-overlays": { "version": "4.1.1", @@ -13765,6 +17513,16 @@ } } }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react-select": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/react-select/-/react-select-3.1.0.tgz", @@ -13971,6 +17729,40 @@ "node": ">=8.10.0" } }, + "node_modules/readline": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", + "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", + "license": "BSD", + "peer": true + }, + "node_modules/recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/recast/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/redux": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz", @@ -14066,14 +17858,12 @@ "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "node_modules/regenerate-unicode-properties": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", - "dev": true, "dependencies": { "regenerate": "^1.4.2" }, @@ -14090,7 +17880,6 @@ "version": "0.15.2", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, "dependencies": { "@babel/runtime": "^7.8.4" } @@ -14142,7 +17931,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", - "dev": true, "dependencies": { "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", @@ -14159,7 +17947,6 @@ "version": "0.9.1", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dev": true, "dependencies": { "jsesc": "~0.5.0" }, @@ -14171,7 +17958,6 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true, "bin": { "jsesc": "bin/jsesc" } @@ -14255,7 +18041,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -14364,7 +18149,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, "engines": { "node": ">=8" } @@ -14999,7 +18783,6 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -15023,15 +18806,100 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/semver": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, "bin": { "semver": "bin/semver" } }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, + "node_modules/send/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", + "peer": true + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "peer": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/serialize-error": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-6.0.0.tgz", @@ -15066,6 +18934,32 @@ "randombytes": "^2.1.0" } }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "peer": true, + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -15146,6 +19040,13 @@ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", "dev": true }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC", + "peer": true + }, "node_modules/sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", @@ -15159,6 +19060,19 @@ "sha.js": "bin.js" } }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "license": "MIT", + "peer": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/shallow-equals": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shallow-equals/-/shallow-equals-1.0.0.tgz", @@ -15185,6 +19099,19 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", @@ -15213,8 +19140,7 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/sisteransi": { "version": "1.0.5", @@ -15488,7 +19414,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -15498,7 +19423,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -15566,8 +19490,7 @@ "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "node_modules/ssri": { "version": "6.0.2", @@ -15582,7 +19505,6 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -15594,7 +19516,36 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "license": "MIT", + "peer": true + }, + "node_modules/stacktrace-parser": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", + "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", + "license": "MIT", + "peer": true, + "dependencies": { + "type-fest": "^0.7.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stacktrace-parser/node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "license": "(MIT OR CC0-1.0)", + "peer": true, "engines": { "node": ">=8" } @@ -15637,6 +19588,16 @@ "node": ">= 0.4" } }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", @@ -15812,7 +19773,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -15905,6 +19865,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -16102,7 +20063,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -16121,8 +20081,7 @@ "node_modules/throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "dev": true + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==" }, "node_modules/through2": { "version": "2.0.5", @@ -16146,11 +20105,27 @@ "node": ">=0.6.0" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT", + "peer": true + }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14.14" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" }, "node_modules/to-arraybuffer": { "version": "1.0.1", @@ -16166,14 +20141,6 @@ "to-space-case": "^1.0.0" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, "node_modules/to-no-case": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/to-no-case/-/to-no-case-1.0.2.tgz", @@ -16228,7 +20195,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -16244,6 +20210,16 @@ "to-no-case": "^1.0.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/tough-cookie": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", @@ -16343,8 +20319,7 @@ "node_modules/tslib": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "dev": true + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -16389,7 +20364,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, "engines": { "node": ">=4" } @@ -16540,7 +20514,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true, "engines": { "node": ">=4" } @@ -16549,7 +20522,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -16562,7 +20534,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "dev": true, "engines": { "node": ">=4" } @@ -16571,7 +20542,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true, "engines": { "node": ">=4" } @@ -16717,6 +20687,16 @@ "node": ">= 4.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -16777,10 +20757,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", - "dev": true, + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "funding": [ { "type": "opencollective", @@ -16795,9 +20774,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -16881,6 +20861,16 @@ "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", "dev": true }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -16983,6 +20973,13 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vlq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", + "license": "MIT", + "peer": true + }, "node_modules/vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -17015,7 +21012,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, "dependencies": { "makeerror": "1.0.12" } @@ -17925,6 +21921,13 @@ "iconv-lite": "0.4.24" } }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "license": "MIT", + "peer": true + }, "node_modules/whatwg-mimetype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", @@ -18098,8 +22101,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write": { "version": "1.0.3", @@ -18181,8 +22183,7 @@ "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/yaml": { "version": "1.10.2", @@ -18262,7 +22263,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, "requires": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -18286,25 +22286,24 @@ } }, "@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "requires": { - "@babel/highlight": "^7.24.7", + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "@babel/compat-data": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", - "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", - "dev": true + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==" }, "@babel/core": { "version": "7.11.6", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", - "dev": true, "requires": { "@babel/code-frame": "^7.10.4", "@babel/generator": "^7.11.6", @@ -18325,44 +22324,42 @@ } }, "@babel/generator": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", - "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", "requires": { - "@babel/types": "^7.25.6", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" } }, "@babel/helper-annotate-as-pure": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", - "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "requires": { - "@babel/types": "^7.24.7" + "@babel/types": "^7.25.9" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", - "dev": true, "requires": { "@babel/traverse": "^7.24.7", "@babel/types": "^7.24.7" } }, "@babel/helper-compilation-targets": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", - "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", - "dev": true, + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", + "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", "requires": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", + "@babel/compat-data": "^7.26.8", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -18370,31 +22367,28 @@ "semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" } } }, "@babel/helper-create-class-features-plugin": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz", - "integrity": "sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/helper-replace-supers": "^7.25.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/traverse": "^7.25.4", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", + "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.27.0", "semver": "^6.3.1" }, "dependencies": { "semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" } } }, @@ -18402,7 +22396,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz", "integrity": "sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==", - "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.24.7", "regexpu-core": "^5.3.1", @@ -18412,27 +22405,28 @@ "semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" } } }, - "@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "dev": true, + "@babel/helper-define-polyfill-provider": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", + "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", + "peer": true, "requires": { - "@babel/types": "^7.24.7" + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" } }, - "@babel/helper-function-name": { + "@babel/helper-environment-visitor": { "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", - "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", - "dev": true, + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", "requires": { - "@babel/template": "^7.24.7", "@babel/types": "^7.24.7" } }, @@ -18440,169 +22434,126 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", - "dev": true, "requires": { "@babel/types": "^7.24.7" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", - "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", "requires": { - "@babel/traverse": "^7.24.8", - "@babel/types": "^7.24.8" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" } }, "@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", - "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "requires": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" } }, "@babel/helper-module-transforms": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", - "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", - "dev": true, + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "requires": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.2" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" } }, "@babel/helper-optimise-call-expression": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", - "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", "requires": { - "@babel/types": "^7.24.7" + "@babel/types": "^7.25.9" } }, "@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", - "dev": true + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==" }, "@babel/helper-remap-async-to-generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", - "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", "requires": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-wrap-function": "^7.25.0", - "@babel/traverse": "^7.25.0" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" } }, "@babel/helper-replace-supers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", - "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", - "dev": true, + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", + "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", "requires": { - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/traverse": "^7.25.0" - } - }, - "@babel/helper-simple-access": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", - "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", - "dev": true, - "requires": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.26.5" } }, "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", - "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", "requires": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "dev": true, - "requires": { - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" } }, "@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==" + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==" }, "@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==" + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==" }, "@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", - "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", - "dev": true + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==" }, "@babel/helper-wrap-function": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", - "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", "requires": { - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" } }, "@babel/helpers": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", - "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", - "dev": true, + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", "requires": { - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.6" - } - }, - "@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", - "requires": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" } }, "@babel/parser": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", - "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", "requires": { - "@babel/types": "^7.25.6" + "@babel/types": "^7.27.0" } }, "@babel/plugin-proposal-async-generator-functions": { "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", - "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-plugin-utils": "^7.20.2", @@ -18614,7 +22565,6 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", - "dev": true, "requires": { "@babel/helper-create-class-features-plugin": "^7.10.4", "@babel/helper-plugin-utils": "^7.10.4" @@ -18624,17 +22574,24 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-dynamic-import": "^7.8.3" } }, + "@babel/plugin-proposal-export-default-from": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.25.9.tgz", + "integrity": "sha512-ykqgwNfSnNOB+C8fV5X4mG3AVmvu+WVxcaU9xHHtBb7PCrPeweMmPjGsn8eMaeJg6SJuoUuZENeeSWaarWqonQ==", + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.25.9" + } + }, "@babel/plugin-proposal-export-namespace-from": { "version": "7.18.9", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.18.9", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" @@ -18644,7 +22601,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-json-strings": "^7.8.3" @@ -18654,7 +22610,6 @@ "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.20.2", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" @@ -18664,7 +22619,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" @@ -18674,7 +22628,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-numeric-separator": "^7.10.4" @@ -18684,7 +22637,6 @@ "version": "7.11.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz", "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", @@ -18695,7 +22647,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" @@ -18705,7 +22656,6 @@ "version": "7.11.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz", "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0", @@ -18716,7 +22666,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "dev": true, "requires": { "@babel/helper-create-class-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -18726,7 +22675,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "dev": true, "requires": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -18736,7 +22684,6 @@ "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -18745,7 +22692,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -18754,7 +22700,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -18763,25 +22708,40 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-export-default-from": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.25.9.tgz", + "integrity": "sha512-9MhJ/SMTsVqsd69GyQg89lYR4o9T+oDGv5F6IsigxxqFVOyR/IflDLYP8WDI1l8fkhNGGktqkvL5qwNCtGEpgQ==", + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.25.9" + } + }, "@babel/plugin-syntax-export-namespace-from": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3" } }, + "@babel/plugin-syntax-flow": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.26.0.tgz", + "integrity": "sha512-B+O2DnPc0iG+YXFqOxv2WNuNU97ToWjOomUQ78DouOENWUaM5sVrmet9mcomUGQFwpJd//gvUagXBSdzO1fRKg==", + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.25.9" + } + }, "@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" } @@ -18790,25 +22750,22 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } }, "@babel/plugin-syntax-jsx": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", - "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", "requires": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" } }, "@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" } @@ -18817,7 +22774,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -18826,7 +22782,6 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" } @@ -18835,7 +22790,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -18844,7 +22798,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -18853,7 +22806,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -18862,34 +22814,41 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.14.5" } }, "@babel/plugin-syntax-typescript": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", - "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", "requires": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" } }, "@babel/plugin-transform-arrow-functions": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7" } }, + "@babel/plugin-transform-async-generator-functions": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", + "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.26.8" + } + }, "@babel/plugin-transform-async-to-generator": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", - "dev": true, "requires": { "@babel/helper-module-imports": "^7.24.7", "@babel/helper-plugin-utils": "^7.24.7", @@ -18900,33 +22859,38 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz", - "integrity": "sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==", - "dev": true, + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.0.tgz", + "integrity": "sha512-u1jGphZ8uDI2Pj/HJj6YQ6XQLZCNjOlprjxB5SVz6rq2T6SwAR+CdrWK0CP7F+9rDVMXdB0+r6Am5G5aobOjAQ==", "requires": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.26.5" } }, - "@babel/plugin-transform-classes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.7.tgz", - "integrity": "sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==", - "dev": true, + "@babel/plugin-transform-class-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "peer": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", "globals": "^11.1.0" } }, @@ -18934,26 +22898,23 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7", "@babel/template": "^7.24.7" } }, "@babel/plugin-transform-destructuring": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.7.tgz", - "integrity": "sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", "requires": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" } }, "@babel/plugin-transform-dotall-regex": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", - "dev": true, "requires": { "@babel/helper-create-regexp-features-plugin": "^7.24.7", "@babel/helper-plugin-utils": "^7.24.7" @@ -18963,7 +22924,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7" } @@ -18972,47 +22932,61 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", - "dev": true, "requires": { "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", "@babel/helper-plugin-utils": "^7.24.7" } }, + "@babel/plugin-transform-flow-strip-types": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.26.5.tgz", + "integrity": "sha512-eGK26RsbIkYUns3Y8qKl362juDDYK+wEdPGHGrhzUl6CewZFo55VZ7hg+CyMFU4dd5QQakBN86nBMpRsFpRvbQ==", + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/plugin-syntax-flow": "^7.26.0" + } + }, "@babel/plugin-transform-for-of": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" } }, "@babel/plugin-transform-function-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz", - "integrity": "sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", "requires": { - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" } }, "@babel/plugin-transform-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz", - "integrity": "sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", "requires": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" + } + }, + "@babel/plugin-transform-logical-assignment-operators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.25.9" } }, "@babel/plugin-transform-member-expression-literals": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7" } @@ -19021,28 +22995,24 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", - "dev": true, "requires": { "@babel/helper-module-transforms": "^7.24.7", "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz", - "integrity": "sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==", - "dev": true, + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", "requires": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7" + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" } }, "@babel/plugin-transform-modules-systemjs": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz", "integrity": "sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==", - "dev": true, "requires": { "@babel/helper-hoist-variables": "^7.24.7", "@babel/helper-module-transforms": "^7.24.7", @@ -19054,7 +23024,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", - "dev": true, "requires": { "@babel/helper-module-transforms": "^7.24.7", "@babel/helper-plugin-utils": "^7.24.7" @@ -19064,7 +23033,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", - "dev": true, "requires": { "@babel/helper-create-regexp-features-plugin": "^7.24.7", "@babel/helper-plugin-utils": "^7.24.7" @@ -19074,35 +23042,100 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7" } }, + "@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.26.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", + "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.26.5" + } + }, + "@babel/plugin-transform-numeric-separator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.25.9" + } + }, + "@babel/plugin-transform-object-rest-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "peer": true, + "requires": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" + } + }, "@babel/plugin-transform-object-super": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7", "@babel/helper-replace-supers": "^7.24.7" } }, + "@babel/plugin-transform-optional-catch-binding": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.25.9" + } + }, + "@babel/plugin-transform-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + } + }, "@babel/plugin-transform-parameters": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", - "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", "requires": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" + } + }, + "@babel/plugin-transform-private-methods": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "peer": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + } + }, + "@babel/plugin-transform-private-property-in-object": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "peer": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" } }, "@babel/plugin-transform-property-literals": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7" } @@ -19111,22 +23144,20 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz", "integrity": "sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-react-jsx": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.24.7.tgz", - "integrity": "sha512-+Dj06GDZEFRYvclU6k4bme55GKBEWUmByM/eoKuqg4zTNQHiApWRhQph5fxQB2wAEFvRzL1tOEj1RJ19wJrhoA==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", + "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", "requires": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-jsx": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/types": "^7.25.9" } }, "@babel/plugin-transform-react-jsx-development": { @@ -19142,7 +23173,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7" } @@ -19151,7 +23181,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7" } @@ -19170,7 +23199,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7", "regenerator-transform": "^0.15.2" @@ -19180,16 +23208,36 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7" } }, + "@babel/plugin-transform-runtime": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz", + "integrity": "sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw==", + "peer": true, + "requires": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true + } + } + }, "@babel/plugin-transform-shorthand-properties": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7" } @@ -19198,7 +23246,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" @@ -19208,7 +23255,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7" } @@ -19217,7 +23263,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7" } @@ -19226,28 +23271,26 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.7.tgz", "integrity": "sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-typescript": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.7.tgz", - "integrity": "sha512-iLD3UNkgx2n/HrjBesVbYX6j0yqn/sJktvbtKKgcaLIQ4bTTQ8obAypc1VpyHPD2y4Phh9zHOaAt8e/L14wCpw==", - "dev": true, + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.0.tgz", + "integrity": "sha512-fRGGjO2UEGPjvEcyAZXRXAS8AfdaQoq7HnxAbJoAoW10B9xOKesmmndJv+Sym2a+9FHWZ9KbyyLCe9s0Sn5jtg==", "requires": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-typescript": "^7.24.7" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.27.0", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-syntax-typescript": "^7.25.9" } }, "@babel/plugin-transform-unicode-escapes": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.24.7" } @@ -19256,7 +23299,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", - "dev": true, "requires": { "@babel/helper-create-regexp-features-plugin": "^7.24.7", "@babel/helper-plugin-utils": "^7.24.7" @@ -19284,7 +23326,6 @@ "version": "7.11.5", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz", "integrity": "sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA==", - "dev": true, "requires": { "@babel/compat-data": "^7.11.0", "@babel/helper-compilation-targets": "^7.10.4", @@ -19356,11 +23397,21 @@ "semver": "^5.5.0" } }, + "@babel/preset-flow": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.25.9.tgz", + "integrity": "sha512-EASHsAhE+SSlEzJ4bzfusnXSHiU+JfAYzj+jbw2vgQKgq5HrUr8qs+vgtiEL5dOH6sEweI+PNt2D7AqrDSHyqQ==", + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-transform-flow-strip-types": "^7.25.9" + } + }, "@babel/preset-modules": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6.tgz", "integrity": "sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", @@ -19394,11 +23445,23 @@ "@babel/plugin-transform-typescript": "^7.10.4" } }, + "@babel/register": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.25.9.tgz", + "integrity": "sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA==", + "peer": true, + "requires": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" + } + }, "@babel/regjsgen": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "dev": true + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "@babel/runtime": { "version": "7.11.2", @@ -19409,37 +23472,51 @@ } }, "@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", - "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", "requires": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" } }, "@babel/traverse": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", - "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "requires": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + } + }, + "@babel/traverse--for-generate-function-map": { + "version": "npm:@babel/traverse@7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "peer": true, "requires": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.6", - "@babel/parser": "^7.25.6", - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.6", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", "debug": "^4.3.1", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", - "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", "requires": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" } }, "@bcoe/v8-coverage": { @@ -19688,11 +23765,16 @@ "tslib": "^2.4.0" } }, + "@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "peer": true + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, "requires": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -19704,8 +23786,7 @@ "@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==" }, "@jest/console": { "version": "26.6.2", @@ -19880,6 +23961,89 @@ } } }, + "@jest/create-cache-key-function": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "peer": true, + "requires": { + "@jest/types": "^29.6.3" + }, + "dependencies": { + "@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "peer": true, + "requires": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "peer": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "peer": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "peer": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "peer": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true + }, + "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==", + "peer": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "@jest/environment": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", @@ -20031,6 +24195,15 @@ } } }, + "@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "peer": true, + "requires": { + "@sinclair/typebox": "^0.27.8" + } + }, "@jest/source-map": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", @@ -20245,6 +24418,16 @@ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" }, + "@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "peer": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", @@ -20300,17 +24483,448 @@ "@primer/octicons-react": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-10.1.0.tgz", - "integrity": "sha512-WjIaetTaf4x66xxaG/gxwsWRL2JYG33n8CfeR/L134YcX2zl9TPps9crLzI2f3rxjOdKZgVFBoUh94Cim4Fflw==" + "integrity": "sha512-WjIaetTaf4x66xxaG/gxwsWRL2JYG33n8CfeR/L134YcX2zl9TPps9crLzI2f3rxjOdKZgVFBoUh94Cim4Fflw==", + "requires": {} }, "@react-native-community/netinfo": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-4.7.0.tgz", - "integrity": "sha512-a/sDB+AsLEUNmhAUlAaTYeXKyQdFGBUfatqKkX5jluBo2CB3OAuTHfm7rSjcaLB9EmG5iSq3fOTpync2E7EYTA==" + "integrity": "sha512-a/sDB+AsLEUNmhAUlAaTYeXKyQdFGBUfatqKkX5jluBo2CB3OAuTHfm7rSjcaLB9EmG5iSq3fOTpync2E7EYTA==", + "requires": {} + }, + "@react-native/assets-registry": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.78.1.tgz", + "integrity": "sha512-SegfYQFuut05EQIQIVB/6QMGaxJ29jEtPmzFWJdIp/yc2mmhIq7MfWRjwOe6qbONzIdp6Ca8p835hiGiAGyeKQ==", + "peer": true + }, + "@react-native/babel-plugin-codegen": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.78.1.tgz", + "integrity": "sha512-rD0tnct/yPEtoOc8eeFHIf8ZJJJEzLkmqLs8HZWSkt3w9VYWngqLXZxiDGqv0ngXjunAlC/Hpq+ULMVOvOnByw==", + "peer": true, + "requires": { + "@babel/traverse": "^7.25.3", + "@react-native/codegen": "0.78.1" + } + }, + "@react-native/babel-preset": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.78.1.tgz", + "integrity": "sha512-yTVcHmEdNQH4Ju7lhvbiQaGxBpMcalgkBy/IvHowXKk/ex3nY1PolF16/mBG1BrefcUA/rtJpqTtk2Ii+7T/Lw==", + "peer": true, + "requires": { + "@babel/core": "^7.25.2", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/template": "^7.25.0", + "@react-native/babel-plugin-codegen": "0.78.1", + "babel-plugin-syntax-hermes-parser": "0.25.1", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "dependencies": { + "@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "peer": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + } + }, + "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==", + "peer": true + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true + } + } + }, + "@react-native/codegen": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.78.1.tgz", + "integrity": "sha512-kGG5qAM9JdFtxzUwe7c6CyJbsU2PnaTrtCHA2dF8VEiNX1K3yd9yKPzfkxA7HPvmHoAn3ga1941O79BStWcM3A==", + "peer": true, + "requires": { + "@babel/parser": "^7.25.3", + "glob": "^7.1.1", + "hermes-parser": "0.25.1", + "invariant": "^2.2.4", + "jscodeshift": "^17.0.0", + "nullthrows": "^1.1.1", + "yargs": "^17.6.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "peer": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "peer": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "peer": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "peer": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "peer": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "peer": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "peer": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "peer": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "peer": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "peer": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "peer": true + } + } + }, + "@react-native/community-cli-plugin": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.78.1.tgz", + "integrity": "sha512-S6vF4oWpFqThpt/dBLrqLQw5ED2M1kg5mVtiL6ZqpoYIg+/e0vg7LZ8EXNbcdMDH4obRnm2xbOd+qlC7mOzNBg==", + "peer": true, + "requires": { + "@react-native/dev-middleware": "0.78.1", + "@react-native/metro-babel-transformer": "0.78.1", + "chalk": "^4.0.0", + "debug": "^2.2.0", + "invariant": "^2.2.4", + "metro": "^0.81.0", + "metro-config": "^0.81.0", + "metro-core": "^0.81.0", + "readline": "^1.3.0", + "semver": "^7.1.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "peer": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "peer": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "peer": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "requires": { + "ms": "2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + }, + "semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "peer": true + }, + "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==", + "peer": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@react-native/debugger-frontend": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.78.1.tgz", + "integrity": "sha512-xev/B++QLxSDpEBWsc74GyCuq9XOHYTBwcGSpsuhOJDUha6WZIbEEvZe3LpVW+OiFso4oGIdnVSQntwippZdWw==", + "peer": true + }, + "@react-native/dev-middleware": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.78.1.tgz", + "integrity": "sha512-l8p7/dXa1vWPOdj0iuACkex8lgbLpYyPZ3QXGkocMcpl0bQ24K7hf3Bj02tfptP5PAm16b2RuEi04sjIGHUzzg==", + "peer": true, + "requires": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.78.1", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.2.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "selfsigned": "^2.4.1", + "serve-static": "^1.16.2", + "ws": "^6.2.3" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + }, + "ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "peer": true, + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "@react-native/gradle-plugin": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.78.1.tgz", + "integrity": "sha512-v8GJU+8DzQDWO3iuTFI1nbuQ/kzuqbXv07VVtSIMLbdofHzuuQT14DGBacBkrIDKBDTVaBGAc/baDNsyxCghng==", + "peer": true + }, + "@react-native/js-polyfills": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.78.1.tgz", + "integrity": "sha512-Ogcv4QOA1o3IyErrf/i4cDnP+nfNcIfGTgw6iNQyAPry1xjPOz4ziajskLpWG/3ADeneIZuyZppKB4A28rZSvg==", + "peer": true + }, + "@react-native/metro-babel-transformer": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.78.1.tgz", + "integrity": "sha512-jQWf69D+QTMvSZSWLR+cr3VUF16rGB6sbD+bItD8Czdfn3hajzfMoHJTkVFP7991cjK5sIVekNiQIObou8JSQw==", + "peer": true, + "requires": { + "@babel/core": "^7.25.2", + "@react-native/babel-preset": "0.78.1", + "hermes-parser": "0.25.1", + "nullthrows": "^1.1.1" + }, + "dependencies": { + "@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "peer": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + } + }, + "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==", + "peer": true + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true + } + } + }, + "@react-native/normalize-colors": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.78.1.tgz", + "integrity": "sha512-h4wARnY4iBFgigN1NjnaKFtcegWwQyE9+CEBVG4nHmwMtr8lZBmc7ZKIM6hUc6lxqY/ugHg48aSQSynss7mJUg==", + "peer": true }, "@restart/context": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz", - "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==" + "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==", + "requires": {} }, "@restart/hooks": { "version": "0.3.27", @@ -20320,6 +24934,12 @@ "dequal": "^2.0.2" } }, + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "peer": true + }, "@sinonjs/commons": { "version": "1.8.6", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", @@ -20348,7 +24968,6 @@ "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, "requires": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -20361,7 +24980,6 @@ "version": "7.6.8", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", - "dev": true, "requires": { "@babel/types": "^7.0.0" } @@ -20370,7 +24988,6 @@ "version": "7.4.4", "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, "requires": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -20380,7 +24997,6 @@ "version": "7.20.6", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", - "dev": true, "requires": { "@babel/types": "^7.20.7" } @@ -20420,11 +25036,19 @@ "@types/react": "*" } }, + "@types/enzyme-adapter-react-16": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.9.tgz", + "integrity": "sha512-z24MMxGtUL8HhXdye3tWzjp+19QTsABqLaX2oOZpxMPHRJgLfahQmOeTTrEBQd9ogW20+UmPBXD9j+XOasFHvw==", + "dev": true, + "requires": { + "@types/enzyme": "*" + } + }, "@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, "requires": { "@types/node": "*" } @@ -20464,14 +25088,12 @@ "@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" }, "@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, "requires": { "@types/istanbul-lib-coverage": "*" } @@ -20480,7 +25102,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, "requires": { "@types/istanbul-lib-report": "*" } @@ -20523,8 +25144,16 @@ "@types/node": { "version": "14.11.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.1.tgz", - "integrity": "sha512-oTQgnd0hblfLsJ6BvJzzSL+Inogp3lq9fGgqRkMB/ziKMgEUaFl801OncOzUmalfzt14N0oPHMK47ipl+wbTIw==", - "dev": true + "integrity": "sha512-oTQgnd0hblfLsJ6BvJzzSL+Inogp3lq9fGgqRkMB/ziKMgEUaFl801OncOzUmalfzt14N0oPHMK47ipl+wbTIw==" + }, + "@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "peer": true, + "requires": { + "@types/node": "*" + } }, "@types/normalize-package-data": { "version": "2.4.4", @@ -20638,8 +25267,7 @@ "@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, "@types/unist": { "version": "2.0.10", @@ -20663,8 +25291,7 @@ "@types/yargs-parser": { "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, "@typescript-eslint/eslint-plugin": { "version": "4.1.1", @@ -20958,6 +25585,25 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "peer": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "peer": true, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -20978,7 +25624,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "7.2.0", @@ -21033,13 +25680,21 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true + "dev": true, + "requires": {} }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} + }, + "anser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", + "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "peer": true }, "ansi-colors": { "version": "4.1.3", @@ -21059,13 +25714,13 @@ "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 + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -21074,7 +25729,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -21090,7 +25744,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -21222,6 +25875,12 @@ "is-shared-array-buffer": "^1.0.2" } }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "peer": true + }, "asn1.js": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", @@ -21274,6 +25933,15 @@ "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", "dev": true }, + "ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "peer": true, + "requires": { + "tslib": "^2.0.1" + } + }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", @@ -21287,6 +25955,12 @@ "dev": true, "optional": true }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "peer": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -21436,7 +26110,6 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -21467,11 +26140,67 @@ "resolve": "^1.12.0" } }, + "babel-plugin-polyfill-corejs2": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", + "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", + "peer": true, + "requires": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.4", + "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true + } + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "peer": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", + "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", + "peer": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.6.4" + } + }, + "babel-plugin-syntax-hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.25.1.tgz", + "integrity": "sha512-IVNpGzboFLfXZUAwkLFcI/bnqVbwky0jP3eBno4HKtqvQJAHBLdgxiG6lQ4to0+Q/YCN3PO0od5NZwIKyY4REQ==", + "peer": true, + "requires": { + "hermes-parser": "0.25.1" + } + }, "babel-plugin-syntax-jsx": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" }, + "babel-plugin-transform-flow-enums": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", + "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", + "peer": true, + "requires": { + "@babel/plugin-syntax-flow": "^7.12.1" + } + }, "babel-plugin-typescript-to-proptypes": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/babel-plugin-typescript-to-proptypes/-/babel-plugin-typescript-to-proptypes-1.4.1.tgz", @@ -21488,7 +26217,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, "requires": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -21543,8 +26271,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base": { "version": "0.11.2", @@ -21621,7 +26348,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -21631,7 +26357,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "requires": { "fill-range": "^7.1.1" } @@ -21723,22 +26448,20 @@ } }, "browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", - "dev": true, + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "requires": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" } }, "bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, "requires": { "node-int64": "^0.4.0" } @@ -21757,8 +26480,7 @@ "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "buffer-xor": { "version": "1.0.3", @@ -21825,6 +26547,32 @@ "set-function-length": "^1.2.1" } }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "peer": true, + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "peer": true + } + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "peer": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -21833,14 +26581,12 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "caniuse-lite": { - "version": "1.0.30001660", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz", - "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==", - "dev": true + "version": "1.0.30001707", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz", + "integrity": "sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==" }, "capture-exit": { "version": "2.0.0", @@ -21855,6 +26601,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -21923,17 +26670,73 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true }, + "chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "peer": true, + "requires": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "peer": true + } + } + }, "chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true }, + "chromium-edge-launcher": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz", + "integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==", + "peer": true, + "requires": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "peer": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "peer": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "peer": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" }, "cipher-base": { "version": "1.0.4", @@ -22030,6 +26833,17 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", "integrity": "sha512-h5FLmEMFHeuzqmpVRcDayNlVZ+k4uK1niyKQN6oUMe7ieJihv44Vc3dY/kDnnWX4PDQSwes48s965PG/D4GntQ==" }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "peer": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -22056,6 +26870,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "requires": { "color-name": "1.1.3" } @@ -22063,7 +26878,8 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "combined-stream": { "version": "1.0.8", @@ -22087,8 +26903,7 @@ "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" }, "component-emitter": { "version": "1.2.1", @@ -22098,8 +26913,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "concat-stream": { "version": "1.6.2", @@ -22113,6 +26927,35 @@ "typedarray": "^0.0.6" } }, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "peer": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + } + } + }, "console-browserify": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", @@ -22162,12 +27005,11 @@ "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" }, "core-js-compat": { - "version": "3.38.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", - "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", - "dev": true, + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz", + "integrity": "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==", "requires": { - "browserslist": "^4.23.3" + "browserslist": "^4.24.4" } }, "core-util-is": { @@ -22505,6 +27347,12 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "peer": true + }, "dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -22520,6 +27368,12 @@ "minimalistic-assert": "^1.0.0" } }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "peer": true + }, "detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", @@ -22677,16 +27531,21 @@ "stream-shift": "^1.0.0" } }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "peer": true + }, "electron-to-chromium": { - "version": "1.5.23", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.23.tgz", - "integrity": "sha512-mBhODedOXg4v5QWwl21DjM5amzjmI1zw9EPrPK/5Wx7C8jt33bpZNrC7OhHUG3pxRtbLpr3W2dXT+Ph1SsfRZA==", - "dev": true + "version": "1.5.123", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.123.tgz", + "integrity": "sha512-refir3NlutEZqlKaBLK0tzlVLe5P2wDKS7UQt/3SpibizgsRAPOsqQC3ffw1nlv3ze5gjRQZYHoPymgVZkplFA==" }, "elliptic": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.0.tgz", - "integrity": "sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", "dev": true, "requires": { "bn.js": "^4.11.9", @@ -22724,6 +27583,12 @@ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "peer": true + }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -22878,6 +27743,15 @@ "is-arrayish": "^0.2.1" } }, + "error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "peer": true, + "requires": { + "stackframe": "^1.3.4" + } + }, "es-abstract": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", @@ -22994,10 +27868,15 @@ } }, "escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "peer": true }, "escape-string-regexp": { "version": "1.0.5", @@ -23268,7 +28147,8 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz", "integrity": "sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg==", - "dev": true + "dev": true, + "requires": {} }, "eslint-plugin-import": { "version": "2.22.0", @@ -23352,7 +28232,8 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.1.2.tgz", "integrity": "sha512-ykUeqkGyUGgwTtk78C0o8UG2fzwmgJ0qxBGPp2WqRKsTwcLuVf01kTDRAtOsd4u6whX2XOC8749n2vPydP82fg==", - "dev": true + "dev": true, + "requires": {} }, "eslint-scope": { "version": "5.1.1", @@ -23409,8 +28290,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { "version": "1.5.0", @@ -23455,8 +28335,19 @@ "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "peer": true + }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "peer": true }, "events": { "version": "3.3.0", @@ -23618,6 +28509,12 @@ } } }, + "exponential-backoff": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", + "integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==", + "peer": true + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -23697,8 +28594,7 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-levenshtein": { "version": "2.0.6", @@ -23719,7 +28615,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, "requires": { "bser": "2.1.1" } @@ -23773,16 +28668,46 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "peer": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + } + } + }, "find-cache-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, "requires": { "commondir": "^1.0.1", "make-dir": "^2.0.0", @@ -23798,7 +28723,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -23950,6 +28874,18 @@ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, + "flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "peer": true + }, + "flow-parser": { + "version": "0.265.3", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.265.3.tgz", + "integrity": "sha512-YH50TTYgnzDnuaZlAxLYQ0UZtXSbbizMO3OCpoY8obvLReJmvQ5UUW22efsC3SZJmze/tATfQ0PtkKul2XwWBw==", + "peer": true + }, "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", @@ -23995,6 +28931,12 @@ "map-cache": "^0.2.2" } }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "peer": true + }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -24026,14 +28968,12 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "optional": true }, "function-bind": { @@ -24068,14 +29008,12 @@ "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-intrinsic": { "version": "1.2.4", @@ -24093,8 +29031,7 @@ "get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==" }, "get-params": { "version": "0.1.2", @@ -24148,7 +29085,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -24283,7 +29219,8 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true }, "has-property-descriptors": { "version": "1.0.2", @@ -24406,6 +29343,21 @@ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==" }, + "hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "peer": true + }, + "hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "peer": true, + "requires": { + "hermes-estree": "0.25.1" + } + }, "history": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", @@ -24486,6 +29438,27 @@ "entities": "^4.4.0" } }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "peer": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "dependencies": { + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "peer": true + } + } + }, "http-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", @@ -24563,6 +29536,15 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "image-size": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "peer": true, + "requires": { + "queue": "6.0.2" + } + }, "immutable": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", @@ -24621,7 +29603,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -24630,8 +29611,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.8", @@ -24798,12 +29778,16 @@ "is-data-descriptor": "^1.0.1" } }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", + "peer": true + }, "is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "optional": true + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" }, "is-extendable": { "version": "1.0.1", @@ -24850,8 +29834,7 @@ "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 + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-number-object": { "version": "1.0.7", @@ -24871,7 +29854,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "requires": { "isobject": "^3.0.1" } @@ -24965,8 +29947,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "optional": true, "requires": { "is-docker": "^2.0.0" } @@ -24986,20 +29966,17 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" }, "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 + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==" }, "istanbul-lib-instrument": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, "requires": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -25012,7 +29989,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", - "dev": true, "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", @@ -25034,14 +30010,12 @@ "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 + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" } } }, @@ -25944,7 +30918,8 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true + "dev": true, + "requires": {} }, "jest-regex-util": { "version": "26.0.0", @@ -26628,7 +31603,6 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -26639,6 +31613,104 @@ "resolved": "https://registry.npmjs.org/jsan/-/jsan-3.1.14.tgz", "integrity": "sha512-wStfgOJqMv4QKktuH273f5fyi3D3vy2pHOiSDGPvpcS/q+wb/M7AK3vkCcaHbkZxDOlDU/lDJgccygKSG2OhtA==" }, + "jsc-safe-url": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", + "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", + "peer": true + }, + "jscodeshift": { + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-17.3.0.tgz", + "integrity": "sha512-LjFrGOIORqXBU+jwfC9nbkjmQfFldtMIoS6d9z2LG/lkmyNXsJAySPT+2SWXJEoE68/bCWcxKpXH37npftgmow==", + "peer": true, + "requires": { + "@babel/core": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/preset-flow": "^7.24.7", + "@babel/preset-typescript": "^7.24.7", + "@babel/register": "^7.24.6", + "flow-parser": "0.*", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.7", + "neo-async": "^2.5.0", + "picocolors": "^1.0.1", + "recast": "^0.23.11", + "tmp": "^0.2.3", + "write-file-atomic": "^5.0.1" + }, + "dependencies": { + "@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "peer": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + } + }, + "@babel/preset-typescript": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.0.tgz", + "integrity": "sha512-vxaPFfJtHhgeOVXRKuHpHPAOgymmy8V8I65T1q53R7GCZlefKeCaTyDs3zOPHTTbmquvNlQYC5klEvWsBAtrBQ==", + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.26.3", + "@babel/plugin-transform-typescript": "^7.27.0" + } + }, + "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==", + "peer": true + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "peer": true + }, + "write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "peer": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + } + } + } + }, "jsdom": { "version": "16.7.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", @@ -26689,15 +31761,14 @@ } }, "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==" }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-parse-even-better-errors": { "version": "2.3.1", @@ -26724,8 +31795,7 @@ "json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" }, "jsx-ast-utils": { "version": "2.4.1", @@ -26740,8 +31810,7 @@ "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "kleur": { "version": "3.0.3", @@ -26758,14 +31827,12 @@ "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" }, "levenary": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", - "dev": true, "requires": { "leven": "^3.1.0" } @@ -26780,6 +31847,33 @@ "type-check": "~0.4.0" } }, + "lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "peer": true, + "requires": { + "debug": "^2.6.9", + "marky": "^1.2.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + } + } + }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -26831,240 +31925,1186 @@ "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", "dev": true }, - "loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, + "loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "peer": true + }, + "lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==", + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "dev": true + }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "peer": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "requires": { + "tmpl": "1.0.5" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "marky": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", + "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", + "peer": true + }, + "mattermost-redux": { + "version": "5.33.1", + "resolved": "https://registry.npmjs.org/mattermost-redux/-/mattermost-redux-5.33.1.tgz", + "integrity": "sha512-fHTW5PeB0xAE5u6uMds+hlWaJsUeWgE1034vekUah1X4bZCzBcMK4fM+ycGaIQ7c5Ylj9ML6Ni10XJYiQpMo5w==", + "requires": { + "core-js": "3.8.3", + "form-data": "3.0.0", + "gfycat-sdk": "1.4.18", + "moment-timezone": "0.5.32", + "redux": "4.0.5", + "redux-action-buffer": "1.2.0", + "redux-offline": "git+https://github.com/enahum/redux-offline.git#885024de96b6ec73650c340c8928066585c413df", + "redux-persist": "4.9.1", + "redux-persist-node-storage": "2.0.0", + "redux-thunk": "2.3.0", + "remote-redux-devtools": "0.5.16", + "reselect": "4.0.0", + "rudder-sdk-js": "1.0.14", + "serialize-error": "6.0.0", + "shallow-equals": "1.0.0" + }, + "dependencies": { + "core-js": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.3.tgz", + "integrity": "sha512-KPYXeVZYemC2TkNEkX/01I+7yd+nX3KddKwZ1Ww7SKWdI2wQprSgLmrTddT8nw92AjEklTsPBoSdQBhbI1bQ6Q==" + }, + "form-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mdast-util-definitions": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + } + }, + "mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + } + }, + "mdast-util-to-hast": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", + "requires": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + } + }, + "mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "requires": { + "@types/mdast": "^3.0.0" + } + }, + "memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, + "memory-fs": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz", + "integrity": "sha512-+y4mDxU4rvXXu5UDSGCGNiesFmwCHuefGMoPCO1WYucNYj7DsLqrFaa2fXVI0H+NNiPTwwzKwspn9yTZqUGqng==", + "dev": true + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "metro": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.81.4.tgz", + "integrity": "sha512-78f0aBNPuwXW7GFnSc+Y0vZhbuQorXxdgqQfvSRqcSizqwg9cwF27I05h47tL8AzQcizS1JZncvq4xf5u/Qykw==", + "peer": true, + "requires": { + "@babel/code-frame": "^7.24.7", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.3", + "@babel/types": "^7.25.2", + "accepts": "^1.3.7", + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "error-stack-parser": "^2.0.6", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.25.1", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.81.4", + "metro-cache": "0.81.4", + "metro-cache-key": "0.81.4", + "metro-config": "0.81.4", + "metro-core": "0.81.4", + "metro-file-map": "0.81.4", + "metro-resolver": "0.81.4", + "metro-runtime": "0.81.4", + "metro-source-map": "0.81.4", + "metro-symbolicate": "0.81.4", + "metro-transform-plugins": "0.81.4", + "metro-transform-worker": "0.81.4", + "mime-types": "^2.1.27", + "nullthrows": "^1.1.1", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "throat": "^5.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "dependencies": { + "@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "peer": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "dependencies": { + "debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "peer": true, + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "peer": true + } + } + }, + "@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "peer": true, + "requires": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "peer": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "peer": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "peer": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "peer": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "peer": true + }, + "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==", + "peer": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "requires": { + "ms": "2.0.0" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "peer": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "peer": true + }, + "jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "peer": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "dependencies": { + "ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "peer": true + } + } + }, + "jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "peer": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "peer": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true + }, + "serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "peer": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "peer": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "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==", + "peer": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "peer": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "peer": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "peer": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "peer": true + } + } + }, + "metro-babel-transformer": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.81.4.tgz", + "integrity": "sha512-WW0yswWrW+eTVK9sYD+b1HwWOiUlZlUoomiw9TIOk0C+dh2V90Wttn/8g62kYi0Y4i+cJfISerB2LbV4nuRGTA==", + "peer": true, "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" + "@babel/core": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "hermes-parser": "0.25.1", + "nullthrows": "^1.1.1" }, "dependencies": { - "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, + "@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "peer": true, "requires": { - "minimist": "^1.2.0" + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" } + }, + "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==", + "peer": true + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true } } }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, - "lodash.escape": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", - "integrity": "sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==", - "dev": true - }, - "lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "dev": true - }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "metro-cache": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.81.4.tgz", + "integrity": "sha512-sxCPH3gowDxazSaZZrwdNPEpnxR8UeXDnvPjBF9+5btDBNN2DpWvDAXPvrohkYkFImhc0LajS2V7eOXvu9PnvQ==", + "peer": true, "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" + "exponential-backoff": "^3.1.1", + "flow-enums-runtime": "^0.0.6", + "metro-core": "0.81.4" } }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, + "metro-cache-key": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.81.4.tgz", + "integrity": "sha512-3SaWQybvf1ivasjBegIxzVKLJzOpcz+KsnGwXFOYADQq0VN4cnM7tT+u2jkOhk6yJiiO1WIjl68hqyMOQJRRLg==", + "peer": true, "requires": { - "yallist": "^3.0.2" + "flow-enums-runtime": "^0.0.6" } }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, + "metro-config": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.81.4.tgz", + "integrity": "sha512-QnhMy3bRiuimCTy7oi5Ug60javrSa3lPh0gpMAspQZHY9h6y86jwHtZPLtlj8hdWQESIlrbeL8inMSF6qI/i9Q==", + "peer": true, "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "connect": "^3.6.5", + "cosmiconfig": "^5.0.5", + "flow-enums-runtime": "^0.0.6", + "jest-validate": "^29.7.0", + "metro": "0.81.4", + "metro-cache": "0.81.4", + "metro-core": "0.81.4", + "metro-runtime": "0.81.4" + }, + "dependencies": { + "@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "peer": true, + "requires": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "peer": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "peer": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "peer": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "peer": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "peer": true + }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "peer": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "peer": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "peer": true + }, + "jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "peer": true, + "requires": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "peer": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "peer": true, + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "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==", + "peer": true + } + } + }, + "react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "peer": true + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "peer": true + }, + "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==", + "peer": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, + "metro-core": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.81.4.tgz", + "integrity": "sha512-GdL4IgmgJhrMA/rTy2lRqXKeXfC77Rg+uvhUEkbhyfj/oz7PrdSgvIFzziapjdHwk1XYq0KyFh/CcVm8ZawG6A==", + "peer": true, "requires": { - "tmpl": "1.0.5" + "flow-enums-runtime": "^0.0.6", + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.81.4" } }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "dev": true, + "metro-file-map": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.81.4.tgz", + "integrity": "sha512-qUIBzkiqOi3qEuscu4cJ83OYQ4hVzjON19FAySWqYys9GKCmxlKa7LkmwqdpBso6lQl+JXZ7nCacX90w5wQvPA==", + "peer": true, "requires": { - "object-visit": "^1.0.0" + "debug": "^2.2.0", + "fb-watchman": "^2.0.0", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + }, + "dependencies": { + "@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "peer": true, + "requires": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "peer": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "peer": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "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==", + "peer": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "peer": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "peer": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "peer": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "requires": { + "ms": "2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true + }, + "jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "peer": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "peer": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "peer": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "mattermost-redux": { - "version": "5.33.1", - "resolved": "https://registry.npmjs.org/mattermost-redux/-/mattermost-redux-5.33.1.tgz", - "integrity": "sha512-fHTW5PeB0xAE5u6uMds+hlWaJsUeWgE1034vekUah1X4bZCzBcMK4fM+ycGaIQ7c5Ylj9ML6Ni10XJYiQpMo5w==", + "metro-minify-terser": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.81.4.tgz", + "integrity": "sha512-oVvq/AGvqmbhuijJDZZ9npeWzaVyeBwQKtdlnjcQ9fH7nR15RiBr5y2zTdgTEdynqOIb1Kc16l8CQIUSzOWVFA==", + "peer": true, "requires": { - "core-js": "3.8.3", - "form-data": "3.0.0", - "gfycat-sdk": "1.4.18", - "moment-timezone": "0.5.32", - "redux": "4.0.5", - "redux-action-buffer": "1.2.0", - "redux-offline": "git+https://github.com/enahum/redux-offline.git#885024de96b6ec73650c340c8928066585c413df", - "redux-persist": "4.9.1", - "redux-persist-node-storage": "2.0.0", - "redux-thunk": "2.3.0", - "remote-redux-devtools": "0.5.16", - "reselect": "4.0.0", - "rudder-sdk-js": "1.0.14", - "serialize-error": "6.0.0", - "shallow-equals": "1.0.0" + "flow-enums-runtime": "^0.0.6", + "terser": "^5.15.0" }, "dependencies": { - "core-js": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.3.tgz", - "integrity": "sha512-KPYXeVZYemC2TkNEkX/01I+7yd+nX3KddKwZ1Ww7SKWdI2wQprSgLmrTddT8nw92AjEklTsPBoSdQBhbI1bQ6Q==" + "acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "peer": true }, - "form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "peer": true + }, + "terser": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", + "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", + "peer": true, "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" } } } }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, + "metro-resolver": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.81.4.tgz", + "integrity": "sha512-Ng7G2mXjSExMeRzj6GC19G6IJ0mfIbOLgjArsMWJgtt9ViZiluCwgWsMW9juBC5NSwjJxUMK2x6pC5NIMFLiHA==", + "peer": true, "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" + "flow-enums-runtime": "^0.0.6" } }, - "mdast-util-definitions": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", - "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "metro-runtime": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.81.4.tgz", + "integrity": "sha512-fBoRgqkF69CwyPtBNxlDi5ha26Zc8f85n2THXYoh13Jn/Bkg8KIDCdKPp/A1BbSeNnkH/++H2EIIfnmaff4uRg==", + "peer": true, "requires": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "unist-util-visit": "^4.0.0" + "@babel/runtime": "^7.25.0", + "flow-enums-runtime": "^0.0.6" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "peer": true, + "requires": { + "regenerator-runtime": "^0.14.0" + } + }, + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "peer": true + } } }, - "mdast-util-from-markdown": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", - "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "metro-source-map": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.81.4.tgz", + "integrity": "sha512-IOwVQ7mLqoqvsL70RZtl1EyE3f9jp43kVsAsb/B/zoWmu0/k4mwEhGLTxmjdXRkLJqPqPrh7WmFChAEf9trW4Q==", + "peer": true, "requires": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "mdast-util-to-string": "^3.1.0", - "micromark": "^3.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-decode-string": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "unist-util-stringify-position": "^3.0.0", - "uvu": "^0.5.0" + "@babel/traverse": "^7.25.3", + "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", + "@babel/types": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-symbolicate": "0.81.4", + "nullthrows": "^1.1.1", + "ob1": "0.81.4", + "source-map": "^0.5.6", + "vlq": "^1.0.0" } }, - "mdast-util-to-hast": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", - "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", + "metro-symbolicate": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.81.4.tgz", + "integrity": "sha512-rWxTmYVN6/BOSaMDUHT8HgCuRf6acd0AjHkenYlHpmgxg7dqdnAG1hLq999q2XpW5rX+cMamZD5W5Ez2LqGaag==", + "peer": true, "requires": { - "@types/hast": "^2.0.0", - "@types/mdast": "^3.0.0", - "mdast-util-definitions": "^5.0.0", - "micromark-util-sanitize-uri": "^1.1.0", - "trim-lines": "^3.0.0", - "unist-util-generated": "^2.0.0", - "unist-util-position": "^4.0.0", - "unist-util-visit": "^4.0.0" + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-source-map": "0.81.4", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "vlq": "^1.0.0" } }, - "mdast-util-to-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", - "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "metro-transform-plugins": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.81.4.tgz", + "integrity": "sha512-nlP069nDXm4v28vbll4QLApAlvVtlB66rP6h+ml8Q/CCQCPBXu2JLaoxUmkIOJQjLhMRUcgTyQHq+TXWJhydOQ==", + "peer": true, "requires": { - "@types/mdast": "^3.0.0" + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.3", + "flow-enums-runtime": "^0.0.6", + "nullthrows": "^1.1.1" + }, + "dependencies": { + "@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "peer": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + } + }, + "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==", + "peer": true + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true + } } }, - "memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" - }, - "memory-fs": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz", - "integrity": "sha512-+y4mDxU4rvXXu5UDSGCGNiesFmwCHuefGMoPCO1WYucNYj7DsLqrFaa2fXVI0H+NNiPTwwzKwspn9yTZqUGqng==", - "dev": true - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true + "metro-transform-worker": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.81.4.tgz", + "integrity": "sha512-lKAeRZ8EUMtx2cA/Y4KvICr9bIr5SE03iK3lm+l9wyn2lkjLUuPjYVep159inLeDqC6AtSubsA8MZLziP7c03g==", + "peer": true, + "requires": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/types": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "metro": "0.81.4", + "metro-babel-transformer": "0.81.4", + "metro-cache": "0.81.4", + "metro-cache-key": "0.81.4", + "metro-minify-terser": "0.81.4", + "metro-source-map": "0.81.4", + "metro-transform-plugins": "0.81.4", + "nullthrows": "^1.1.1" + }, + "dependencies": { + "@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "peer": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + } + }, + "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==", + "peer": true + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "peer": true + } + } }, "micromark": { "version": "3.2.0", @@ -27281,7 +33321,6 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", - "dev": true, "requires": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -27305,6 +33344,12 @@ } } }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "peer": true + }, "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -27340,7 +33385,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -27488,11 +33532,16 @@ } } }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "peer": true + }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "nice-try": { "version": "1.0.5", @@ -27529,11 +33578,16 @@ } } }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "peer": true + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" }, "node-libs-browser": { "version": "2.2.1", @@ -27618,10 +33672,9 @@ } }, "node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "dev": true + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" }, "normalize-package-data": { "version": "2.5.0", @@ -27638,8 +33691,7 @@ "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 + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "npm-run-path": { "version": "4.0.1", @@ -27659,12 +33711,27 @@ "boolbase": "^1.0.0" } }, + "nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "peer": true + }, "nwsapi": { "version": "2.2.10", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz", "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==", "dev": true }, + "ob1": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.81.4.tgz", + "integrity": "sha512-EZLYM8hfPraC2SYOR5EWLFAPV5e6g+p83m2Jth9bzCpFxP1NDQJYXdmXRB2bfbaWQSmm6NkIQlbzk7uU5lLfgg==", + "peer": true, + "requires": { + "flow-enums-runtime": "^0.0.6" + } + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -27803,11 +33870,19 @@ "es-object-atoms": "^1.0.0" } }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "peer": true, + "requires": { + "ee-first": "1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } @@ -27821,6 +33896,16 @@ "mimic-fn": "^2.1.0" } }, + "open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "peer": true, + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + } + }, "optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -27857,7 +33942,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -27866,7 +33950,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "requires": { "p-limit": "^2.2.0" } @@ -27874,8 +33957,7 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "pako": { "version": "1.0.11", @@ -27969,6 +34051,12 @@ "parse5": "^7.0.0" } }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "peer": true + }, "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", @@ -27991,14 +34079,12 @@ "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, "path-key": { "version": "3.1.1", @@ -28035,33 +34121,29 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, "pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==" }, "pkg-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, "requires": { "find-up": "^3.0.0" }, @@ -28070,7 +34152,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, "requires": { "locate-path": "^3.0.0" } @@ -28079,7 +34160,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -28089,7 +34169,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, "requires": { "p-limit": "^2.0.0" } @@ -28097,8 +34176,7 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==" } } }, @@ -28309,6 +34387,15 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "peer": true, + "requires": { + "asap": "~2.0.6" + } + }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -28469,6 +34556,15 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, + "queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "peer": true, + "requires": { + "inherits": "~2.0.3" + } + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -28518,6 +34614,12 @@ "safe-buffer": "^5.1.0" } }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "peer": true + }, "react": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", @@ -28563,6 +34665,27 @@ "raf": "^3.1.0" } }, + "react-devtools-core": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.1.tgz", + "integrity": "sha512-TFo1MEnkqE6hzAbaztnyR5uLTMoz6wnEWwWBsCUzNt+sVXJycuRJdDqvL078M4/h65BI/YO5XWTaxZDWVsW0fw==", + "peer": true, + "requires": { + "shell-quote": "^1.6.1", + "ws": "^7" + } + }, + "react-dom": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", + "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + } + }, "react-input-autosize": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz", @@ -28628,6 +34751,495 @@ } } }, + "react-native": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.78.1.tgz", + "integrity": "sha512-3CK/xxX02GeeVFyrXbsHvREZFVaXwHW43Km/EdYITn5G32cccWTGaqY9QdPddEBLw5O3BPip3LHbR1SywE0cpA==", + "peer": true, + "requires": { + "@jest/create-cache-key-function": "^29.6.3", + "@react-native/assets-registry": "0.78.1", + "@react-native/codegen": "0.78.1", + "@react-native/community-cli-plugin": "0.78.1", + "@react-native/gradle-plugin": "0.78.1", + "@react-native/js-polyfills": "0.78.1", + "@react-native/normalize-colors": "0.78.1", + "@react-native/virtualized-lists": "0.78.1", + "abort-controller": "^3.0.0", + "anser": "^1.4.9", + "ansi-regex": "^5.0.0", + "babel-jest": "^29.7.0", + "babel-plugin-syntax-hermes-parser": "0.25.1", + "base64-js": "^1.5.1", + "chalk": "^4.0.0", + "commander": "^12.0.0", + "event-target-shim": "^5.0.1", + "flow-enums-runtime": "^0.0.6", + "glob": "^7.1.1", + "invariant": "^2.2.4", + "jest-environment-node": "^29.6.3", + "memoize-one": "^5.0.0", + "metro-runtime": "^0.81.0", + "metro-source-map": "^0.81.0", + "nullthrows": "^1.1.1", + "pretty-format": "^29.7.0", + "promise": "^8.3.0", + "react-devtools-core": "^6.0.1", + "react-refresh": "^0.14.0", + "regenerator-runtime": "^0.13.2", + "scheduler": "0.25.0", + "semver": "^7.1.3", + "stacktrace-parser": "^0.1.10", + "whatwg-fetch": "^3.0.0", + "ws": "^6.2.3", + "yargs": "^17.6.2" + }, + "dependencies": { + "@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "peer": true, + "requires": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + } + }, + "@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "peer": true, + "requires": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + } + }, + "@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "peer": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + } + }, + "@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "peer": true, + "requires": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@react-native/virtualized-lists": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.78.1.tgz", + "integrity": "sha512-v0jqDNMFXpnRnSlkDVvwNxXgPhifzzTFlxTSnHj9erKJsKpE26gSU5qB4hmJkEsscLG/ygdJ1c88aqinSh/wRA==", + "peer": true, + "requires": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + } + }, + "@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "peer": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "peer": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + } + }, + "@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "peer": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "peer": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "peer": true, + "requires": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "peer": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "peer": true, + "requires": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "peer": true + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "peer": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "peer": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "peer": true + }, + "commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "peer": true + }, + "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==", + "peer": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "peer": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "peer": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "peer": true + }, + "jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "peer": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + } + }, + "jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "peer": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "peer": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "peer": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + } + }, + "jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "peer": true + }, + "jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "peer": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "peer": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "peer": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "peer": true, + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "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==", + "peer": true + } + } + }, + "react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "peer": true + }, + "scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "peer": true + }, + "semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "peer": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "peer": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "peer": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "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==", + "peer": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "peer": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "peer": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "peer": true, + "requires": { + "async-limiter": "~1.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "peer": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "peer": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "peer": true + } + } + }, "react-overlays": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-4.1.1.tgz", @@ -28670,6 +35282,12 @@ "react-is": "^16.9.0" } }, + "react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "peer": true + }, "react-select": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/react-select/-/react-select-3.1.0.tgz", @@ -28838,6 +35456,33 @@ "picomatch": "^2.2.1" } }, + "readline": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", + "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", + "peer": true + }, + "recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "peer": true, + "requires": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "peer": true + } + } + }, "redux": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz", @@ -28921,14 +35566,12 @@ "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "regenerate-unicode-properties": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", - "dev": true, "requires": { "regenerate": "^1.4.2" } @@ -28942,7 +35585,6 @@ "version": "0.15.2", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, "requires": { "@babel/runtime": "^7.8.4" } @@ -28979,7 +35621,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", - "dev": true, "requires": { "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", @@ -28993,7 +35634,6 @@ "version": "0.9.1", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dev": true, "requires": { "jsesc": "~0.5.0" }, @@ -29001,8 +35641,7 @@ "jsesc": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==" } } }, @@ -29069,8 +35708,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" }, "require-main-filename": { "version": "2.0.0", @@ -29156,8 +35794,7 @@ "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" }, "resolve-pkg-maps": { "version": "1.0.0", @@ -29626,7 +36263,6 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -29643,11 +36279,81 @@ "ajv-keywords": "^3.5.2" } }, + "selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "peer": true, + "requires": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + } + }, "semver": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" + }, + "send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "peer": true, + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "peer": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "peer": true + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "peer": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "peer": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "peer": true + } + } }, "serialize-error": { "version": "6.0.0", @@ -29673,6 +36379,26 @@ "randombytes": "^2.1.0" } }, + "serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "peer": true, + "requires": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "dependencies": { + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "peer": true + } + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -29740,6 +36466,12 @@ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", "dev": true }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "peer": true + }, "sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", @@ -29750,6 +36482,15 @@ "safe-buffer": "^5.0.1" } }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "peer": true, + "requires": { + "kind-of": "^6.0.2" + } + }, "shallow-equals": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shallow-equals/-/shallow-equals-1.0.0.tgz", @@ -29770,6 +36511,12 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "peer": true + }, "shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", @@ -29792,8 +36539,7 @@ "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "sisteransi": { "version": "1.0.5", @@ -30010,7 +36756,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -30019,8 +36764,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -30079,8 +36823,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "ssri": { "version": "6.0.2", @@ -30095,7 +36838,6 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, "requires": { "escape-string-regexp": "^2.0.0" }, @@ -30103,8 +36845,30 @@ "escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" + } + } + }, + "stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "peer": true + }, + "stacktrace-parser": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", + "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", + "peer": true, + "requires": { + "type-fest": "^0.7.1" + }, + "dependencies": { + "type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "peer": true } } }, @@ -30139,6 +36903,12 @@ } } }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "peer": true + }, "stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", @@ -30283,7 +37053,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -30347,6 +37116,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -30493,7 +37263,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, "requires": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -30509,8 +37278,7 @@ "throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "dev": true + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==" }, "through2": { "version": "2.0.5", @@ -30531,11 +37299,22 @@ "setimmediate": "^1.0.4" } }, + "tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "peer": true + }, + "tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "peer": true + }, "tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" }, "to-arraybuffer": { "version": "1.0.1", @@ -30551,11 +37330,6 @@ "to-space-case": "^1.0.0" } }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" - }, "to-no-case": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/to-no-case/-/to-no-case-1.0.2.tgz", @@ -30603,7 +37377,6 @@ "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, "requires": { "is-number": "^7.0.0" } @@ -30616,6 +37389,12 @@ "to-no-case": "^1.0.0" } }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "peer": true + }, "tough-cookie": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", @@ -30695,8 +37474,7 @@ "tslib": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "dev": true + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, "tsutils": { "version": "3.21.0", @@ -30733,8 +37511,7 @@ "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" }, "type-fest": { "version": "0.21.3", @@ -30841,14 +37618,12 @@ "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==" }, "unicode-match-property-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, "requires": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -30857,14 +37632,12 @@ "unicode-match-property-value-ecmascript": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "dev": true + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==" }, "unicode-property-aliases-ecmascript": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==" }, "unified": { "version": "10.1.2", @@ -30972,6 +37745,12 @@ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "peer": true + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -31020,13 +37799,12 @@ "optional": true }, "update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", - "dev": true, + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "requires": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" } }, "uri-js": { @@ -31101,6 +37879,12 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "peer": true + }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -31180,6 +37964,12 @@ "unist-util-stringify-position": "^3.0.0" } }, + "vlq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", + "peer": true + }, "vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -31208,7 +37998,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, "requires": { "makeerror": "1.0.12" } @@ -31948,6 +38737,12 @@ "iconv-lite": "0.4.24" } }, + "whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "peer": true + }, "whatwg-mimetype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", @@ -32084,8 +38879,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "write": { "version": "1.0.3", @@ -32111,7 +38905,8 @@ "ws": { "version": "7.5.10", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==" + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "requires": {} }, "xml": { "version": "1.0.1", @@ -32146,8 +38941,7 @@ "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "yaml": { "version": "1.10.2", diff --git a/webapp/package.json b/webapp/package.json index 684d72cfa..70e353061 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -27,6 +27,7 @@ "@emotion/babel-preset-css-prop": "10.0.27", "@emotion/core": "10.0.35", "@types/enzyme": "3.10.6", + "@types/enzyme-adapter-react-16": "1.0.9", "@types/jest": "26.0.14", "@types/node": "14.11.1", "@types/react": "16.9.49", @@ -76,6 +77,7 @@ "react": "16.13.1", "react-bootstrap": "1.3.0", "react-custom-scrollbars": "4.2.1", + "react-dom": "16.13.1", "react-markdown": "^8.0.5", "react-redux": "7.2.1", "react-select": "3.1.0", @@ -98,6 +100,7 @@ "text-summary" ], "moduleNameMapper": { + "^@/(.*)$": "/src/$1", "^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "identity-obj-proxy", "^.+\\.(css|less|scss)$": "identity-obj-proxy", "^.*i18n.*\\.(json)$": "/tests/i18n_mock.json", @@ -118,6 +121,9 @@ "setupFiles": [ "jest-canvas-mock" ], + "setupFilesAfterEnv": [ + "/tests/setup.tsx" + ], "testURL": "http://localhost:8065" } } diff --git a/webapp/src/components/github_repo_selector/github_repo_selector.jsx b/webapp/src/components/github_repo_selector/github_repo_selector.jsx index f6b72a8b6..c69eee4b1 100644 --- a/webapp/src/components/github_repo_selector/github_repo_selector.jsx +++ b/webapp/src/components/github_repo_selector/github_repo_selector.jsx @@ -48,7 +48,7 @@ export default class GithubRepoSelector extends PureComponent { } render() { - const repoOptions = this.props.yourRepos.repos.map((item) => ({value: item.full_name, label: item.full_name})); + const repoOptions = this.props.yourRepos.repos ? this.props.yourRepos.repos.map((item) => ({value: item.full_name, label: item.full_name})) : []; return (
diff --git a/webapp/src/components/modals/create_update_issue/__snapshots__/create_update_issue.test.jsx.snap b/webapp/src/components/modals/create_update_issue/__snapshots__/create_update_issue.test.jsx.snap new file mode 100644 index 000000000..42cb01f85 --- /dev/null +++ b/webapp/src/components/modals/create_update_issue/__snapshots__/create_update_issue.test.jsx.snap @@ -0,0 +1,105 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CreateOrUpdateIssueModal should render correctly with default props 1`] = ` + + + + Create GitHub Issue + + + + +
+ + + +
+
+ + + + Submit + + + +
+`; diff --git a/webapp/src/components/modals/create_update_issue/create_update_issue.test.jsx b/webapp/src/components/modals/create_update_issue/create_update_issue.test.jsx new file mode 100644 index 000000000..bed1bcba0 --- /dev/null +++ b/webapp/src/components/modals/create_update_issue/create_update_issue.test.jsx @@ -0,0 +1,116 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {shallow} from 'enzyme'; + +import CreateOrUpdateIssueModal from './create_update_issue'; + +jest.mock('utils/user_utils', () => ({ + getErrorMessage: jest.fn(() => 'Error occurred'), +})); + +describe('CreateOrUpdateIssueModal', () => { + const defaultProps = { + close: jest.fn(), + create: jest.fn(() => Promise.resolve({})), + update: jest.fn(), + getIssueInfo: jest.fn(), + post: null, + theme: { + centerChannelColor: '#000', + centerChannelBg: '#fff', + }, + visible: true, + }; + + + it('should render correctly with default props', () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); + + it('should call close prop when handleClose is called', () => { + const wrapper = shallow(); + wrapper.instance().handleClose(); + expect(defaultProps.close).toHaveBeenCalled(); + }); + + it('should display error message when create returns an error', async () => { + const mockCreateFunction = jest.fn().mockResolvedValue({error: {message: 'Some error'}}); + + const errorProps = { + ...defaultProps, + create: mockCreateFunction, + messageData: { + repo_owner: 'owner', + repo_name: 'repo', + }, + }; + + const wrapper = shallow(); + wrapper.setState({issueTitle: 'Test Issue'}); + + await wrapper.instance().handleCreateOrUpdate({preventDefault: jest.fn()}); + wrapper.update(); + + expect(wrapper.find('.help-text.error-text').text()).toEqual('Error occurred'); + }); + + it('should show validation error when issueTitle is empty', async () => { + const wrapper = shallow(); + wrapper.setState({ issueTitle: '' }); + + await wrapper.instance().handleCreateOrUpdate({ preventDefault: jest.fn() }); + + expect(wrapper.state('issueTitleValid')).toBe(false); + expect(wrapper.state('showErrors')).toBe(true); + }); + + it('should update repo state when handleRepoChange is called', () => { + const wrapper = shallow(); + const repo = {name: 'repo-name'}; + + wrapper.instance().handleRepoChange(repo); + expect(wrapper.state('repo')).toEqual(repo); + }); + + it('should update labels state when handleLabelsChange is called', () => { + const wrapper = shallow(); + const labels = ['label1', 'label2']; + + wrapper.instance().handleLabelsChange(labels); + expect(wrapper.state('labels')).toEqual(labels); + }); + + it('should update assignees state when handleAssigneesChange is called', () => { + const wrapper = shallow(); + const assignees = ['user1', 'user2']; + + wrapper.instance().handleAssigneesChange(assignees); + expect(wrapper.state('assignees')).toEqual(assignees); + }); + + it('should set issueDescription state when post prop is updated', () => { + const wrapper = shallow(); + const post = {message: 'test post'}; + wrapper.setProps({post}); + + expect(wrapper.state('issueDescription')).toEqual(post.message); + }); + + it('should not display attribute selectors when repo does not have push permissions', () => { + const wrapper = shallow(); + wrapper.setState({repo: {name: 'repo-name', permissions: {push: false}}}); + + expect(wrapper.instance().renderIssueAttributeSelectors()).toBeNull(); + }); + + it('should display attribute selectors when repo has push permissions', () => { + const wrapper = shallow(); + wrapper.setState({repo: {name: 'repo-name', permissions: {push: true}}}); + + const selectors = wrapper.instance().renderIssueAttributeSelectors(); + expect(selectors).not.toBeNull(); + }); +}); diff --git a/webapp/tests/setup.tsx b/webapp/tests/setup.tsx new file mode 100644 index 000000000..87151a3a4 --- /dev/null +++ b/webapp/tests/setup.tsx @@ -0,0 +1,7 @@ +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import Adapter from 'enzyme-adapter-react-16'; +import {configure} from 'enzyme'; + +configure({adapter: new Adapter()}); From 762a3530e7e9dca734bacd0f697c3e86665d98ee Mon Sep 17 00:00:00 2001 From: kshitij katiyar Date: Mon, 21 Apr 2025 15:52:04 +0530 Subject: [PATCH 13/18] updated snapshot --- .../__snapshots__/create_update_issue.test.jsx.snap | 1 - 1 file changed, 1 deletion(-) diff --git a/webapp/src/components/modals/create_update_issue/__snapshots__/create_update_issue.test.jsx.snap b/webapp/src/components/modals/create_update_issue/__snapshots__/create_update_issue.test.jsx.snap index 42cb01f85..d0f6e2a35 100644 --- a/webapp/src/components/modals/create_update_issue/__snapshots__/create_update_issue.test.jsx.snap +++ b/webapp/src/components/modals/create_update_issue/__snapshots__/create_update_issue.test.jsx.snap @@ -57,7 +57,6 @@ exports[`CreateOrUpdateIssueModal should render correctly with default props 1`] value={null} /> Date: Mon, 21 Apr 2025 16:07:00 +0530 Subject: [PATCH 14/18] fixed lint --- .../create_update_issue.test.jsx | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/webapp/src/components/modals/create_update_issue/create_update_issue.test.jsx b/webapp/src/components/modals/create_update_issue/create_update_issue.test.jsx index bed1bcba0..5b90f16c5 100644 --- a/webapp/src/components/modals/create_update_issue/create_update_issue.test.jsx +++ b/webapp/src/components/modals/create_update_issue/create_update_issue.test.jsx @@ -23,7 +23,6 @@ describe('CreateOrUpdateIssueModal', () => { }, visible: true, }; - it('should render correctly with default props', () => { const wrapper = shallow(); @@ -38,7 +37,7 @@ describe('CreateOrUpdateIssueModal', () => { it('should display error message when create returns an error', async () => { const mockCreateFunction = jest.fn().mockResolvedValue({error: {message: 'Some error'}}); - + const errorProps = { ...defaultProps, create: mockCreateFunction, @@ -47,22 +46,22 @@ describe('CreateOrUpdateIssueModal', () => { repo_name: 'repo', }, }; - + const wrapper = shallow(); wrapper.setState({issueTitle: 'Test Issue'}); - + await wrapper.instance().handleCreateOrUpdate({preventDefault: jest.fn()}); wrapper.update(); - + expect(wrapper.find('.help-text.error-text').text()).toEqual('Error occurred'); - }); + }); it('should show validation error when issueTitle is empty', async () => { - const wrapper = shallow(); - wrapper.setState({ issueTitle: '' }); - - await wrapper.instance().handleCreateOrUpdate({ preventDefault: jest.fn() }); - + const wrapper = shallow(); + wrapper.setState({issueTitle: ''}); + + await wrapper.instance().handleCreateOrUpdate({preventDefault: jest.fn()}); + expect(wrapper.state('issueTitleValid')).toBe(false); expect(wrapper.state('showErrors')).toBe(true); }); From fc3e0c0620abed26483985ee82edc18a9207a505 Mon Sep 17 00:00:00 2001 From: kshitij katiyar Date: Mon, 21 Apr 2025 18:57:11 +0530 Subject: [PATCH 15/18] convert comment custom post to interactive dialog --- server/plugin/api.go | 182 +++++++++++++++++++++++++++---------------- 1 file changed, 115 insertions(+), 67 deletions(-) diff --git a/server/plugin/api.go b/server/plugin/api.go index 394afd3ec..9a44e24a6 100644 --- a/server/plugin/api.go +++ b/server/plugin/api.go @@ -112,7 +112,7 @@ const ( KeyIssueID string = "issue_id" KeyStatus string = "status" KeyChannelID string = "channel_id" - KeyPostID string = "postId" + KeyPostID string = "post_id" WebsocketEventOpenCommentModal string = "open_comment_modal" WebsocketEventOpenStatusModal string = "open_status_modal" @@ -121,6 +121,8 @@ const ( PathOpenIssueCommentModal string = "/open-comment-modal" PathOpenIssueEditModal string = "/open-edit-modal" PathOpenIssueStatusModal string = "/open-status-modal" + + PathHandleOpenIssueCommentModal string = "/submit_issue_comment_dialog" ) func (p *Plugin) writeJSON(w http.ResponseWriter, v interface{}) { @@ -190,6 +192,7 @@ func (p *Plugin) initializeAPI() { apiRouter.HandleFunc(PathOpenIssueCommentModal, p.checkAuth(p.attachUserContext(p.handleOpenIssueCommentModal), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc(PathOpenIssueEditModal, p.checkAuth(p.attachUserContext(p.handleOpenEditIssueModal), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc(PathOpenIssueStatusModal, p.checkAuth(p.attachUserContext(p.handleOpenIssueStatusModal), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc(PathHandleOpenIssueCommentModal, p.checkAuth(p.attachUserContext(p.handleSubmitIssueCommentDialog), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc("/config", checkPluginRequest(p.getConfig)).Methods(http.MethodGet) apiRouter.HandleFunc("/token", checkPluginRequest(p.getToken)).Methods(http.MethodGet) @@ -908,71 +911,55 @@ func getFailReason(code int, repo string, username string) string { return cause } -func (p *Plugin) createIssueComment(c *UserContext, w http.ResponseWriter, r *http.Request) { - type CreateIssueCommentRequest struct { - PostID string `json:"post_id"` - Owner string `json:"owner"` - Repo string `json:"repo"` - Number int `json:"number"` - Comment string `json:"comment"` - ShowAttachedMessage bool `json:"show_attached_message"` - } +type CreateIssueCommentRequest struct { + PostID string `json:"post_id"` + Owner string `json:"owner"` + Repo string `json:"repo"` + Number int `json:"number"` + Comment string `json:"comment"` + ShowAttachedMessage bool `json:"show_attached_message"` +} +func (p *Plugin) createIssueComment(c *UserContext, w http.ResponseWriter, r *http.Request) { req := &CreateIssueCommentRequest{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { c.Log.WithError(err).Warnf("Error decoding CreateIssueCommentRequest JSON body") - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) - return - } - - if req.PostID == "" { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid post id", StatusCode: http.StatusBadRequest}) return } - if req.Owner == "" { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid repository owner.", StatusCode: http.StatusBadRequest}) - return - } - - if req.Repo == "" { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid repository.", StatusCode: http.StatusBadRequest}) + result, statusCode, err := p.handleCreateIssueComment(c, req) + if err != nil { + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: err.Error(), StatusCode: statusCode}) return } - if req.Number == 0 { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid issue number.", StatusCode: http.StatusBadRequest}) - return - } + p.writeJSON(w, result) +} - if req.Comment == "" { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid non empty comment.", StatusCode: http.StatusBadRequest}) - return +func (p *Plugin) handleCreateIssueComment(c *UserContext, req *CreateIssueCommentRequest) (*github.IssueComment, int, error) { + if req.PostID == "" || req.Owner == "" || req.Repo == "" || req.Number == 0 || req.Comment == "" { + p.client.Log.Error("Error creating comment on issue", "missing request fields") + return nil, http.StatusBadRequest, errors.New("invalid request fields") } - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) post, err := p.client.Post.GetPost(req.PostID) - if err != nil { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", req.PostID), StatusCode: http.StatusInternalServerError}) - return - } - if post == nil { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", req.PostID), StatusCode: http.StatusNotFound}) - return + if err != nil || post == nil { + p.client.Log.Error("Error getting Post ID", "postID", req.PostID) + return nil, http.StatusInternalServerError, fmt.Errorf("failed to load post: %w", err) } commentUsername, err := p.getUsername(post.UserId) if err != nil { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) - return + p.client.Log.Error("Error getting username", "UserID", post.UserId, "error", err.Error()) + return nil, http.StatusInternalServerError, fmt.Errorf("failed to get username: %w", err) } currentUsername := c.GHInfo.GitHubUsername permalink, err := p.getPermaLink(req.PostID) if err != nil { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to generate permalink", StatusCode: http.StatusInternalServerError}) - return + p.client.Log.Error("Error getting permalink for the post", "PostID", req.PostID, "error", err.Error()) + return nil, http.StatusInternalServerError, fmt.Errorf("failed to generate permalink: %w", err) } permalinkMessage := fmt.Sprintf("*@%s attached a* [message](%s) *from %s*\n\n", currentUsername, permalink, commentUsername) @@ -987,10 +974,7 @@ func (p *Plugin) createIssueComment(c *UserContext, w http.ResponseWriter, r *ht var rawResponse *github.Response if cErr := p.useGitHubClient(c.GHInfo, func(info *GitHubUserInfo, token *oauth2.Token) error { result, rawResponse, err = githubClient.Issues.CreateComment(c.Ctx, req.Owner, req.Repo, req.Number, comment) - if err != nil { - return err - } - return nil + return err }); cErr != nil { statusCode := 500 if rawResponse != nil { @@ -1001,8 +985,7 @@ func (p *Plugin) createIssueComment(c *UserContext, w http.ResponseWriter, r *ht "repo": req.Repo, "number": req.Number, }).Errorf("failed to create an issue comment") - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to create an issue comment: " + getFailReason(statusCode, req.Repo, currentUsername), StatusCode: statusCode}) - return + return nil, statusCode, fmt.Errorf("GitHub comment creation failed: %w", err) } rootID := req.PostID @@ -1023,13 +1006,48 @@ func (p *Plugin) createIssueComment(c *UserContext, w http.ResponseWriter, r *ht UserId: c.UserID, } - err = p.client.Post.CreatePost(reply) + if err := p.client.Post.CreatePost(reply); err != nil { + p.client.Log.Error("Error creating post for issue comment", "error", err.Error()) + return nil, http.StatusInternalServerError, fmt.Errorf("failed to create reply post: %w", err) + } + + return result, http.StatusOK, nil +} + +func (p *Plugin) handleSubmitIssueCommentDialog(c *UserContext, w http.ResponseWriter, r *http.Request) { + var submission model.SubmitDialogRequest + if err := json.NewDecoder(r.Body).Decode(&submission); err != nil { + p.client.Log.Error("Error decoding the request body for submit issue comment dialog handling", "error", err.Error()) + http.Error(w, "invalid payload", http.StatusBadRequest) + return + } + + state := make(map[string]string) + if err := json.Unmarshal([]byte(submission.State), &state); err != nil { + p.client.Log.Error("Error unmarshalling state for submit issue comment dialog handling", "error", err.Error()) + http.Error(w, "invalid state", http.StatusBadRequest) + return + } + + comment := submission.Submission["comment"].(string) + issueNumber, _ := strconv.Atoi(state["issue_number"]) + + req := &CreateIssueCommentRequest{ + PostID: state[KeyPostID], + Owner: state[KeyRepoOwner], + Repo: state[KeyRepoName], + Number: issueNumber, + Comment: comment, + ShowAttachedMessage: false, + } + + _, status, err := p.handleCreateIssueComment(c, req) if err != nil { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create the notification post %s", req.PostID), StatusCode: http.StatusInternalServerError}) + http.Error(w, err.Error(), status) return } - p.writeJSON(w, result) + w.WriteHeader(http.StatusOK) } func (p *Plugin) getLHSData(c *UserContext) (reviewResp []*graphql.GithubPRDetails, assignmentResp []*github.Issue, openPRResp []*graphql.GithubPRDetails, err error) { @@ -1951,29 +1969,59 @@ func (p *Plugin) handleOpenIssueStatusModal(c *UserContext, w http.ResponseWrite } func (p *Plugin) handleOpenIssueCommentModal(c *UserContext, w http.ResponseWriter, r *http.Request) { - response := &model.PostActionIntegrationResponse{} decoder := json.NewDecoder(r.Body) postActionIntegrationRequest := &model.PostActionIntegrationRequest{} if err := decoder.Decode(&postActionIntegrationRequest); err != nil { p.API.LogError("Error decoding PostActionIntegrationRequest params", "Error", err.Error()) - p.returnPostActionIntegrationResponse(w, response) - return + p.returnPostActionIntegrationResponse(w, &model.PostActionIntegrationResponse{}) + return + } + + triggerID := postActionIntegrationRequest.TriggerId + if triggerID == "" { + p.API.LogError("Trigger ID missing in request") + p.returnPostActionIntegrationResponse(w, &model.PostActionIntegrationResponse{}) + return + } + + dialog := model.OpenDialogRequest{ + TriggerId: triggerID, + URL: fmt.Sprintf("%s%s", p.GetPluginAPIPath(), PathHandleOpenIssueCommentModal), + Dialog: model.Dialog{ + CallbackId: "create_issue_comment", + Title: "Create a comment to GitHub Issue", + SubmitLabel: "Attach", + NotifyOnCancel: false, + Elements: []model.DialogElement{ + { + DisplayName: "Create a comment", + Name: "comment", + Type: "textarea", + Placeholder: "Enter your comment here...", + Optional: false, + }, + }, + State: toJSON(map[string]string{ + KeyRepoOwner: postActionIntegrationRequest.Context[KeyRepoOwner].(string), + KeyRepoName: postActionIntegrationRequest.Context[KeyRepoName].(string), + KeyIssueNumber: fmt.Sprintf("%v", postActionIntegrationRequest.Context[KeyIssueNumber]), + KeyPostID: postActionIntegrationRequest.PostId, + }), + }, } - p.client.Frontend.PublishWebSocketEvent( - WebsocketEventOpenCommentModal, - map[string]interface{}{ - KeyRepoName: postActionIntegrationRequest.Context[KeyRepoName], - KeyRepoOwner: postActionIntegrationRequest.Context[KeyRepoOwner], - KeyIssueNumber: postActionIntegrationRequest.Context[KeyIssueNumber], - KeyPostID: postActionIntegrationRequest.PostId, - KeyStatus: postActionIntegrationRequest.Context[KeyStatus], - KeyChannelID: postActionIntegrationRequest.ChannelId, - }, - &model.WebsocketBroadcast{UserId: postActionIntegrationRequest.UserId}, - ) + if appErr := p.API.OpenInteractiveDialog(dialog); appErr != nil { + p.API.LogError("Failed to open interactive dialog", "Error", appErr.Error()) + } - p.returnPostActionIntegrationResponse(w, response) + p.API.LogInfo("Dialog Title", "Title", dialog.Dialog.Title) + + p.returnPostActionIntegrationResponse(w, &model.PostActionIntegrationResponse{}) +} + +func toJSON(v interface{}) string { + b, _ := json.Marshal(v) + return string(b) } // parseRepo parses the owner & repository name from the repo query parameter From a235186d4f4db42ce4ad3f352fa6d5bb007996cc Mon Sep 17 00:00:00 2001 From: kshitij katiyar Date: Fri, 25 Apr 2025 16:11:09 +0530 Subject: [PATCH 16/18] convert close/reopen issue custom modal to interactive dialog --- server/plugin/api.go | 211 +++++++++++++++++++++++++++++++++---------- 1 file changed, 162 insertions(+), 49 deletions(-) diff --git a/server/plugin/api.go b/server/plugin/api.go index 9a44e24a6..c688109f4 100644 --- a/server/plugin/api.go +++ b/server/plugin/api.go @@ -106,13 +106,17 @@ const ( // ResponseTypePlain indicates that response type is text plain ResponseTypePlain ResponseType = "TEXT_RESPONSE" - KeyRepoName string = "repo_name" - KeyRepoOwner string = "repo_owner" - KeyIssueNumber string = "issue_number" - KeyIssueID string = "issue_id" - KeyStatus string = "status" - KeyChannelID string = "channel_id" - KeyPostID string = "post_id" + KeyRepoName string = "repo_name" + KeyRepoOwner string = "repo_owner" + KeyIssueNumber string = "issue_number" + KeyIssueID string = "issue_id" + KeyIssueComment string = "issue_comment" + KeyStatus string = "status" + KeyStatusReason string = "status_reason" + KeyChannelID string = "channel_id" + KeyPostID string = "post_id" + + IssueOpen string = "open" WebsocketEventOpenCommentModal string = "open_comment_modal" WebsocketEventOpenStatusModal string = "open_status_modal" @@ -122,7 +126,8 @@ const ( PathOpenIssueEditModal string = "/open-edit-modal" PathOpenIssueStatusModal string = "/open-status-modal" - PathHandleOpenIssueCommentModal string = "/submit_issue_comment_dialog" + PathHandleOpenIssueCommentModal string = "/submit_issue_comment_dialog" + PathHandleOpenIssueCloseReopenModal string = "/submit_issue_close_reopen_dialog" ) func (p *Plugin) writeJSON(w http.ResponseWriter, v interface{}) { @@ -193,6 +198,7 @@ func (p *Plugin) initializeAPI() { apiRouter.HandleFunc(PathOpenIssueEditModal, p.checkAuth(p.attachUserContext(p.handleOpenEditIssueModal), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc(PathOpenIssueStatusModal, p.checkAuth(p.attachUserContext(p.handleOpenIssueStatusModal), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc(PathHandleOpenIssueCommentModal, p.checkAuth(p.attachUserContext(p.handleSubmitIssueCommentDialog), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc(PathHandleOpenIssueCloseReopenModal, p.checkAuth(p.attachUserContext(p.handleSubmitIssueCloseReopenDialog), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc("/config", checkPluginRequest(p.getConfig)).Methods(http.MethodGet) apiRouter.HandleFunc("/token", checkPluginRequest(p.getToken)).Methods(http.MethodGet) @@ -921,13 +927,13 @@ type CreateIssueCommentRequest struct { } func (p *Plugin) createIssueComment(c *UserContext, w http.ResponseWriter, r *http.Request) { - req := &CreateIssueCommentRequest{} - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + createIssueCommentRequest := &CreateIssueCommentRequest{} + if err := json.NewDecoder(r.Body).Decode(&createIssueCommentRequest); err != nil { c.Log.WithError(err).Warnf("Error decoding CreateIssueCommentRequest JSON body") return } - result, statusCode, err := p.handleCreateIssueComment(c, req) + result, statusCode, err := p.handleCreateIssueComment(c, createIssueCommentRequest) if err != nil { p.writeAPIError(w, &APIErrorResponse{ID: "", Message: err.Error(), StatusCode: statusCode}) return @@ -1050,6 +1056,47 @@ func (p *Plugin) handleSubmitIssueCommentDialog(c *UserContext, w http.ResponseW w.WriteHeader(http.StatusOK) } +func (p *Plugin) handleSubmitIssueCloseReopenDialog(c *UserContext, w http.ResponseWriter, r *http.Request) { + var submission model.SubmitDialogRequest + if err := json.NewDecoder(r.Body).Decode(&submission); err != nil { + p.client.Log.Error("Error decoding the request body for submit issue close/reopen dialog", "error", err.Error()) + http.Error(w, "invalid payload", http.StatusBadRequest) + return + } + + state := make(map[string]string) + if err := json.Unmarshal([]byte(submission.State), &state); err != nil { + p.client.Log.Error("Error unmarshalling state for issue close/reopen dialog", "error", err.Error()) + http.Error(w, "invalid state", http.StatusBadRequest) + return + } + + issueNumber, _ := strconv.Atoi(state[KeyIssueNumber]) + + req := &CommentAndCloseRequest{ + PostID: state[KeyPostID], + ChannelID: state[KeyChannelID], + Owner: state[KeyRepoOwner], + Repository: state[KeyRepoName], + Number: issueNumber, + Status: state[KeyStatus], + IssueComment: submission.Submission[KeyIssueComment].(string), + } + + if req.Status == IssueOpen { + req.StatusReason = submission.Submission[KeyStatusReason].(string) + } else { + req.StatusReason = state[KeyStatusReason] + } + + if req.ChannelID == "" || req.Owner == "" || req.Repository == "" || req.Number == 0 || req.PostID == "" { + http.Error(w, "missing required issue data", http.StatusBadRequest) + return + } + + p.handleCloseOrReopenIssue(c, w, req) +} + func (p *Plugin) getLHSData(c *UserContext) (reviewResp []*graphql.GithubPRDetails, assignmentResp []*github.Issue, openPRResp []*graphql.GithubPRDetails, err error) { graphQLClient := p.graphQLConnect(c.GHInfo) @@ -1686,33 +1733,38 @@ func (p *Plugin) updateIssue(c *UserContext, w http.ResponseWriter, r *http.Requ p.writeJSON(w, result) } +type CommentAndCloseRequest struct { + ChannelID string `json:"channel_id"` + IssueComment string `json:"issue_comment"` + StatusReason string `json:"status_reason"` + Number int `json:"number"` + Owner string `json:"owner"` + Repository string `json:"repo"` + Status string `json:"status"` + PostID string `json:"postId"` +} + func (p *Plugin) closeOrReopenIssue(c *UserContext, w http.ResponseWriter, r *http.Request) { - type CommentAndCloseRequest struct { - ChannelID string `json:"channel_id"` - IssueComment string `json:"issue_comment"` - StatusReason string `json:"status_reason"` - Number int `json:"number"` - Owner string `json:"owner"` - Repository string `json:"repo"` - Status string `json:"status"` - PostID string `json:"postId"` - } - - req := &CommentAndCloseRequest{} - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + closeReopenIssueRequest := &CommentAndCloseRequest{} + if err := json.NewDecoder(r.Body).Decode(&closeReopenIssueRequest); err != nil { c.Log.WithError(err).Warnf("Error decoding the JSON body") p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid JSON object.", StatusCode: http.StatusBadRequest}) return } - post, appErr := p.API.GetPost(req.PostID) + p.handleCloseOrReopenIssue(c, w, closeReopenIssueRequest) +} + +func (p *Plugin) handleCloseOrReopenIssue(c *UserContext, w http.ResponseWriter, closeReopenIssueRequest *CommentAndCloseRequest) { + post, appErr := p.API.GetPost(closeReopenIssueRequest.PostID) if appErr != nil { - p.client.Log.Error("Unable to get the post", "PostID", req.PostID, "Error", appErr.Error()) - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", req.PostID), StatusCode: http.StatusInternalServerError}) + p.client.Log.Error("Unable to get the post", "PostID", closeReopenIssueRequest.PostID, "Error", appErr.Error()) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", closeReopenIssueRequest.PostID), StatusCode: http.StatusInternalServerError}) return } + if post == nil { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", req.PostID), StatusCode: http.StatusNotFound}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", closeReopenIssueRequest.PostID), StatusCode: http.StatusNotFound}) return } @@ -1721,15 +1773,17 @@ func (p *Plugin) closeOrReopenIssue(c *UserContext, w http.ResponseWriter, r *ht p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) return } - if req.IssueComment != "" { - p.CreateCommentToIssue(c, w, req.IssueComment, req.Owner, req.Repository, post, req.Number) + + if closeReopenIssueRequest.IssueComment != "" { + p.CreateCommentToIssue(c, w, closeReopenIssueRequest.IssueComment, closeReopenIssueRequest.Owner, closeReopenIssueRequest.Repository, post, closeReopenIssueRequest.Number) } - if req.Status == statusClose { - p.CloseOrReopenIssue(c, w, issueClose, req.StatusReason, req.Owner, req.Repository, post, req.Number) - } else { - p.CloseOrReopenIssue(c, w, issueOpen, req.StatusReason, req.Owner, req.Repository, post, req.Number) + status := issueOpen + if closeReopenIssueRequest.Status == IssueOpen { + status = issueClose } + + p.CloseOrReopenIssue(c, w, status, closeReopenIssueRequest.StatusReason, closeReopenIssueRequest.Owner, closeReopenIssueRequest.Repository, post, closeReopenIssueRequest.Number) } func (p *Plugin) createIssue(c *UserContext, w http.ResponseWriter, r *http.Request) { @@ -1943,29 +1997,88 @@ func (p *Plugin) returnPostActionIntegrationResponse(w http.ResponseWriter, res } func (p *Plugin) handleOpenIssueStatusModal(c *UserContext, w http.ResponseWriter, r *http.Request) { - response := &model.PostActionIntegrationResponse{} decoder := json.NewDecoder(r.Body) postActionIntegrationRequest := &model.PostActionIntegrationRequest{} - if err := decoder.Decode(&postActionIntegrationRequest); err != nil { + if err := decoder.Decode(postActionIntegrationRequest); err != nil { p.API.LogError("Error decoding PostActionIntegrationRequest params", "Error", err.Error()) - p.returnPostActionIntegrationResponse(w, response) + p.returnPostActionIntegrationResponse(w, &model.PostActionIntegrationResponse{}) return } - p.client.Frontend.PublishWebSocketEvent( - WebsocketEventOpenStatusModal, - map[string]interface{}{ - KeyRepoName: postActionIntegrationRequest.Context[KeyRepoName], - KeyRepoOwner: postActionIntegrationRequest.Context[KeyRepoOwner], - KeyIssueNumber: postActionIntegrationRequest.Context[KeyIssueNumber], - KeyPostID: postActionIntegrationRequest.PostId, - KeyStatus: postActionIntegrationRequest.Context[KeyStatus], - KeyChannelID: postActionIntegrationRequest.ChannelId, + triggerID := postActionIntegrationRequest.TriggerId + if triggerID == "" { + p.API.LogError("Trigger ID missing in request") + p.returnPostActionIntegrationResponse(w, &model.PostActionIntegrationResponse{}) + return + } + + repoOwner := postActionIntegrationRequest.Context[KeyRepoOwner].(string) + repoName := postActionIntegrationRequest.Context[KeyRepoName].(string) + issueNumber := fmt.Sprintf("%v", postActionIntegrationRequest.Context[KeyIssueNumber]) + postID := postActionIntegrationRequest.PostId + channelID := postActionIntegrationRequest.ChannelId + status := postActionIntegrationRequest.Context[KeyStatus].(string) + + dialogTitle := "Reopen Issue" + submitLabel := "Reopen Issue" + elements := []model.DialogElement{ + { + DisplayName: "Leave a comment", + Name: KeyIssueComment, + Type: "textarea", + Placeholder: "Add a comment...", + Optional: true, }, - &model.WebsocketBroadcast{UserId: postActionIntegrationRequest.UserId}, - ) + } - p.returnPostActionIntegrationResponse(w, response) + stateMap := map[string]string{ + KeyChannelID: channelID, + KeyStatus: status, + KeyIssueNumber: issueNumber, + KeyRepoOwner: repoOwner, + KeyRepoName: repoName, + KeyPostID: postID, + } + + if status == IssueOpen { + status = statusClose + dialogTitle = "Close Issue" + submitLabel = "Close Issue" + + elements = append(elements, model.DialogElement{ + DisplayName: "Mark issue as", + Name: KeyStatusReason, + Type: "radio", + Optional: false, + Options: []*model.PostActionOptions{ + {Text: "Completed", Value: "completed"}, + {Text: "Not planned", Value: "not_planned"}, + }, + }) + } else { + stateMap[KeyStatusReason] = "reopened" + } + + state := toJSON(stateMap) + + dialog := model.OpenDialogRequest{ + TriggerId: triggerID, + URL: fmt.Sprintf("%s%s", p.GetPluginAPIPath(), PathHandleOpenIssueCloseReopenModal), + Dialog: model.Dialog{ + CallbackId: "close_or_reopen_issue", + Title: dialogTitle, + SubmitLabel: submitLabel, + NotifyOnCancel: false, + Elements: elements, + State: state, + }, + } + + if appErr := p.API.OpenInteractiveDialog(dialog); appErr != nil { + p.API.LogError("Failed to open interactive dialog", "Error", appErr.Error()) + } + + p.returnPostActionIntegrationResponse(w, &model.PostActionIntegrationResponse{}) } func (p *Plugin) handleOpenIssueCommentModal(c *UserContext, w http.ResponseWriter, r *http.Request) { From ea9e9efb2689f749990bb16096c29c8199cbfab6 Mon Sep 17 00:00:00 2001 From: kshitij katiyar Date: Tue, 29 Apr 2025 19:46:18 +0530 Subject: [PATCH 17/18] convert edit issue custom modal to interactive dialog --- server/plugin/api.go | 498 ++++++++++++++++++++++++++++++------------- 1 file changed, 349 insertions(+), 149 deletions(-) diff --git a/server/plugin/api.go b/server/plugin/api.go index c688109f4..484794cf0 100644 --- a/server/plugin/api.go +++ b/server/plugin/api.go @@ -115,6 +115,11 @@ const ( KeyStatusReason string = "status_reason" KeyChannelID string = "channel_id" KeyPostID string = "post_id" + KeyTitle string = "title" + KeyAssignees string = "assignees" + KeyLabels string = "labels" + KeyDescription string = "description" + KeyRepoFullName string = "repo_full_name" IssueOpen string = "open" @@ -128,6 +133,7 @@ const ( PathHandleOpenIssueCommentModal string = "/submit_issue_comment_dialog" PathHandleOpenIssueCloseReopenModal string = "/submit_issue_close_reopen_dialog" + PathHandleOpenIssueEditModal string = "/submit_issue_edit_dialog" ) func (p *Plugin) writeJSON(w http.ResponseWriter, v interface{}) { @@ -199,6 +205,7 @@ func (p *Plugin) initializeAPI() { apiRouter.HandleFunc(PathOpenIssueStatusModal, p.checkAuth(p.attachUserContext(p.handleOpenIssueStatusModal), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc(PathHandleOpenIssueCommentModal, p.checkAuth(p.attachUserContext(p.handleSubmitIssueCommentDialog), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc(PathHandleOpenIssueCloseReopenModal, p.checkAuth(p.attachUserContext(p.handleSubmitIssueCloseReopenDialog), ResponseTypePlain)).Methods(http.MethodPost) + apiRouter.HandleFunc(PathHandleOpenIssueEditModal, p.checkAuth(p.attachUserContext(p.handleSubmitIssueEditDialog), ResponseTypePlain)).Methods(http.MethodPost) apiRouter.HandleFunc("/config", checkPluginRequest(p.getConfig)).Methods(http.MethodGet) apiRouter.HandleFunc("/token", checkPluginRequest(p.getToken)).Methods(http.MethodGet) @@ -1097,6 +1104,57 @@ func (p *Plugin) handleSubmitIssueCloseReopenDialog(c *UserContext, w http.Respo p.handleCloseOrReopenIssue(c, w, req) } +func (p *Plugin) handleSubmitIssueEditDialog(c *UserContext, w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + + var submission model.SubmitDialogRequest + if err := json.NewDecoder(r.Body).Decode(&submission); err != nil { + c.Log.WithError(err).Warnf("Failed to decode SubmitDialogRequest") + http.Error(w, "Invalid request", http.StatusBadRequest) + return + } + + var state map[string]interface{} + if err := json.Unmarshal([]byte(submission.State), &state); err != nil { + c.Log.WithError(err).Warnf("Failed to decode dialog state") + http.Error(w, "Invalid state", http.StatusBadRequest) + return + } + + issueNumberStr := fmt.Sprintf("%v", state[KeyIssueNumber]) + issueNumber, err := strconv.Atoi(issueNumberStr) + if err != nil { + c.Log.WithError(err).Warnf("Invalid issue number in state") + http.Error(w, "Invalid issue number", http.StatusBadRequest) + return + } + + milestone := 0 + if v, ok := submission.Submission["milestone"].(string); ok && v != "" { + milestone, _ = strconv.Atoi(v) + } + + issue := &UpdateIssueRequest{ + Title: submission.Submission["Title"].(string), + Body: submission.Submission["Description"].(string), + Repo: fmt.Sprintf("%v/%v", state[KeyRepoOwner], state[KeyRepoName]), + PostID: fmt.Sprintf("%v", state[KeyPostID]), + ChannelID: fmt.Sprintf("%v", state[KeyChannelID]), + Labels: []string{submission.Submission["Label"].(string)}, + Assignees: []string{submission.Submission["Assignee"].(string)}, + Milestone: milestone, + IssueNumber: issueNumber, + } + + if _, err := p.updateGithubIssue(c, issue); err != nil { + c.Log.WithError(err).Warnf("Failed to update issue") + http.Error(w, "Failed to update issue", http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) +} + func (p *Plugin) getLHSData(c *UserContext) (reviewResp []*graphql.GithubPRDetails, assignmentResp []*github.Issue, openPRResp []*graphql.GithubPRDetails, err error) { graphQLClient := p.graphQLConnect(c.GHInfo) @@ -1178,23 +1236,11 @@ func (p *Plugin) updateSettings(c *UserContext, w http.ResponseWriter, r *http.R p.writeJSON(w, info.Settings) } -func (p *Plugin) getIssueInfo(c *UserContext, w http.ResponseWriter, r *http.Request) { - owner := r.FormValue(ownerQueryParam) - repo := r.FormValue(repoQueryParam) - number := r.FormValue(numberQueryParam) - postID := r.FormValue(postIDQueryParam) +func (p *Plugin) getGitHubIssueInfo(c *UserContext, owner, repo string, issueNumber int, postID string) (map[string]interface{}, *APIErrorResponse) { + client := p.githubConnectUser(c.Context.Ctx, c.GHInfo) - issueNumber, err := strconv.Atoi(number) + issue, _, err := client.Issues.Get(c.Context.Ctx, owner, repo, issueNumber) if err != nil { - p.writeAPIError(w, &APIErrorResponse{Message: "Invalid param 'number'.", StatusCode: http.StatusBadRequest}) - return - } - - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) - issue, _, err := githubClient.Issues.Get(c.Ctx, owner, repo, issueNumber) - if err != nil { - // If the issue is not found, it probably belongs to a private repo. - // Return an empty response in that case. var gerr *github.ErrorResponse if errors.As(err, &gerr) && gerr.Response.StatusCode == http.StatusNotFound { c.Log.WithError(err).With(logger.LogContext{ @@ -1202,8 +1248,7 @@ func (p *Plugin) getIssueInfo(c *UserContext, w http.ResponseWriter, r *http.Req "repo": repo, "number": issueNumber, }).Debugf("Issue not found") - p.writeJSON(w, nil) - return + return nil, nil } c.Log.WithError(err).With(logger.LogContext{ @@ -1211,8 +1256,16 @@ func (p *Plugin) getIssueInfo(c *UserContext, w http.ResponseWriter, r *http.Req "repo": repo, "number": issueNumber, }).Debugf("Could not get the issue") - p.writeAPIError(w, &APIErrorResponse{Message: "Could not get the issue", StatusCode: http.StatusInternalServerError}) - return + return nil, &APIErrorResponse{Message: "Could not get the issue", StatusCode: http.StatusInternalServerError} + } + + post, appErr := p.API.GetPost(postID) + if appErr != nil { + p.client.Log.Error("Unable to get the post", "PostID", postID, "Error", appErr.Error()) + return nil, &APIErrorResponse{Message: fmt.Sprintf("failed to load the post %s", postID), StatusCode: http.StatusInternalServerError} + } + if post == nil { + return nil, &APIErrorResponse{Message: fmt.Sprintf("failed to load the post %s : not found", postID), StatusCode: http.StatusNotFound} } description := "" @@ -1231,34 +1284,47 @@ func (p *Plugin) getIssueInfo(c *UserContext, w http.ResponseWriter, r *http.Req } milestoneTitle := "" - var milestoneNumber int + milestoneNumber := 0 if issue.Milestone != nil && issue.Milestone.Title != nil { milestoneTitle = *issue.Milestone.Title milestoneNumber = *issue.Milestone.Number } - post, appErr := p.API.GetPost(postID) - if appErr != nil { - p.client.Log.Error("Unable to get the post", "PostID", postID, "Error", appErr.Error()) - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", postID), StatusCode: http.StatusInternalServerError}) + return map[string]interface{}{ + KeyTitle: issue.GetTitle(), + KeyChannelID: post.ChannelId, + KeyPostID: postID, + "milestone_title": milestoneTitle, + "milestone_number": milestoneNumber, + KeyAssignees: assignees, + KeyLabels: labels, + KeyDescription: description, + KeyRepoFullName: fmt.Sprintf("%s/%s", owner, repo), + "issue_number": issue.GetNumber(), + }, nil +} + +func (p *Plugin) getIssueInfo(c *UserContext, w http.ResponseWriter, r *http.Request) { + owner := r.FormValue(ownerQueryParam) + repo := r.FormValue(repoQueryParam) + number := r.FormValue(numberQueryParam) + postID := r.FormValue(postIDQueryParam) + + issueNumber, err := strconv.Atoi(number) + if err != nil { + p.writeAPIError(w, &APIErrorResponse{Message: "Invalid param 'number'.", StatusCode: http.StatusBadRequest}) return } - if post == nil { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", postID), StatusCode: http.StatusNotFound}) + + issueInfo, apiErr := p.getGitHubIssueInfo(c, owner, repo, issueNumber, postID) + + if apiErr != nil { + p.writeAPIError(w, apiErr) return } - - issueInfo := map[string]interface{}{ - "title": *issue.Title, - "channel_id": post.ChannelId, - "postId": postID, - "milestone_title": milestoneTitle, - "milestone_number": milestoneNumber, - "assignees": assignees, - "labels": labels, - "description": description, - "repo_full_name": fmt.Sprintf("%s/%s", owner, repo), - "issue_number": *issue.Number, + if issueInfo == nil { + p.writeJSON(w, nil) + return } p.writeJSON(w, issueInfo) @@ -1360,33 +1426,21 @@ func (p *Plugin) getPrByNumber(c *UserContext, w http.ResponseWriter, r *http.Re p.writeJSON(w, result) } -func (p *Plugin) getLabels(c *UserContext, w http.ResponseWriter, r *http.Request) { - owner, repo, err := parseRepo(r.URL.Query().Get("repo")) - if err != nil { - p.writeAPIError(w, &APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) - return - } - - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) +func (p *Plugin) fetchLabels(ctx context.Context, ghInfo *GitHubUserInfo, owner, repo string) ([]*github.Label, error) { + githubClient := p.githubConnectUser(ctx, ghInfo) var allLabels []*github.Label opt := github.ListOptions{PerPage: 50} for { var labels []*github.Label var resp *github.Response - if cErr := p.useGitHubClient(c.GHInfo, func(info *GitHubUserInfo, token *oauth2.Token) error { - labels, resp, err = githubClient.Issues.ListLabels(c.Ctx, owner, repo, &opt) - if err != nil { - return err - } - return nil - }); cErr != nil { - c.Log.WithError(cErr).With(logger.LogContext{ - "owner": owner, - "repo": repo, - }).Warnf("Failed to list labels") - p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch labels", StatusCode: http.StatusInternalServerError}) - return + err := p.useGitHubClient(ghInfo, func(info *GitHubUserInfo, token *oauth2.Token) error { + var e error + labels, resp, e = githubClient.Issues.ListLabels(ctx, owner, repo, &opt) + return e + }) + if err != nil { + return nil, err } allLabels = append(allLabels, labels...) if resp.NextPage == 0 { @@ -1395,36 +1449,24 @@ func (p *Plugin) getLabels(c *UserContext, w http.ResponseWriter, r *http.Reques opt.Page = resp.NextPage } - p.writeJSON(w, allLabels) + return allLabels, nil } -func (p *Plugin) getAssignees(c *UserContext, w http.ResponseWriter, r *http.Request) { - owner, repo, err := parseRepo(r.URL.Query().Get("repo")) - if err != nil { - p.writeAPIError(w, &APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) - return - } - - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) +func (p *Plugin) fetchAssignees(ctx context.Context, ghInfo *GitHubUserInfo, owner, repo string) ([]*github.User, error) { + githubClient := p.githubConnectUser(ctx, ghInfo) var allAssignees []*github.User opt := github.ListOptions{PerPage: 50} for { var assignees []*github.User var resp *github.Response - if cErr := p.useGitHubClient(c.GHInfo, func(info *GitHubUserInfo, token *oauth2.Token) error { - assignees, resp, err = githubClient.Issues.ListAssignees(c.Ctx, owner, repo, &opt) - if err != nil { - return err - } - return nil - }); cErr != nil { - c.Log.WithError(cErr).With(logger.LogContext{ - "owner": owner, - "repo": repo, - }).Warnf("Failed to list assignees") - p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch assignees", StatusCode: http.StatusInternalServerError}) - return + err := p.useGitHubClient(ghInfo, func(info *GitHubUserInfo, token *oauth2.Token) error { + var e error + assignees, resp, e = githubClient.Issues.ListAssignees(ctx, owner, repo, &opt) + return e + }) + if err != nil { + return nil, err } allAssignees = append(allAssignees, assignees...) if resp.NextPage == 0 { @@ -1433,36 +1475,24 @@ func (p *Plugin) getAssignees(c *UserContext, w http.ResponseWriter, r *http.Req opt.Page = resp.NextPage } - p.writeJSON(w, allAssignees) + return allAssignees, nil } -func (p *Plugin) getMilestones(c *UserContext, w http.ResponseWriter, r *http.Request) { - owner, repo, err := parseRepo(r.URL.Query().Get("repo")) - if err != nil { - p.writeAPIError(w, &APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) - return - } - - githubClient := p.githubConnectUser(c.Context.Ctx, c.GHInfo) +func (p *Plugin) fetchMilestones(ctx context.Context, ghInfo *GitHubUserInfo, owner, repo string) ([]*github.Milestone, error) { + githubClient := p.githubConnectUser(ctx, ghInfo) var allMilestones []*github.Milestone opt := github.ListOptions{PerPage: 50} for { var milestones []*github.Milestone var resp *github.Response - if cErr := p.useGitHubClient(c.GHInfo, func(info *GitHubUserInfo, token *oauth2.Token) error { - milestones, resp, err = githubClient.Issues.ListMilestones(c.Ctx, owner, repo, &github.MilestoneListOptions{ListOptions: opt}) - if err != nil { - return err - } - return nil - }); cErr != nil { - c.Log.WithError(cErr).With(logger.LogContext{ - "owner": owner, - "repo": repo, - }).Warnf("Failed to list milestones") - p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch milestones", StatusCode: http.StatusInternalServerError}) - return + err := p.useGitHubClient(ghInfo, func(info *GitHubUserInfo, token *oauth2.Token) error { + var e error + milestones, resp, e = githubClient.Issues.ListMilestones(ctx, owner, repo, &github.MilestoneListOptions{ListOptions: opt}) + return e + }) + if err != nil { + return nil, err } allMilestones = append(allMilestones, milestones...) if resp.NextPage == 0 { @@ -1471,7 +1501,58 @@ func (p *Plugin) getMilestones(c *UserContext, w http.ResponseWriter, r *http.Re opt.Page = resp.NextPage } - p.writeJSON(w, allMilestones) + return allMilestones, nil +} + +func (p *Plugin) getLabels(c *UserContext, w http.ResponseWriter, r *http.Request) { + owner, repo, err := parseRepo(r.URL.Query().Get("repo")) + if err != nil { + p.writeAPIError(w, &APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) + return + } + + labels, err := p.fetchLabels(c.Context.Ctx, c.GHInfo, owner, repo) + if err != nil { + c.Log.WithError(err).Warnf("Failed to list labels") + p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch labels", StatusCode: http.StatusInternalServerError}) + return + } + + p.writeJSON(w, labels) +} + +func (p *Plugin) getAssignees(c *UserContext, w http.ResponseWriter, r *http.Request) { + owner, repo, err := parseRepo(r.URL.Query().Get("repo")) + if err != nil { + p.writeAPIError(w, &APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) + return + } + + assignees, err := p.fetchAssignees(c.Context.Ctx, c.GHInfo, owner, repo) + if err != nil { + c.Log.WithError(err).Warnf("Failed to list assignees") + p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch assignees", StatusCode: http.StatusInternalServerError}) + return + } + + p.writeJSON(w, assignees) +} + +func (p *Plugin) getMilestones(c *UserContext, w http.ResponseWriter, r *http.Request) { + owner, repo, err := parseRepo(r.URL.Query().Get("repo")) + if err != nil { + p.writeAPIError(w, &APIErrorResponse{Message: err.Error(), StatusCode: http.StatusBadRequest}) + return + } + + milestones, err := p.fetchMilestones(c.Context.Ctx, c.GHInfo, owner, repo) + if err != nil { + c.Log.WithError(err).Warnf("Failed to list milestones") + p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch milestones", StatusCode: http.StatusInternalServerError}) + return + } + + p.writeJSON(w, milestones) } func (p *Plugin) getRepositoryList(c context.Context, ghInfo *GitHubUserInfo, userName string, githubClient *github.Client, opt github.ListOptions) ([]*github.Repository, error) { @@ -1627,7 +1708,6 @@ func (p *Plugin) getRepositories(c *UserContext, w http.ResponseWriter, r *http. } func (p *Plugin) updateIssue(c *UserContext, w http.ResponseWriter, r *http.Request) { - // get data for the issue from the request body and fill UpdateIssueRequest to update the issue issue := &UpdateIssueRequest{} if err := json.NewDecoder(r.Body).Decode(&issue); err != nil { c.Log.WithError(err).Warnf("Error decoding the JSON body") @@ -1639,18 +1719,26 @@ func (p *Plugin) updateIssue(c *UserContext, w http.ResponseWriter, r *http.Requ return } + result, apiErr := p.updateGithubIssue(c, issue) + if apiErr != nil { + p.writeAPIError(w, apiErr) + return + } + + p.writeJSON(w, result) +} + +func (p *Plugin) updateGithubIssue(c *UserContext, issue *UpdateIssueRequest) (*github.Issue, *APIErrorResponse) { var post *model.Post if issue.PostID != "" { var appErr *model.AppError post, appErr = p.API.GetPost(issue.PostID) if appErr != nil { p.client.Log.Error("Unable to get the post", "PostID", issue.PostID, "Error", appErr.Error()) - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s", issue.PostID), StatusCode: http.StatusInternalServerError}) - return + return nil, &APIErrorResponse{Message: fmt.Sprintf("failed to load the post %s", issue.PostID), StatusCode: http.StatusInternalServerError} } if post == nil { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to load the post %s : not found", issue.PostID), StatusCode: http.StatusNotFound}) - return + return nil, &APIErrorResponse{Message: fmt.Sprintf("post %s not found", issue.PostID), StatusCode: http.StatusNotFound} } } @@ -1660,9 +1748,6 @@ func (p *Plugin) updateIssue(c *UserContext, w http.ResponseWriter, r *http.Requ Labels: &issue.Labels, Assignees: &issue.Assignees, } - - // submitting the request with an invalid milestone ID results in a 422 error - // we should make sure it's not zero here because the webapp client might have left this field empty if issue.Milestone > 0 { githubIssue.Milestone = &issue.Milestone } @@ -1670,13 +1755,12 @@ func (p *Plugin) updateIssue(c *UserContext, w http.ResponseWriter, r *http.Requ currentUser, appErr := p.API.GetUser(c.UserID) if appErr != nil { p.client.Log.Error("Unable to get the user", "UserID", c.UserID, "Error", appErr.Error()) - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load current user", StatusCode: http.StatusInternalServerError}) - return + return nil, &APIErrorResponse{Message: "failed to load current user", StatusCode: http.StatusInternalServerError} } splittedRepo := strings.Split(issue.Repo, "/") if len(splittedRepo) < 2 { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid repository", StatusCode: http.StatusBadRequest}) + return nil, &APIErrorResponse{Message: "Please provide a valid repository", StatusCode: http.StatusBadRequest} } owner, repoName := splittedRepo[0], splittedRepo[1] @@ -1685,20 +1769,13 @@ func (p *Plugin) updateIssue(c *UserContext, w http.ResponseWriter, r *http.Requ result, resp, err := githubClient.Issues.Edit(c.Ctx, owner, repoName, issue.IssueNumber, githubIssue) if err != nil { if resp != nil && resp.Response.StatusCode == http.StatusGone { - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Issues are disabled on this repository.", StatusCode: http.StatusMethodNotAllowed}) - return + return nil, &APIErrorResponse{Message: "Issues are disabled on this repository.", StatusCode: http.StatusMethodNotAllowed} } - c.Log.WithError(err).Warnf("Failed to update the issue") - p.writeAPIError(w, &APIErrorResponse{ - ID: "", - Message: fmt.Sprintf("failed to update the issue: %s", getFailReason(resp.StatusCode, - issue.Repo, - currentUser.Username, - )), + return nil, &APIErrorResponse{ + Message: fmt.Sprintf("failed to update the issue: %s", getFailReason(resp.StatusCode, issue.Repo, currentUser.Username)), StatusCode: resp.StatusCode, - }) - return + } } rootID := issue.PostID @@ -1719,18 +1796,20 @@ func (p *Plugin) updateIssue(c *UserContext, w http.ResponseWriter, r *http.Requ } if post != nil { - _, appErr = p.API.CreatePost(reply) + if _, appErr = p.API.CreatePost(reply); appErr != nil { + c.Log.WithError(appErr).Warnf("failed to create notification post") + return nil, &APIErrorResponse{ + Message: fmt.Sprintf("failed to create the notification post, postID: %s, channelID: %s", issue.PostID, channelID), + StatusCode: http.StatusInternalServerError, + } + } } else { _ = p.API.SendEphemeralPost(c.UserID, reply) } - if appErr != nil { - c.Log.WithError(appErr).Warnf("failed to create the notification post") - p.writeAPIError(w, &APIErrorResponse{ID: "", Message: fmt.Sprintf("failed to create the notification post, postID: %s, channelID: %s", issue.PostID, channelID), StatusCode: http.StatusInternalServerError}) - return - } - p.updatePost(issue, w) - p.writeJSON(w, result) + p.updatePost(issue, nil) + + return result, nil } type CommentAndCloseRequest struct { @@ -1963,29 +2042,150 @@ func (p *Plugin) getToken(w http.ResponseWriter, r *http.Request) { } func (p *Plugin) handleOpenEditIssueModal(c *UserContext, w http.ResponseWriter, r *http.Request) { - response := &model.PostActionIntegrationResponse{} decoder := json.NewDecoder(r.Body) postActionIntegrationRequest := &model.PostActionIntegrationRequest{} - if err := decoder.Decode(&postActionIntegrationRequest); err != nil { + if err := decoder.Decode(postActionIntegrationRequest); err != nil { p.API.LogError("Error decoding PostActionIntegrationRequest params", "Error", err.Error()) - p.returnPostActionIntegrationResponse(w, response) + p.returnPostActionIntegrationResponse(w, &model.PostActionIntegrationResponse{}) return } - p.client.Frontend.PublishWebSocketEvent( - WebsocketEventOpenEditModal, - map[string]interface{}{ - KeyRepoName: postActionIntegrationRequest.Context[KeyRepoName], - KeyRepoOwner: postActionIntegrationRequest.Context[KeyRepoOwner], - KeyIssueNumber: postActionIntegrationRequest.Context[KeyIssueNumber], - KeyPostID: postActionIntegrationRequest.PostId, - KeyStatus: postActionIntegrationRequest.Context[KeyStatus], - KeyChannelID: postActionIntegrationRequest.ChannelId, + triggerID := postActionIntegrationRequest.TriggerId + if triggerID == "" { + p.API.LogError("Trigger ID missing in request") + p.returnPostActionIntegrationResponse(w, &model.PostActionIntegrationResponse{}) + return + } + + repoOwner := postActionIntegrationRequest.Context[KeyRepoOwner].(string) + repoName := postActionIntegrationRequest.Context[KeyRepoName].(string) + issueNumber := fmt.Sprintf("%v", postActionIntegrationRequest.Context[KeyIssueNumber]) + postID := postActionIntegrationRequest.PostId + channelID := postActionIntegrationRequest.ChannelId + status := postActionIntegrationRequest.Context[KeyStatus].(string) + + issueNum, err := strconv.Atoi(issueNumber) + if err != nil { + p.API.LogError("Invalid issue number", "IssueNumber", issueNumber, "Error", err.Error()) + p.returnPostActionIntegrationResponse(w, &model.PostActionIntegrationResponse{}) + return + } + + issueInfo, apiErr := p.getGitHubIssueInfo(c, repoOwner, repoName, issueNum, postID) + if apiErr != nil { + p.API.LogError("Failed to fetch issue info", "Error", apiErr.Message) + p.returnPostActionIntegrationResponse(w, &model.PostActionIntegrationResponse{}) + return + } + + issueTitle, _ := issueInfo[KeyTitle].(string) + assigneesList, _ := issueInfo[KeyAssignees].([]string) + labelsList, _ := issueInfo[KeyLabels].([]string) + description, _ := issueInfo[KeyDescription].(string) + repoFullName, _ := issueInfo[KeyRepoFullName].(string) + + labels, err := p.fetchLabels(c.Context.Ctx, c.GHInfo, repoOwner, repoName) + if err != nil { + p.API.LogError("Failed to fetch labels", "Error", err.Error()) + p.returnPostActionIntegrationResponse(w, &model.PostActionIntegrationResponse{}) + return + } + assignees, err := p.fetchAssignees(c.Context.Ctx, c.GHInfo, repoOwner, repoName) + if err != nil { + p.API.LogError("Failed to fetch assignees", "Error", err.Error()) + p.returnPostActionIntegrationResponse(w, &model.PostActionIntegrationResponse{}) + return + } + milestones, err := p.fetchMilestones(c.Context.Ctx, c.GHInfo, repoOwner, repoName) + if err != nil { + p.API.LogError("Failed to fetch milestones", "Error", err.Error()) + p.returnPostActionIntegrationResponse(w, &model.PostActionIntegrationResponse{}) + return + } + + milestoneDefaultValue := "" + if len(milestones) > 0 { + milestoneDefaultValue = fmt.Sprintf("%d", milestones[0].GetNumber()) + } + + labelValues := []*model.PostActionOptions{} + for _, label := range labels { + name := label.GetName() + labelValues = append(labelValues, &model.PostActionOptions{Text: name, Value: name}) + } + + assigneeValues := []*model.PostActionOptions{} + for _, user := range assignees { + login := user.GetLogin() + assigneeValues = append(assigneeValues, &model.PostActionOptions{Text: login, Value: login}) + } + + milestoneValues := []*model.PostActionOptions{} + for _, milestone := range milestones { + milestoneValues = append(milestoneValues, &model.PostActionOptions{Text: milestone.GetTitle(), Value: fmt.Sprintf("%d", milestone.GetNumber())}) + } + + var elements []model.DialogElement + + elements = append(elements, model.DialogElement{ + DisplayName: "Title for the GitHub Issue", + Name: "Title", + Type: "text", + Optional: false, + Default: issueTitle, + }) + + elements = append(elements, model.DialogElement{ + DisplayName: "Description for the GitHub Issue", + Name: "Description", + Placeholder: "Add description to the issue...", + Type: "textarea", + Optional: true, + Default: description, + }) + + elements = append(elements, model.DialogElement{ + DisplayName: "Milestone", + Name: "Milestone", + Type: "select", + Placeholder: "Select...", + Optional: true, + Options: milestoneValues, + Default: milestoneDefaultValue, + }) + + stateMap := map[string]string{ + KeyChannelID: channelID, + KeyStatus: status, + KeyIssueNumber: issueNumber, + KeyRepoOwner: repoOwner, + KeyRepoName: repoName, + KeyPostID: postID, + } + + state := toJSON(stateMap) + + introduction := fmt.Sprintf("##### Repository: `%s`\n ##### Labels: `%s` \n ##### Assignees: `%s`", repoFullName, strings.Join(labelsList, ", "), strings.Join(assigneesList, ", ")) + + dialog := model.OpenDialogRequest{ + TriggerId: triggerID, + URL: fmt.Sprintf("%s%s", p.GetPluginAPIPath(), PathHandleOpenIssueEditModal), + Dialog: model.Dialog{ + CallbackId: "edit_issue", + Title: "Update GitHub Issue", + IntroductionText: introduction, + SubmitLabel: "Submit", + NotifyOnCancel: false, + Elements: elements, + State: state, }, - &model.WebsocketBroadcast{UserId: postActionIntegrationRequest.UserId}, - ) + } + + if appErr := p.API.OpenInteractiveDialog(dialog); appErr != nil { + p.API.LogError("Failed to open interactive dialog", "Error", appErr.Error()) + } - p.returnPostActionIntegrationResponse(w, response) + p.returnPostActionIntegrationResponse(w, &model.PostActionIntegrationResponse{}) } func (p *Plugin) returnPostActionIntegrationResponse(w http.ResponseWriter, res *model.PostActionIntegrationResponse) { From 5e5e35231653f1b87eb08adac1f1686f1077d947 Mon Sep 17 00:00:00 2001 From: kshitij katiyar Date: Wed, 30 Apr 2025 11:12:39 +0530 Subject: [PATCH 18/18] Lint fixes --- server/plugin/api.go | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/server/plugin/api.go b/server/plugin/api.go index 484794cf0..7d8be91cc 100644 --- a/server/plugin/api.go +++ b/server/plugin/api.go @@ -2084,18 +2084,6 @@ func (p *Plugin) handleOpenEditIssueModal(c *UserContext, w http.ResponseWriter, description, _ := issueInfo[KeyDescription].(string) repoFullName, _ := issueInfo[KeyRepoFullName].(string) - labels, err := p.fetchLabels(c.Context.Ctx, c.GHInfo, repoOwner, repoName) - if err != nil { - p.API.LogError("Failed to fetch labels", "Error", err.Error()) - p.returnPostActionIntegrationResponse(w, &model.PostActionIntegrationResponse{}) - return - } - assignees, err := p.fetchAssignees(c.Context.Ctx, c.GHInfo, repoOwner, repoName) - if err != nil { - p.API.LogError("Failed to fetch assignees", "Error", err.Error()) - p.returnPostActionIntegrationResponse(w, &model.PostActionIntegrationResponse{}) - return - } milestones, err := p.fetchMilestones(c.Context.Ctx, c.GHInfo, repoOwner, repoName) if err != nil { p.API.LogError("Failed to fetch milestones", "Error", err.Error()) @@ -2108,18 +2096,6 @@ func (p *Plugin) handleOpenEditIssueModal(c *UserContext, w http.ResponseWriter, milestoneDefaultValue = fmt.Sprintf("%d", milestones[0].GetNumber()) } - labelValues := []*model.PostActionOptions{} - for _, label := range labels { - name := label.GetName() - labelValues = append(labelValues, &model.PostActionOptions{Text: name, Value: name}) - } - - assigneeValues := []*model.PostActionOptions{} - for _, user := range assignees { - login := user.GetLogin() - assigneeValues = append(assigneeValues, &model.PostActionOptions{Text: login, Value: login}) - } - milestoneValues := []*model.PostActionOptions{} for _, milestone := range milestones { milestoneValues = append(milestoneValues, &model.PostActionOptions{Text: milestone.GetTitle(), Value: fmt.Sprintf("%d", milestone.GetNumber())}) @@ -2241,7 +2217,6 @@ func (p *Plugin) handleOpenIssueStatusModal(c *UserContext, w http.ResponseWrite } if status == IssueOpen { - status = statusClose dialogTitle = "Close Issue" submitLabel = "Close Issue"