diff --git a/.github/actions/build-ui/action.yml b/.github/actions/build-ui/action.yml index 1ab6dc4e73..a7c73aea10 100644 --- a/.github/actions/build-ui/action.yml +++ b/.github/actions/build-ui/action.yml @@ -6,7 +6,7 @@ runs: steps: - name: Cache UI artifacts id: cache-ui - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: | pkg/github/ui_dist/get-me.html @@ -18,9 +18,9 @@ runs: - name: Set up Node.js if: steps.cache-ui.outputs.cache-hit != 'true' - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: - node-version: "22" + node-version-file: ui/package.json cache: npm cache-dependency-path: ui/package-lock.json diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d43dcee742..dbb35ef4d5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,6 +9,10 @@ updates: directory: "/" schedule: interval: "weekly" + - package-ecosystem: "npm" + directory: "/ui" + schedule: + interval: "weekly" - package-ecosystem: "docker" directory: "/" schedule: diff --git a/.github/workflows/code-scanning.yml b/.github/workflows/code-scanning.yml index ca08306237..18d8d69ac8 100644 --- a/.github/workflows/code-scanning.yml +++ b/.github/workflows/code-scanning.yml @@ -44,7 +44,7 @@ jobs: uses: actions/checkout@v7 - name: Initialize CodeQL - uses: github/codeql-action/init@v4.37.9 + uses: github/codeql-action/init@v4.38.2 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -61,13 +61,13 @@ jobs: threat-models: [ ] - name: Setup proxy for registries id: proxy - uses: github/codeql-action/start-proxy@v4.37.9 + uses: github/codeql-action/start-proxy@v4.38.2 with: registries_credentials: ${{ secrets.GITHUB_REGISTRIES_PROXY }} language: ${{ matrix.language }} - name: Configure - uses: github/codeql-action/resolve-environment@v4.37.9 + uses: github/codeql-action/resolve-environment@v4.38.2 id: resolve-environment with: language: ${{ matrix.language }} @@ -82,7 +82,7 @@ jobs: if: matrix.language == 'javascript' uses: actions/setup-node@v7 with: - node-version: "20" + node-version-file: ui/package.json cache: "npm" cache-dependency-path: ui/package-lock.json @@ -91,10 +91,10 @@ jobs: uses: ./.github/actions/build-ui - name: Autobuild - uses: github/codeql-action/autobuild@v4.37.9 + uses: github/codeql-action/autobuild@v4.38.2 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4.37.9 + uses: github/codeql-action/analyze@v4.38.2 env: CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }} CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }} diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index b06914cfb9..a810a716ca 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -48,13 +48,13 @@ jobs: if: github.event_name != 'pull_request' uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 #v4.1.2 with: - cosign-release: "v2.2.4" + cosign-release: "v3.1.3" # Set up BuildKit Docker container builder to be able to build # multi-platform images and export cache # https://github.com/docker/setup-buildx-action - name: Set up Docker Buildx - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 # Login against a Docker registry except on PR # https://github.com/docker/login-action @@ -106,7 +106,7 @@ jobs: # https://github.com/docker/build-push-action - name: Build and push Docker image id: build-and-push - uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . push: ${{ github.event_name != 'pull_request' }} @@ -116,23 +116,26 @@ jobs: cache-to: type=gha,mode=max platforms: linux/amd64,linux/arm64 build-args: | - VERSION=${{ github.ref_name }} + VERSION=${{ github.ref_type == 'tag' && github.ref_name || 'dev' }} secrets: | oauth_client_id=${{ secrets.OAUTH_CLIENT_ID }} oauth_client_secret=${{ secrets.OAUTH_CLIENT_SECRET }} # Sign the resulting Docker image digest except on PRs. - # This will only write to the public Rekor transparency log when the Docker - # repository is public to avoid leaking data. If you would like to publish - # transparency data even for private images, pass --force to cosign below. + # Publish both v2-compatible .sig tags and native Sigstore bundles. + # --yes also consents to publishing signing metadata to the public log. # https://github.com/sigstore/cosign - - name: Sign the published Docker image + - name: Sign the published Docker image (legacy) if: ${{ github.event_name != 'pull_request' }} env: # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable - TAGS: ${{ steps.meta.outputs.tags }} DIGEST: ${{ steps.build-and-push.outputs.digest }} # This step uses the identity token to provision an ephemeral certificate # against the sigstore community Fulcio instance. - run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} - \ No newline at end of file + run: cosign sign --yes --new-bundle-format=false --use-signing-config=false --registry-referrers-mode=legacy "${REGISTRY}/${IMAGE_NAME}@${DIGEST}" + + - name: Sign the published Docker image (native bundle) + if: ${{ github.event_name != 'pull_request' }} + env: + DIGEST: ${{ steps.build-and-push.outputs.digest }} + run: cosign sign --yes "${REGISTRY}/${IMAGE_NAME}@${DIGEST}" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 68da3f03b2..568ea1ca18 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,9 +18,9 @@ jobs: uses: ./.github/actions/build-ui - uses: actions/setup-go@v7 with: - go-version: '1.26' + go-version-file: 'go.mod' - name: golangci-lint uses: golangci/golangci-lint-action@v9 with: # sync with script/lint - version: v2.9 + version: v2.14.0 diff --git a/.golangci.yml b/.golangci.yml index b80372fdbd..bde46b8e80 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -31,13 +31,17 @@ linters: - examples$ - internal/githubv4mock rules: + # v2.9.0 misreports SA4006 for Go 1.26 new(expr); retain go-github's helper until the linter is upgraded. + - linters: + - staticcheck + text: '(go)?github\.Ptr is deprecated: use the new builtin instead\.' - linters: - revive text: "var-naming: avoid package names that conflict with Go standard library package names" - - linters: - - modernize - text: "newexpr:" settings: + modernize: + disable: + - newexpr staticcheck: checks: - "all" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c16cd27dd..24f93af022 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,8 +26,27 @@ Thanks for contributing and for helping us build toolsets that are truly valuabl These are one time installations required to be able to test your changes locally as part of the pull request (PR) submission process. -1. Install Go [through download](https://go.dev/doc/install) | [through Homebrew](https://formulae.brew.sh/formula/go) -2. [Install golangci-lint v2](https://golangci-lint.run/welcome/install/#local-installation) +1. Install Go 1.26.8 or later [through download](https://go.dev/doc/install) | [through Homebrew](https://formulae.brew.sh/formula/go) +2. [Install golangci-lint v2.14.0](https://golangci-lint.run/welcome/install/#local-installation), or let `script/lint` install the repository-pinned version. The pinned version supports both Go 1.26 and Go 1.27. + +### UI development + +Use Node.js 26.x for the UI in `ui/`. This matches the Docker UI build stage +and `@types/node`; GitHub Actions reads the Node version from `ui/package.json`. +Node 26 is a supported Current release, with LTS scheduled for October 2026. + +From `ui/`, run `npm ci`, `npm run typecheck`, and `npm run build`. +Type checking uses the native TypeScript 7 compiler through `tsc`; Vite handles +transpilation and bundling separately. No TypeScript compiler API integration, +typescript-eslint, or ts-node is required. + +### MCP Apps UI + +The `ui/` views use React 19 and Primer React 38. With Node.js 20.19+ or 22.12+, run `cd ui && npm ci && npm run typecheck && npm run build && npm audit` before `script/test`. The build writes self-contained HTML to `pkg/github/ui_dist/`, which the Go server embeds; these generated files are not committed. + +Primer 38 no longer exports `Box` or accepts `sx`/styled-system props. Use semantic HTML, native `style` props for dynamic/layout styles, and CSS Modules for nested selectors. `AppProvider` loads Primer's primitive tokens and light/dark themes, so use CSS variables rather than JavaScript theme values. Custom element typings must augment `react/jsx-runtime` rather than the global `JSX` namespace. + +For UI dependency upgrades, compare all four views (`get-me`, `issue-write`, `pr-write`, and `pr-edit`) in light/dark themes and at narrow widths in an MCP Apps host, including menus, Markdown editing/preview, and completed-result views. Include before/after screenshots in the pull request. ## Submitting a pull request diff --git a/README.md b/README.md index d013303d81..c1f9857d1a 100644 --- a/README.md +++ b/README.md @@ -403,7 +403,13 @@ For a complete overview of all installation options, see our **[Installation Gui ### Build from source If you don't have Docker, you can use `go build` to build the binary in the -`cmd/github-mcp-server` directory, and use the `github-mcp-server stdio` command with the `GITHUB_PERSONAL_ACCESS_TOKEN` environment variable set to your token. To specify the output location of the build, use the `-o` flag. You should configure your server to use the built executable as its `command`. For example: +`cmd/github-mcp-server` directory, and use the `github-mcp-server stdio` command with the `GITHUB_PERSONAL_ACCESS_TOKEN` environment variable set to your token. To specify the output location of the build, use the `-o` flag. You should configure your server to use the built executable as its `command`. + +STDIO API requests identify the server as `github-mcp-server/` and retain the upstream MCP client's name/version in parentheses when available. Control characters, quotes, backslashes and parentheses in client metadata are escaped so the HTTP header remains valid; ordinary names, versions, spaces and printable Unicode are preserved. Release builds keep their release version. Source and default Docker builds with revision metadata use `vcs-`. The Docker publishing workflow preserves tag versions, including prereleases, and lets branch, pull-request and nightly builds use the linked source revision. The `-dirty` marker applies only when both the revision and modified state come from embedded VCS metadata, never to a valid, explicitly supplied `main.commit`. This is a VCS build identifier, not a release number. + +Build the complete package with `go build -o github-mcp-server ./cmd/github-mcp-server` from a Git checkout to embed its VCS revision. For builds without VCS metadata, supply the actual release with `-ldflags '-X main.version='` or the full source revision with `-ldflags '-X main.commit='`. Valid metadata is selected in this order: explicit release, explicit source revision, embedded VCS revision, then installed main-module version. Valid explicit revisions remain authoritative even if build-context filtering changes embedded VCS metadata. Missing or malformed candidates fall through to the next usable source. If none is available, STDIO still starts with the development label `dev` and emits a warning on stderr, leaving stdout available for the MCP protocol. Supply real release or revision metadata when version-specific attribution is needed. + +For example: ```JSON { @@ -704,6 +710,7 @@ The following sets of tools are available: person Context - **get_me** - Get my user profile + - **MCP App UI**: `ui://github-mcp-server/get-me` - No parameters required - **get_team_members** - Get team members @@ -715,6 +722,12 @@ The following sets of tools are available: - **OAuth Challenge Scopes**: `read:org` - `user`: Username to get teams for. If not provided, uses the authenticated user. (string, optional) +- **ui_get** - Get UI data + - **OAuth Challenge Scopes**: `repo`, `read:org` + - `method`: The type of data to fetch (string, required) + - `owner`: Repository owner (required for all methods) (string, required) + - `repo`: Repository name (required for labels, assignees, milestones, branches, issue fields, reviewers) (string, optional) +
@@ -983,6 +996,7 @@ The following sets of tools are available: - **issue_write** - Create or update issue/pull request - **OAuth Challenge Scopes**: `repo` + - **MCP App UI**: `ui://github-mcp-server/issue-write` - `assignees`: Usernames to assign to this issue (string[], optional) - `body`: Issue body content (string, optional) - `duplicate_of`: Issue number that this issue is a duplicate of. Required when state_reason is 'duplicate'. (number, optional) @@ -1238,6 +1252,7 @@ The following sets of tools are available: - **create_pull_request** - Open new pull request - **OAuth Challenge Scopes**: `repo` + - **MCP App UI**: `ui://github-mcp-server/pr-write` - `base`: Branch to merge into (string, required) - `body`: PR description (string, optional) - `draft`: Create as draft PR (boolean, optional) @@ -1316,6 +1331,7 @@ The following sets of tools are available: - **update_pull_request** - Edit pull request - **OAuth Challenge Scopes**: `repo` + - **MCP App UI**: `ui://github-mcp-server/pr-edit` - `base`: New base branch name (string, optional) - `body`: New description (string, optional) - `draft`: Mark pull request as draft (true) or ready for review (false) (boolean, optional) diff --git a/cmd/github-mcp-server/generate_docs.go b/cmd/github-mcp-server/generate_docs.go index a9a6c20f83..736513f6f1 100644 --- a/cmd/github-mcp-server/generate_docs.go +++ b/cmd/github-mcp-server/generate_docs.go @@ -222,9 +222,7 @@ func writeToolDoc(buf *strings.Builder, tool inventory.ServerTool) { fmt.Fprintf(buf, " - **OAuth Challenge Scopes**: `%s`\n", strings.Join(scopes, "`, `")) } - // MCP App UI metadata (only rendered when the remote_mcp_ui_apps flag - // applied to the inventory; for the no-flags README this section is - // stripped by inventory.ToolsForRegistration before rendering). + // MCP App UI metadata. if ui, ok := tool.Tool.Meta["ui"].(map[string]any); ok { if uri, ok := ui["resourceUri"].(string); ok && uri != "" { fmt.Fprintf(buf, " - **MCP App UI**: `%s`\n", uri) diff --git a/cmd/github-mcp-server/main.go b/cmd/github-mcp-server/main.go index c0cadbbc63..91abef6a08 100644 --- a/cmd/github-mcp-server/main.go +++ b/cmd/github-mcp-server/main.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "runtime/debug" "strings" "time" @@ -38,7 +39,13 @@ var ( Use: "stdio", Short: "Start stdio server", Long: `Start a server that communicates via standard input/output streams using JSON-RPC messages.`, - RunE: func(_ *cobra.Command, _ []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { + info, _ := debug.ReadBuildInfo() + serverVersion := resolveServerVersion(version, commit, info) + if serverVersion == developmentServerVersion { + cmd.PrintErrln("Warning: no usable server build version metadata; using development version dev") + } + token := viper.GetString("personal_access_token") appID := viper.GetString("app-id") appInstallationID := viper.GetString("app-installation-id") @@ -110,7 +117,7 @@ var ( ttl := viper.GetDuration("repo-access-cache-ttl") stdioServerConfig := ghmcp.StdioServerConfig{ - Version: version, + Version: serverVersion, Host: viper.GetString("host"), Token: token, EnabledToolsets: enabledToolsets, diff --git a/cmd/github-mcp-server/version.go b/cmd/github-mcp-server/version.go new file mode 100644 index 0000000000..51b9e5880c --- /dev/null +++ b/cmd/github-mcp-server/version.go @@ -0,0 +1,75 @@ +package main + +import ( + "encoding/hex" + "runtime/debug" + "strings" + "unicode" +) + +const developmentServerVersion = "dev" + +func resolveServerVersion(release, revision string, info *debug.BuildInfo) string { + isPlaceholder := func(value string) bool { + switch value { + case "", "version", "dev", "unknown", "(devel)": + return true + default: + return false + } + } + validRelease := func(value string) bool { + if isPlaceholder(value) { + return false + } + for _, r := range value { + if r > unicode.MaxASCII || r <= ' ' || r == 127 || strings.ContainsRune("()<>@,;:\\\"/[]?={}", r) { + return false + } + } + return true + } + if validRelease(release) { + return release + } + + var vcsRevision string + var dirty bool + if info != nil { + for _, setting := range info.Settings { + switch setting.Key { + case "vcs.revision": + vcsRevision = setting.Value + case "vcs.modified": + dirty = setting.Value == "true" + } + } + } + revisions := []struct { + value string + dirty bool + }{ + {value: revision}, + {value: vcsRevision, dirty: dirty}, + } + for _, candidate := range revisions { + if len(candidate.value) != 40 && len(candidate.value) != 64 { + continue + } + if _, err := hex.DecodeString(candidate.value); err != nil { + continue + } + if strings.Trim(candidate.value, "0") == "" { + continue + } + resolved := "vcs-" + strings.ToLower(candidate.value) + if candidate.dirty { + resolved += "-dirty" + } + return resolved + } + if info != nil && validRelease(info.Main.Version) { + return info.Main.Version + } + return developmentServerVersion +} diff --git a/cmd/github-mcp-server/version_test.go b/cmd/github-mcp-server/version_test.go new file mode 100644 index 0000000000..4258b817c1 --- /dev/null +++ b/cmd/github-mcp-server/version_test.go @@ -0,0 +1,122 @@ +package main + +import ( + "runtime/debug" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestResolveServerVersion(t *testing.T) { + t.Parallel() + sha := strings.Repeat("a1", 20) + otherSHA := strings.Repeat("b2", 20) + source := &debug.BuildInfo{ + Main: debug.Module{Version: "(devel)"}, + Settings: []debug.BuildSetting{ + {Key: "vcs.revision", Value: sha}, {Key: "vcs.modified", Value: "false"}, + }, + } + dirty := &debug.BuildInfo{ + Main: debug.Module{Version: "(devel)"}, + Settings: []debug.BuildSetting{ + {Key: "vcs.revision", Value: sha}, {Key: "vcs.modified", Value: "true"}, + }, + } + tests := []struct { + name string + release string + revision string + info *debug.BuildInfo + want string + }{ + {name: "release unchanged", release: "v1.2.3", revision: sha, info: dirty, want: "v1.2.3"}, + {name: "release suffix unchanged", release: "v1.2.3-rc.1+build.4", want: "v1.2.3-rc.1+build.4"}, + {name: "release before invalid revision", release: "v1.2.3", revision: "short", info: dirty, want: "v1.2.3"}, + {name: "source build", release: "version", revision: "commit", info: source, want: "vcs-" + sha}, + { + name: "source revision before inferred module version", release: "version", revision: "commit", + info: &debug.BuildInfo{ + Main: debug.Module{Version: "v1.2.4-0.20260916095829-a1a1a1a1a1a1+dirty"}, + Settings: dirty.Settings, + }, + want: "vcs-" + sha + "-dirty", + }, + {name: "dirty source", release: "version", revision: "commit", info: dirty, want: "vcs-" + sha + "-dirty"}, + {name: "Docker revision", release: "dev", revision: sha, want: "vcs-" + sha}, + {name: "Docker revision ignores context dirty state", release: "dev", revision: sha, info: dirty, want: "vcs-" + sha}, + {name: "linked revision ignores unrelated dirty state", release: "dev", revision: otherSHA, info: dirty, want: "vcs-" + otherSHA}, + {name: "linked revision precedence", release: "dev", revision: otherSHA, info: source, want: "vcs-" + otherSHA}, + {name: "SHA256", release: "dev", revision: strings.Repeat("ab", 32), want: "vcs-" + strings.Repeat("ab", 32)}, + {name: "canonical hex", release: "dev", revision: strings.ToUpper(sha), want: "vcs-" + sha}, + {name: "installed module", info: &debug.BuildInfo{Main: debug.Module{Version: "v1.2.3"}}, want: "v1.2.3"}, + {name: "absent build info", release: "version", revision: "commit", want: "dev"}, + {name: "missing VCS metadata", release: "dev", info: &debug.BuildInfo{}, want: "dev"}, + {name: "unknown placeholder", release: "unknown", want: "dev"}, + {name: "short revision", release: "dev", revision: "abcdef", want: "dev"}, + {name: "short revision falls through to VCS", release: "dev", revision: "abcdef", info: source, want: "vcs-" + sha}, + {name: "malformed linked revision falls through to VCS", release: "dev", revision: strings.Repeat("x", 40), info: source, want: "vcs-" + sha}, + {name: "malformed linked revision without VCS", release: "dev", revision: strings.Repeat("x", 40), want: "dev"}, + { + name: "malformed VCS falls through to installed module", + info: &debug.BuildInfo{ + Main: debug.Module{Version: "v1.2.3"}, + Settings: []debug.BuildSetting{{Key: "vcs.revision", Value: "short"}}, + }, + want: "v1.2.3", + }, + { + name: "malformed VCS without installed module", + info: &debug.BuildInfo{ + Main: debug.Module{Version: "(devel)"}, + Settings: []debug.BuildSetting{{Key: "vcs.revision", Value: "short"}}, + }, + want: "dev", + }, + {name: "invalid installed module", info: &debug.BuildInfo{Main: debug.Module{Version: "v1.2.3\n"}}, want: "dev"}, + {name: "placeholder installed module", info: &debug.BuildInfo{Main: debug.Module{Version: "(devel)"}}, want: "dev"}, + {name: "release whitespace", release: "v1.2.3 extra", want: "dev"}, + {name: "release newline", release: "v1.2.3\n", want: "dev"}, + {name: "release slash", release: "release/v1.2.3", want: "dev"}, + {name: "release non ASCII", release: "v1.2.3-\u00e9", want: "dev"}, + {name: "invalid release falls through to linked revision", release: "v1.2.3 extra", revision: otherSHA, info: dirty, want: "vcs-" + otherSHA}, + {name: "invalid release falls through to dirty VCS", release: "release/v1.2.3", info: dirty, want: "vcs-" + sha + "-dirty"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, resolveServerVersion(tt.release, tt.revision, tt.info)) + }) + } +} + +func TestResolveServerVersionSkipsNullRevisions(t *testing.T) { + t.Parallel() + for _, revision := range []string{strings.Repeat("0", 40), strings.Repeat("0", 64)} { + for _, modified := range []string{"false", "true"} { + for _, linked := range []bool{false, true} { + name := revision + "/modified=" + modified + if linked { + name += "/linked" + } + t.Run(name, func(t *testing.T) { + info := &debug.BuildInfo{Settings: []debug.BuildSetting{ + {Key: "vcs.revision", Value: revision}, + {Key: "vcs.modified", Value: modified}, + }} + commit := "commit" + want := "dev" + if linked { + commit = revision + info.Settings[0].Value = strings.Repeat("ab", 20) + want = "vcs-" + strings.Repeat("ab", 20) + if modified == "true" { + want += "-dirty" + } + } + assert.Equal(t, want, resolveServerVersion("dev", commit, info)) + }) + } + } + } +} diff --git a/docs/feature-flags.md b/docs/feature-flags.md index ec5282bd54..0d32b3c33a 100644 --- a/docs/feature-flags.md +++ b/docs/feature-flags.md @@ -86,70 +86,6 @@ as output formatting) won't appear here. -### `remote_mcp_ui_apps` - -- **create_pull_request** - Open new pull request - - **OAuth Challenge Scopes**: `repo` - - **MCP App UI**: `ui://github-mcp-server/pr-write` - - `base`: Branch to merge into (string, required) - - `body`: PR description (string, optional) - - `draft`: Create as draft PR (boolean, optional) - - `head`: Branch containing changes (string, required) - - `maintainer_can_modify`: Allow maintainer edits (boolean, optional) - - `owner`: Repository owner (string, required) - - `repo`: Repository name (string, required) - - `reviewers`: GitHub usernames or ORG/team-slug team reviewers to request reviews from (string[], optional) - - `title`: PR title (string, required) - -- **get_me** - Get my user profile - - **MCP App UI**: `ui://github-mcp-server/get-me` - - No parameters required - -- **issue_write** - Create or update issue/pull request - - **OAuth Challenge Scopes**: `repo` - - **MCP App UI**: `ui://github-mcp-server/issue-write` - - `assignees`: Usernames to assign to this issue (string[], optional) - - `body`: Issue body content (string, optional) - - `duplicate_of`: Issue number that this issue is a duplicate of. Required when state_reason is 'duplicate'. (number, optional) - - `issue_fields`: Issue field values to set or clear. Each item requires 'field_name' and exactly one of 'value', 'field_option_name', or 'delete: true'. (object[], optional) - - `issue_number`: Issue number to update (number, optional) - - `labels`: Labels to apply to this issue (string[], optional) - - `method`: Write operation to perform on a single issue. - Options are: - - 'create' - creates a new issue. - - 'update' - updates an existing issue. - (string, required) - - `milestone`: Milestone number (number, optional) - - `owner`: Repository owner (string, required) - - `parent_issue_number`: Issue number of the parent issue. Only used when method is 'create' and cannot be combined with issue_fields. The new issue is created and attached to this parent in the same operation. (number, optional) - - `parent_owner`: Repository owner of the parent issue. Must be provided with parent_repo. Omit both to use owner and repo. Only used when method is 'create' and parent_issue_number is provided. (string, optional) - - `parent_repo`: Repository name of the parent issue. Must be provided with parent_owner. Omit both to use owner and repo. Only used when method is 'create' and parent_issue_number is provided. (string, optional) - - `repo`: Repository name (string, required) - - `state`: New state (string, optional) - - `state_reason`: Reason for the state change. Ignored unless state is changed. (string, optional) - - `title`: Issue title (string, optional) - - `type`: Type of this issue. For updates, pass null to remove the current type. Only use if issue types are enabled for this repository. Use list_issue_types to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter. (string | null, optional) - -- **ui_get** - Get UI data - - **OAuth Challenge Scopes**: `repo`, `read:org` - - `method`: The type of data to fetch (string, required) - - `owner`: Repository owner (required for all methods) (string, required) - - `repo`: Repository name (required for labels, assignees, milestones, branches, issue fields, reviewers) (string, optional) - -- **update_pull_request** - Edit pull request - - **OAuth Challenge Scopes**: `repo` - - **MCP App UI**: `ui://github-mcp-server/pr-edit` - - `base`: New base branch name (string, optional) - - `body`: New description (string, optional) - - `draft`: Mark pull request as draft (true) or ready for review (false) (boolean, optional) - - `maintainer_can_modify`: Allow maintainer edits (boolean, optional) - - `owner`: Repository owner (string, required) - - `pullNumber`: Pull request number to update (number, required) - - `repo`: Repository name (string, required) - - `reviewers`: GitHub usernames or ORG/team-slug team reviewers to request reviews from (string[], optional) - - `state`: New state (string, optional) - - `title`: New title (string, optional) - ### `issues_granular` - **add_issue_comment_reaction** - Add Reaction to Issue or Pull Request Comment @@ -184,6 +120,13 @@ as output formatting) won't appear here. - `repo`: Repository name (string, required) - `title`: Issue title (string, required) +- **hide_issue_comment** - Hide Issue Comment + - **OAuth Challenge Scopes**: `repo` + - `classifier`: The reason for hiding the comment (string, required) + - `comment_id`: The numeric ID of the issue or pull request conversation comment (number, required) + - `owner`: Repository owner (username or organization) (string, required) + - `repo`: Repository name (string, required) + - **remove_issue_comment_reaction** - Remove Reaction from Issue or Pull Request Comment - **OAuth Challenge Scopes**: `repo` - `comment_id`: The issue or pull request comment ID (number, required) @@ -221,6 +164,12 @@ as output formatting) won't appear here. - `owner`: Repository owner (username or organization) (string, required) - `repo`: Repository name (string, required) +- **unhide_issue_comment** - Unhide Issue Comment + - **OAuth Challenge Scopes**: `repo` + - `comment_id`: The numeric ID of the issue or pull request conversation comment (number, required) + - `owner`: Repository owner (username or organization) (string, required) + - `repo`: Repository name (string, required) + - **update_issue_assignees** - Update Issue Assignees - **OAuth Challenge Scopes**: `repo` - `assignees`: GitHub usernames to assign to this issue. ([], required) @@ -315,6 +264,21 @@ as output formatting) won't appear here. - `pullNumber`: The pull request number (number, required) - `repo`: Repository name (string, required) +- **hide_pull_request_review** - Hide Pull Request Review + - **OAuth Challenge Scopes**: `repo` + - `classifier`: The reason for hiding the comment (string, required) + - `owner`: Repository owner (username or organization) (string, required) + - `pullNumber`: The pull request number (number, required) + - `repo`: Repository name (string, required) + - `review_id`: The numeric ID of the pull request review (number, required) + +- **hide_pull_request_review_comment** - Hide Pull Request Review Comment + - **OAuth Challenge Scopes**: `repo` + - `classifier`: The reason for hiding the comment (string, required) + - `comment_id`: The numeric pull request review comment ID. Use the number from a #discussion_r... anchor, not the GraphQL thread node ID (PRRT_...). (number, required) + - `owner`: Repository owner (username or organization) (string, required) + - `repo`: Repository name (string, required) + - **remove_pull_request_review_comment_reaction** - Remove Pull Request Review Comment Reaction - **OAuth Challenge Scopes**: `repo` - `comment_id`: The numeric pull request review comment ID. Use the number from a #discussion_r... anchor, not the GraphQL thread node ID (PRRT_...). (number, required) @@ -341,6 +305,19 @@ as output formatting) won't appear here. - `pullNumber`: The pull request number (number, required) - `repo`: Repository name (string, required) +- **unhide_pull_request_review** - Unhide Pull Request Review + - **OAuth Challenge Scopes**: `repo` + - `owner`: Repository owner (username or organization) (string, required) + - `pullNumber`: The pull request number (number, required) + - `repo`: Repository name (string, required) + - `review_id`: The numeric ID of the pull request review (number, required) + +- **unhide_pull_request_review_comment** - Unhide Pull Request Review Comment + - **OAuth Challenge Scopes**: `repo` + - `comment_id`: The numeric pull request review comment ID. Use the number from a #discussion_r... anchor, not the GraphQL thread node ID (PRRT_...). (number, required) + - `owner`: Repository owner (username or organization) (string, required) + - `repo`: Repository name (string, required) + - **unresolve_review_thread** - Unresolve Review Thread - **OAuth Challenge Scopes**: `repo` - `threadID`: The node ID of the review thread to unresolve (e.g., PRRT_kwDOxxx) (string, required) diff --git a/docs/insiders-features.md b/docs/insiders-features.md index 4255e5bdd9..5738252c7c 100644 --- a/docs/insiders-features.md +++ b/docs/insiders-features.md @@ -26,70 +26,6 @@ The list below is generated from the Go source. It covers tool **inventory and s -### `remote_mcp_ui_apps` - -- **create_pull_request** - Open new pull request - - **OAuth Challenge Scopes**: `repo` - - **MCP App UI**: `ui://github-mcp-server/pr-write` - - `base`: Branch to merge into (string, required) - - `body`: PR description (string, optional) - - `draft`: Create as draft PR (boolean, optional) - - `head`: Branch containing changes (string, required) - - `maintainer_can_modify`: Allow maintainer edits (boolean, optional) - - `owner`: Repository owner (string, required) - - `repo`: Repository name (string, required) - - `reviewers`: GitHub usernames or ORG/team-slug team reviewers to request reviews from (string[], optional) - - `title`: PR title (string, required) - -- **get_me** - Get my user profile - - **MCP App UI**: `ui://github-mcp-server/get-me` - - No parameters required - -- **issue_write** - Create or update issue/pull request - - **OAuth Challenge Scopes**: `repo` - - **MCP App UI**: `ui://github-mcp-server/issue-write` - - `assignees`: Usernames to assign to this issue (string[], optional) - - `body`: Issue body content (string, optional) - - `duplicate_of`: Issue number that this issue is a duplicate of. Required when state_reason is 'duplicate'. (number, optional) - - `issue_fields`: Issue field values to set or clear. Each item requires 'field_name' and exactly one of 'value', 'field_option_name', or 'delete: true'. (object[], optional) - - `issue_number`: Issue number to update (number, optional) - - `labels`: Labels to apply to this issue (string[], optional) - - `method`: Write operation to perform on a single issue. - Options are: - - 'create' - creates a new issue. - - 'update' - updates an existing issue. - (string, required) - - `milestone`: Milestone number (number, optional) - - `owner`: Repository owner (string, required) - - `parent_issue_number`: Issue number of the parent issue. Only used when method is 'create' and cannot be combined with issue_fields. The new issue is created and attached to this parent in the same operation. (number, optional) - - `parent_owner`: Repository owner of the parent issue. Must be provided with parent_repo. Omit both to use owner and repo. Only used when method is 'create' and parent_issue_number is provided. (string, optional) - - `parent_repo`: Repository name of the parent issue. Must be provided with parent_owner. Omit both to use owner and repo. Only used when method is 'create' and parent_issue_number is provided. (string, optional) - - `repo`: Repository name (string, required) - - `state`: New state (string, optional) - - `state_reason`: Reason for the state change. Ignored unless state is changed. (string, optional) - - `title`: Issue title (string, optional) - - `type`: Type of this issue. For updates, pass null to remove the current type. Only use if issue types are enabled for this repository. Use list_issue_types to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter. (string | null, optional) - -- **ui_get** - Get UI data - - **OAuth Challenge Scopes**: `repo`, `read:org` - - `method`: The type of data to fetch (string, required) - - `owner`: Repository owner (required for all methods) (string, required) - - `repo`: Repository name (required for labels, assignees, milestones, branches, issue fields, reviewers) (string, optional) - -- **update_pull_request** - Edit pull request - - **OAuth Challenge Scopes**: `repo` - - **MCP App UI**: `ui://github-mcp-server/pr-edit` - - `base`: New base branch name (string, optional) - - `body`: New description (string, optional) - - `draft`: Mark pull request as draft (true) or ready for review (false) (boolean, optional) - - `maintainer_can_modify`: Allow maintainer edits (boolean, optional) - - `owner`: Repository owner (string, required) - - `pullNumber`: Pull request number to update (number, required) - - `repo`: Repository name (string, required) - - `reviewers`: GitHub usernames or ORG/team-slug team reviewers to request reviews from (string[], optional) - - `state`: New state (string, optional) - - `title`: New title (string, optional) - ### `file_blame` - **get_file_blame** - Get file blame information @@ -139,31 +75,6 @@ The list below is generated from the Go source. It covers tool **inventory and s --- -## MCP Apps - -[MCP Apps](https://modelcontextprotocol.io/docs/extensions/apps) is an extension to the Model Context Protocol that enables servers to deliver interactive user interfaces to end users. Instead of returning plain text that the LLM must interpret and relay, tools can render forms, profiles, and dashboards right in the chat using MCP Apps. - -This means you can interact with GitHub visually: fill out forms to create issues, see user profiles with avatars, open pull requests — all without leaving your agent chat. - -### Supported tools - -The following tools have MCP Apps UIs: - -| Tool | Description | -|------|-------------| -| `get_me` | Displays your GitHub user profile with avatar, bio, and stats in a rich card | -| `issue_write` | Opens an interactive form to create or update issues | -| `create_pull_request` | Provides a full PR creation form to create a pull request (or a draft pull request) | - -### Client requirements - -MCP Apps requires a host that supports the [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps). Currently tested and working with: - -- **VS Code Insiders** — enable via the `chat.mcp.apps.enabled` setting -- **Visual Studio Code** — enable via the `chat.mcp.apps.enabled` setting - ---- - ## CSV output for list tools CSV output mode returns supported list tool responses as CSV instead of JSON. This is intended to reduce response context for agents when scanning or summarising lists of GitHub data. diff --git a/docs/installation-guides/README.md b/docs/installation-guides/README.md index 46581aa77e..888caf64a4 100644 --- a/docs/installation-guides/README.md +++ b/docs/installation-guides/README.md @@ -66,7 +66,7 @@ The GitHub MCP Server can be installed using several methods. **Docker is the mo - **Pros**: Latest features, full customization, no external dependencies - **Cons**: Requires Go development environment, more complex setup - **Prerequisites**: [Go 1.24+](https://go.dev/doc/install) -- **Build command**: `go build -o github-mcp-server cmd/github-mcp-server/main.go` +- **Build command**: `go build -o github-mcp-server ./cmd/github-mcp-server` (build the complete package from a Git checkout to retain its source revision). - **Best for**: Developers who want the latest features or need custom modifications ### Important Notes on the GitHub MCP Server diff --git a/docs/server-configuration.md b/docs/server-configuration.md index 42584362d9..25c641153e 100644 --- a/docs/server-configuration.md +++ b/docs/server-configuration.md @@ -345,7 +345,7 @@ As an intentional exception, content authored by trusted bot accounts (currently **Best for:** Users who want early access to experimental features and new tools before they reach general availability. -Insiders Mode unlocks experimental features, such as [MCP Apps](#mcp-apps) support. We created this mode to have a way to roll out experimental features and collect feedback. So if you are using Insiders, please don't hesitate to share your feedback with us! Features in Insiders Mode may change, evolve, or be removed based on user feedback. +Insiders Mode unlocks experimental features, such as [CSV output for list tools](./insiders-features.md#csv-output-for-list-tools). We created this mode to have a way to roll out experimental features and collect feedback. So if you are using Insiders, please don't hesitate to share your feedback with us! Features in Insiders Mode may change, evolve, or be removed based on user feedback. @@ -402,18 +402,18 @@ See [Insiders Features](./insiders-features.md) for a full list of what's availa [MCP Apps](https://modelcontextprotocol.io/docs/extensions/apps) is an extension to the Model Context Protocol that enables servers to deliver interactive user interfaces to end users. Instead of returning plain text that the LLM must interpret and relay, tools can render forms, profiles, and dashboards right in the chat. -MCP Apps is enabled by [Insiders Mode](#insiders-mode), or independently via the `remote_mcp_ui_apps` feature flag. +MCP Apps is enabled by default. Tools with MCP Apps UIs advertise them via `_meta.ui` to clients that support the [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps); the metadata is omitted for clients that do not advertise the `io.modelcontextprotocol/ui` capability. To keep MCP App result views enabled while making write tools execute directly -instead of first opening an interactive form, also enable the -`mcp_apps_disable_form_deferral` feature flag. For the remote server, send both -flags in the request header: +instead of first opening an interactive form, enable the +`mcp_apps_disable_form_deferral` feature flag. For the remote server, send the +flag in the request header: ```http -X-MCP-Features: remote_mcp_ui_apps,mcp_apps_disable_form_deferral +X-MCP-Features: mcp_apps_disable_form_deferral ``` -For the local server, pass both flags to `--features`. +For the local server, pass it to `--features`. **Supported tools:** @@ -422,47 +422,10 @@ For the local server, pass both flags to `--features`. | `get_me` | Displays your GitHub user profile with avatar, bio, and stats in a rich card | | `issue_write` | Opens an interactive form to create or update issues | | `create_pull_request` | Provides a full PR creation form to create a pull request (or a draft pull request) | +| `update_pull_request` | Opens an interactive form to edit a pull request | **Client requirements:** MCP Apps requires a host that supports the [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps). Currently tested with VS Code (`chat.mcp.apps.enabled` setting). -
Remote ServerLocal Server
- - - - - -
Remote ServerLocal Server
- -```json -{ - "type": "http", - "url": "https://api.githubcopilot.com/mcp/", - "headers": { - "X-MCP-Features": "remote_mcp_ui_apps" - } -} -``` - - - -```json -{ - "type": "stdio", - "command": "go", - "args": [ - "run", - "./cmd/github-mcp-server", - "stdio", - "--features=remote_mcp_ui_apps" - ], - "env": { - "GITHUB_PERSONAL_ACCESS_TOKEN": "${input:github_token}" - } -} -``` - -
- --- ### Scope Filtering diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index 112f653a37..3df717087a 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -20,7 +20,7 @@ import ( "github.com/github/github-mcp-server/pkg/github" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - gogithub "github.com/google/go-github/v89/github" + gogithub "github.com/google/go-github/v92/github" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/require" ) diff --git a/go.mod b/go.mod index 474a269471..dade1dbe16 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,19 @@ module github.com/github/github-mcp-server -go 1.26.0 +go 1.26.8 require ( github.com/go-chi/chi/v5 v5.3.2 github.com/go-viper/mapstructure/v2 v2.5.0 - github.com/google/go-github/v89 v89.0.1-0.20260728185857-34349a88bac3 + github.com/google/go-github/v92 v92.0.0 github.com/google/jsonschema-go v0.4.3 github.com/josephburnett/jd/v2 v2.5.0 github.com/lithammer/fuzzysearch v1.1.8 github.com/microcosm-cc/bluemonday v1.0.27 github.com/modelcontextprotocol/go-sdk v1.8.0 github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021 - github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 - github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 + github.com/shurcooL/githubv4 v0.0.0-20260209031235-2402fdf4a9ed + github.com/shurcooL/graphql v0.0.0-20240915155400-7ee5256398cf github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 diff --git a/go.sum b/go.sum index ce672a6765..bfa3c4dbae 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArs github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-github/v89 v89.0.1-0.20260728185857-34349a88bac3 h1:0a/p9KtPso8UBauBD/p9Go1oaZrrEydBNHjaaKkSHJo= -github.com/google/go-github/v89 v89.0.1-0.20260728185857-34349a88bac3/go.mod h1:QLcbU0ipeAqQuR5KSg8c2lql4Qk1EwJ2dWz/0rP4Nho= +github.com/google/go-github/v92 v92.0.0 h1:4vW4RVffwIvoEfIA4RX09mRSB47qQX557/2+HgeWRRg= +github.com/google/go-github/v92 v92.0.0/go.mod h1:w3CH62ZcmRfvW1cdXpyTztSOVMtdjxtKpgo0GouLmjY= github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0= @@ -49,10 +49,10 @@ github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= -github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 h1:cYCy18SHPKRkvclm+pWm1Lk4YrREb4IOIb/YdFO0p2M= -github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8= -github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0= -github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE= +github.com/shurcooL/githubv4 v0.0.0-20260209031235-2402fdf4a9ed h1:KT7hI8vYXgU0s2qaMkrfq9tCA1w/iEPgfredVP+4Tzw= +github.com/shurcooL/githubv4 v0.0.0-20260209031235-2402fdf4a9ed/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8= +github.com/shurcooL/graphql v0.0.0-20240915155400-7ee5256398cf h1:o1uxfymjZ7jZ4MsgCErcwWGtVKSiNAXtS59Lhs6uI/g= +github.com/shurcooL/graphql v0.0.0-20240915155400-7ee5256398cf/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index f713a44026..886f90c5f8 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "os/signal" + "strconv" "strings" "syscall" "time" @@ -26,19 +27,17 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - gogithub "github.com/google/go-github/v89/github" + gogithub "github.com/google/go-github/v92/github" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" ) // githubClients holds all the GitHub API clients created for a server instance. type githubClients struct { - rest *gogithub.Client - restUATransp *transport.UserAgentTransport - gql *githubv4.Client - gqlHTTP *http.Client // retained for middleware to modify transport - raw *raw.Client - repoAccess *lockdown.RepoAccessCache + rest *gogithub.Client + gql *githubv4.Client + raw *raw.Client + repoAccess *lockdown.RepoAccessCache } // createGitHubClients creates all the GitHub API clients needed by the server. @@ -90,7 +89,7 @@ func createGitHubClients(cfg github.MCPServerConfig, apiHost utils.APIHostResolv // client per request (see pkg/github RequestDeps) and does not use this path. restUATransport := &transport.UserAgentTransport{ Transport: &transport.ETagTransport{Transport: http.DefaultTransport}, - Agent: fmt.Sprintf("github-mcp-server/%s", cfg.Version), + Agent: stdioUserAgent(cfg, nil), } restClient, err := newRESTClient(cfg, restUATransport, restURL.String(), uploadURL.String(), allowedHosts) if err != nil { @@ -102,7 +101,10 @@ func createGitHubClients(cfg github.MCPServerConfig, apiHost utils.APIHostResolv gqlHTTPClient := &http.Client{ Transport: &transport.BearerAuthTransport{ Transport: &transport.GraphQLFeaturesTransport{ - Transport: http.DefaultTransport, + Transport: &transport.UserAgentTransport{ + Transport: http.DefaultTransport, + Agent: stdioUserAgent(cfg, nil), + }, }, Token: cfg.Token, TokenProvider: cfg.TokenProvider, @@ -117,7 +119,7 @@ func createGitHubClients(cfg github.MCPServerConfig, apiHost utils.APIHostResolv // be large and are streamed rather than retained in memory. rawUATransport := &transport.UserAgentTransport{ Transport: http.DefaultTransport, - Agent: fmt.Sprintf("github-mcp-server/%s", cfg.Version), + Agent: stdioUserAgent(cfg, nil), } rawRESTClient, err := newRESTClient(cfg, rawUATransport, restURL.String(), uploadURL.String(), allowedHosts) if err != nil { @@ -141,12 +143,10 @@ func createGitHubClients(cfg github.MCPServerConfig, apiHost utils.APIHostResolv } return &githubClients{ - rest: restClient, - restUATransp: restUATransport, - gql: gqlClient, - gqlHTTP: gqlHTTPClient, - raw: rawClient, - repoAccess: repoAccessCache, + rest: restClient, + gql: gqlClient, + raw: rawClient, + repoAccess: repoAccessCache, }, nil } @@ -232,7 +232,7 @@ func NewStdioMCPServer(ctx context.Context, cfg github.MCPServerConfig) (*mcp.Se return nil, fmt.Errorf("failed to create GitHub MCP server: %w", err) } - ghServer.AddReceivingMiddleware(addUserAgentsMiddleware(cfg, clients.restUATransp, clients.gqlHTTP)) + ghServer.AddReceivingMiddleware(addUserAgentsMiddleware(cfg)) return ghServer, nil } @@ -441,36 +441,28 @@ func createFeatureChecker(enabledFeatures []string, insidersMode bool) inventory } } -func addUserAgentsMiddleware(cfg github.MCPServerConfig, restUATransp *transport.UserAgentTransport, gqlHTTPClient *http.Client) func(next mcp.MethodHandler) mcp.MethodHandler { +func stdioUserAgent(cfg github.MCPServerConfig, client *mcp.Implementation) string { + agent := fmt.Sprintf("github-mcp-server/%s", cfg.Version) + if client != nil { + comment := strconv.Quote(client.Name + "/" + client.Version) + comment = comment[1 : len(comment)-1] + comment = strings.NewReplacer("(", `\(`, ")", `\)`).Replace(comment) + agent += " (" + comment + ")" + } + if cfg.InsidersMode { + agent += " (insiders)" + } + return agent +} + +func addUserAgentsMiddleware(cfg github.MCPServerConfig) func(next mcp.MethodHandler) mcp.MethodHandler { return func(next mcp.MethodHandler) mcp.MethodHandler { return func(ctx context.Context, method string, request mcp.Request) (result mcp.Result, err error) { - if method != "initialize" { - return next(ctx, method, request) - } - - initializeRequest, ok := request.(*mcp.InitializeRequest) - if !ok { - return next(ctx, method, request) + var client *mcp.Implementation + if info, ok := request.(interface{ ClientInfo() *mcp.Implementation }); ok { + client = info.ClientInfo() } - - message := initializeRequest - userAgent := fmt.Sprintf( - "github-mcp-server/%s (%s/%s)", - cfg.Version, - message.Params.ClientInfo.Name, - message.Params.ClientInfo.Version, - ) - if cfg.InsidersMode { - userAgent += " (insiders)" - } - - restUATransp.Agent = userAgent - - gqlHTTPClient.Transport = &transport.UserAgentTransport{ - Transport: gqlHTTPClient.Transport, - Agent: userAgent, - } - + ctx = transport.WithUserAgent(ctx, stdioUserAgent(cfg, client)) return next(ctx, method, request) } } diff --git a/internal/ghmcp/server_test.go b/internal/ghmcp/server_test.go index 6f0e3ac3f3..b0fa1f7544 100644 --- a/internal/ghmcp/server_test.go +++ b/internal/ghmcp/server_test.go @@ -1 +1,251 @@ package ghmcp + +import ( + "context" + "encoding/json" + "net" + "net/http" + "net/http/httptest" + "sync/atomic" + "testing" + "time" + + "github.com/github/github-mcp-server/pkg/github" + "github.com/github/github-mcp-server/pkg/observability" + "github.com/github/github-mcp-server/pkg/observability/metrics" + "github.com/github/github-mcp-server/pkg/translations" + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCreateGitHubClientsGraphQLUserAgent(t *testing.T) { + t.Parallel() + for _, insiders := range []bool{false, true} { + t.Run(map[bool]string{false: "release", true: "insiders"}[insiders], func(t *testing.T) { + headers := make(chan http.Header, 1) + api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + headers <- r.Header.Clone() + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"data":{"viewer":{"login":"octocat"}}}`)) + })) + defer api.Close() + cfg := github.MCPServerConfig{Version: "v1.2.3", Token: "fixture-token", InsidersMode: insiders} + clients, err := createGitHubClients(cfg, newStaticAPIHostResolver(t, api.URL)) + require.NoError(t, err) + var query struct { + Viewer struct{ Login string } + } + require.NoError(t, clients.gql.Query(t.Context(), &query, nil)) + want := "github-mcp-server/v1.2.3" + if insiders { + want += " (insiders)" + } + header := <-headers + assert.Equal(t, want, header.Get("User-Agent")) + assert.Equal(t, "Bearer fixture-token", header.Get("Authorization")) + }) + } +} + +func TestStdioGraphQLUserAgent(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + handshake string + clientInfo *mcp.Implementation + version string + insiders bool + want string + }{ + { + name: "SDK discovery", handshake: "sdk", version: "v1.2.3", + clientInfo: &mcp.Implementation{Name: "test-client", Version: "4.5.6"}, + want: "github-mcp-server/v1.2.3 (test-client/4.5.6)", + }, + { + name: "direct modern call", version: "v1.2.3", + clientInfo: &mcp.Implementation{Name: "test-client", Version: "4.5.6"}, + want: "github-mcp-server/v1.2.3 (test-client/4.5.6)", + }, + { + name: "modern call without optional client info", version: "v1.2.3", + want: "github-mcp-server/v1.2.3", + }, + { + name: "legacy initialize", handshake: "legacy", version: "v1.2.3", + clientInfo: &mcp.Implementation{Name: "test-client", Version: "4.5.6"}, + want: "github-mcp-server/v1.2.3 (test-client/4.5.6)", + }, + { + name: "insiders", version: "v1.2.3", insiders: true, + clientInfo: &mcp.Implementation{Name: "test-client", Version: "4.5.6"}, + want: "github-mcp-server/v1.2.3 (test-client/4.5.6) (insiders)", + }, + { + name: "different server build", version: "v1.2.4", + clientInfo: &mcp.Implementation{Name: "test-client", Version: "4.5.6"}, + want: "github-mcp-server/v1.2.4 (test-client/4.5.6)", + }, + { + name: "HTTP comment delimiters", version: "v1.2.3", + clientInfo: &mcp.Implementation{Name: `Editor (Preview) \client`, Version: `v(1)\build`}, + want: `github-mcp-server/v1.2.3 (Editor \(Preview\) \\client/v\(1\)\\build)`, + }, + { + name: "modern control characters", version: "v1.2.3", + clientInfo: &mcp.Implementation{Name: "editor(\x00)\\client\r\nX-Fake:1", Version: "1.0\tbeta\x7f"}, + want: `github-mcp-server/v1.2.3 (editor\(\x00\)\\client\r\nX-Fake:1/1.0\tbeta\x7f)`, + }, + { + name: "SDK control characters", handshake: "sdk", version: "v1.2.3", insiders: true, + clientInfo: &mcp.Implementation{Name: "editor(\x00)\\client\r\nX-Fake:1", Version: "1.0\tbeta\x7f"}, + want: `github-mcp-server/v1.2.3 (editor\(\x00\)\\client\r\nX-Fake:1/1.0\tbeta\x7f) (insiders)`, + }, + { + name: "legacy control characters", handshake: "legacy", version: "v1.2.3", + clientInfo: &mcp.Implementation{Name: "editor(\x00)\\client\r\nX-Fake:1", Version: "1.0\tbeta\x7f"}, + want: `github-mcp-server/v1.2.3 (editor\(\x00\)\\client\r\nX-Fake:1/1.0\tbeta\x7f)`, + }, + { + name: "spaces and Unicode preserved", version: "v1.2.3", + clientInfo: &mcp.Implementation{Name: "VS Code \u03b2", Version: "\u03b1 1.0"}, + want: "github-mcp-server/v1.2.3 (VS Code \u03b2/\u03b1 1.0)", + }, + { + name: "empty client metadata", version: "v1.2.3", + clientInfo: &mcp.Implementation{}, + want: "github-mcp-server/v1.2.3 (/)", + }, + { + name: "quoted client metadata", version: "v1.2.3", + clientInfo: &mcp.Implementation{Name: `"Editor"`, Version: `v"1`}, + want: `github-mcp-server/v1.2.3 (\"Editor\"/v\"1)`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) + defer cancel() + + headers := make(chan http.Header, 2) + api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + headers <- r.Header.Clone() + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"data":{"repository":{"isPrivate":false,"issues":{"nodes":[],"totalCount":0,"pageInfo":{"hasNextPage":false,"endCursor":""}}}}}`)) + })) + defer api.Close() + + cfg := github.MCPServerConfig{ + Version: tt.version, Token: "fixture-token", InsidersMode: tt.insiders, + Translator: translations.NullTranslationHelper, Logger: discardLogger(), + } + clients, err := createGitHubClients(cfg, newStaticAPIHostResolver(t, api.URL)) + require.NoError(t, err) + obs, err := observability.NewExporters(cfg.Logger, metrics.NewNoopMetrics()) + require.NoError(t, err) + deps := github.NewBaseDeps( + clients.rest, clients.gql, clients.raw, nil, cfg.Translator, + github.FeatureFlags{}, 5000, createFeatureChecker(nil, tt.insiders), obs, + ) + inv, err := github.NewInventory(cfg.Translator). + WithToolsets([]string{}).WithTools([]string{"list_issues"}).Build() + require.NoError(t, err) + server, err := github.NewMCPServer(ctx, &cfg, deps, inv) + require.NoError(t, err) + server.AddReceivingMiddleware(addUserAgentsMiddleware(cfg)) + var initialized atomic.Bool + server.AddReceivingMiddleware(func(next mcp.MethodHandler) mcp.MethodHandler { + return func(ctx context.Context, method string, req mcp.Request) (mcp.Result, error) { + if method == "initialize" { + initialized.Store(true) + } + return next(ctx, method, req) + } + }) + + serverConn, clientConn := net.Pipe() + defer clientConn.Close() + require.NoError(t, clientConn.SetDeadline(time.Now().Add(10*time.Second))) + session, err := server.Connect(ctx, &mcp.IOTransport{Reader: serverConn, Writer: serverConn}, nil) + require.NoError(t, err) + defer session.Close() + params := &mcp.CallToolParams{ + Name: "list_issues", Arguments: map[string]any{"owner": "owner", "repo": "repo"}, + } + var changedClient bool + if tt.handshake == "sdk" { + client := mcp.NewClient(tt.clientInfo, nil) + cs, err := client.Connect(ctx, &mcp.IOTransport{Reader: clientConn, Writer: clientConn}, nil) + require.NoError(t, err) + defer cs.Close() + assert.Equal(t, "2026-07-28", cs.InitializeResult().ProtocolVersion) + result, err := cs.CallTool(ctx, params) + require.NoError(t, err) + require.False(t, result.IsError, "%+v", result.Content) + } else { + encoder, decoder := json.NewEncoder(clientConn), json.NewDecoder(clientConn) + id := 0 + call := func(method string, params any) json.RawMessage { + t.Helper() + id++ + require.NoError(t, encoder.Encode(map[string]any{ + "jsonrpc": "2.0", "id": id, "method": method, "params": params, + })) + var response struct { + Result json.RawMessage `json:"result"` + Error json.RawMessage `json:"error"` + } + require.NoError(t, decoder.Decode(&response)) + require.Empty(t, response.Error) + return response.Result + } + if tt.handshake == "legacy" { + call("initialize", &mcp.InitializeParams{ + ProtocolVersion: "2025-11-25", Capabilities: &mcp.ClientCapabilities{}, + ClientInfo: tt.clientInfo, + }) + require.NoError(t, encoder.Encode(map[string]any{ + "jsonrpc": "2.0", "method": "notifications/initialized", + })) + } else { + params.Meta = mcp.Meta{ + mcp.MetaKeyProtocolVersion: "2026-07-28", mcp.MetaKeyClientCapabilities: map[string]any{}, + } + if tt.clientInfo != nil { + params.Meta[mcp.MetaKeyClientInfo] = tt.clientInfo + } + } + var result mcp.CallToolResult + require.NoError(t, json.Unmarshal(call("tools/call", params), &result)) + require.False(t, result.IsError, "%+v", result.Content) + if tt.handshake != "legacy" { + params.Meta[mcp.MetaKeyClientInfo] = &mcp.Implementation{Name: "other-client", Version: "7.8.9"} + require.NoError(t, json.Unmarshal(call("tools/call", params), &result)) + require.False(t, result.IsError, "%+v", result.Content) + changedClient = true + } + } + + assert.Equal(t, tt.handshake == "legacy", initialized.Load()) + select { + case header := <-headers: + assert.Equal(t, tt.want, header.Get("User-Agent")) + assert.Equal(t, "Bearer fixture-token", header.Get("Authorization")) + assert.Equal(t, "issue_fields, repo_issue_fields", header.Get("GraphQL-Features")) + case <-ctx.Done(): + t.Fatal("GraphQL request did not reach the local HTTP server") + } + if changedClient { + want := "github-mcp-server/" + tt.version + " (other-client/7.8.9)" + if tt.insiders { + want += " (insiders)" + } + assert.Equal(t, want, (<-headers).Get("User-Agent")) + } + }) + } +} diff --git a/internal/oauth/testutil_test.go b/internal/oauth/testutil_test.go index dc34c3841c..5c0ac501f7 100644 --- a/internal/oauth/testutil_test.go +++ b/internal/oauth/testutil_test.go @@ -73,7 +73,7 @@ func (f *fakeGitHub) handleAuthorize(w http.ResponseWriter, r *http.Request) { f.mu.Unlock() redirect := q.Get("redirect_uri") + "?code=authcode&state=" + url.QueryEscape(q.Get("state")) - http.Redirect(w, r, redirect, http.StatusFound) + http.Redirect(w, r, redirect, http.StatusFound) //nolint:gosec // G710: Fake OAuth server redirects to the test client's callback, not an external user's input. } func (f *fakeGitHub) handleToken(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/buffer/buffer_test.go b/pkg/buffer/buffer_test.go index 86308ec5e3..b66c3bc20b 100644 --- a/pkg/buffer/buffer_test.go +++ b/pkg/buffer/buffer_test.go @@ -83,14 +83,14 @@ func TestProcessResponseAsRingBufferToEnd(t *testing.T) { // Ring buffer size is 5, so we should only keep the last 5 lines var sb strings.Builder for i := 1; i <= 10; i++ { - sb.WriteString(fmt.Sprintf("line%d\n", i)) + fmt.Fprintf(&sb, "line%d\n", i) } // Insert an 11MB line (exceeds maxLineSize of 10MB) longLine := strings.Repeat("x", 11*1024*1024) sb.WriteString(longLine) sb.WriteString("\n") for i := 11; i <= 20; i++ { - sb.WriteString(fmt.Sprintf("line%d\n", i)) + fmt.Fprintf(&sb, "line%d\n", i) } resp := &http.Response{ diff --git a/pkg/errors/error.go b/pkg/errors/error.go index cc3e542cc9..c0e45ad1ad 100644 --- a/pkg/errors/error.go +++ b/pkg/errors/error.go @@ -11,7 +11,7 @@ import ( "github.com/github/github-mcp-server/pkg/sanitize" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -165,8 +165,7 @@ func NewGitHubAPIErrorResponse(ctx context.Context, message string, resp *github _, _ = addGitHubAPIErrorToContext(ctx, apiErr) // Explicitly ignore error for graceful handling } - var rateLimitErr *github.RateLimitError - if stderrors.As(err, &rateLimitErr) { + if rateLimitErr, ok := stderrors.AsType[*github.RateLimitError](err); ok { resetTime := rateLimitErr.Rate.Reset.Time if !resetTime.IsZero() { retryIn := time.Until(resetTime).Round(time.Second) @@ -179,8 +178,7 @@ func NewGitHubAPIErrorResponse(ctx context.Context, message string, resp *github "%s: GitHub API rate limit exceeded. Wait before retrying.", message)) } - var abuseErr *github.AbuseRateLimitError - if stderrors.As(err, &abuseErr) { + if abuseErr, ok := stderrors.AsType[*github.AbuseRateLimitError](err); ok { if abuseErr.RetryAfter != nil { retryAfter := abuseErr.RetryAfter.Round(time.Second) if retryAfter > 0 { diff --git a/pkg/errors/error_test.go b/pkg/errors/error_test.go index bdf7cb16bf..dc47b9d58b 100644 --- a/pkg/errors/error_test.go +++ b/pkg/errors/error_test.go @@ -3,7 +3,7 @@ package errors import ( "context" "fmt" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/github/__toolsnaps__/hide_issue_comment.snap b/pkg/github/__toolsnaps__/hide_issue_comment.snap new file mode 100644 index 0000000000..bb66c3fc22 --- /dev/null +++ b/pkg/github/__toolsnaps__/hide_issue_comment.snap @@ -0,0 +1,48 @@ +{ + "annotations": { + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true, + "readOnlyHint": false, + "title": "Hide Issue Comment" + }, + "description": "Hide (minimize) a comment on an issue, or a conversation comment on a pull request. Requires triage or write access to the repository, or being its author.", + "inputSchema": { + "properties": { + "classifier": { + "description": "The reason for hiding the comment", + "enum": [ + "SPAM", + "ABUSE", + "OFF_TOPIC", + "OUTDATED", + "DUPLICATE", + "RESOLVED", + "LOW_QUALITY" + ], + "type": "string" + }, + "comment_id": { + "description": "The numeric ID of the issue or pull request conversation comment", + "minimum": 1, + "type": "number" + }, + "owner": { + "description": "Repository owner (username or organization)", + "type": "string" + }, + "repo": { + "description": "Repository name", + "type": "string" + } + }, + "required": [ + "owner", + "repo", + "comment_id", + "classifier" + ], + "type": "object" + }, + "name": "hide_issue_comment" +} \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/hide_pull_request_review.snap b/pkg/github/__toolsnaps__/hide_pull_request_review.snap new file mode 100644 index 0000000000..09ecfdf6cd --- /dev/null +++ b/pkg/github/__toolsnaps__/hide_pull_request_review.snap @@ -0,0 +1,54 @@ +{ + "annotations": { + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true, + "readOnlyHint": false, + "title": "Hide Pull Request Review" + }, + "description": "Hide (minimize) the body of a submitted pull request review. Requires triage or write access to the repository, or being its author.", + "inputSchema": { + "properties": { + "classifier": { + "description": "The reason for hiding the comment", + "enum": [ + "SPAM", + "ABUSE", + "OFF_TOPIC", + "OUTDATED", + "DUPLICATE", + "RESOLVED", + "LOW_QUALITY" + ], + "type": "string" + }, + "owner": { + "description": "Repository owner (username or organization)", + "type": "string" + }, + "pullNumber": { + "description": "The pull request number", + "minimum": 1, + "type": "number" + }, + "repo": { + "description": "Repository name", + "type": "string" + }, + "review_id": { + "description": "The numeric ID of the pull request review", + "minimum": 1, + "type": "number" + } + }, + "required": [ + "owner", + "repo", + "pullNumber", + "review_id", + "classifier" + ], + "type": "object" + }, + "name": "hide_pull_request_review" +} \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/hide_pull_request_review_comment.snap b/pkg/github/__toolsnaps__/hide_pull_request_review_comment.snap new file mode 100644 index 0000000000..46c39c9885 --- /dev/null +++ b/pkg/github/__toolsnaps__/hide_pull_request_review_comment.snap @@ -0,0 +1,48 @@ +{ + "annotations": { + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true, + "readOnlyHint": false, + "title": "Hide Pull Request Review Comment" + }, + "description": "Hide (minimize) an inline review comment on a pull request diff. Requires triage or write access to the repository, or being its author.", + "inputSchema": { + "properties": { + "classifier": { + "description": "The reason for hiding the comment", + "enum": [ + "SPAM", + "ABUSE", + "OFF_TOPIC", + "OUTDATED", + "DUPLICATE", + "RESOLVED", + "LOW_QUALITY" + ], + "type": "string" + }, + "comment_id": { + "description": "The numeric pull request review comment ID. Use the number from a #discussion_r... anchor, not the GraphQL thread node ID (PRRT_...).", + "minimum": 1, + "type": "number" + }, + "owner": { + "description": "Repository owner (username or organization)", + "type": "string" + }, + "repo": { + "description": "Repository name", + "type": "string" + } + }, + "required": [ + "owner", + "repo", + "comment_id", + "classifier" + ], + "type": "object" + }, + "name": "hide_pull_request_review_comment" +} \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/unhide_issue_comment.snap b/pkg/github/__toolsnaps__/unhide_issue_comment.snap new file mode 100644 index 0000000000..ea258d12f3 --- /dev/null +++ b/pkg/github/__toolsnaps__/unhide_issue_comment.snap @@ -0,0 +1,34 @@ +{ + "annotations": { + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true, + "readOnlyHint": false, + "title": "Unhide Issue Comment" + }, + "description": "Unhide (unminimize) a previously hidden comment on an issue, or conversation comment on a pull request. Requires triage or write access to the repository, or being its author.", + "inputSchema": { + "properties": { + "comment_id": { + "description": "The numeric ID of the issue or pull request conversation comment", + "minimum": 1, + "type": "number" + }, + "owner": { + "description": "Repository owner (username or organization)", + "type": "string" + }, + "repo": { + "description": "Repository name", + "type": "string" + } + }, + "required": [ + "owner", + "repo", + "comment_id" + ], + "type": "object" + }, + "name": "unhide_issue_comment" +} \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/unhide_pull_request_review.snap b/pkg/github/__toolsnaps__/unhide_pull_request_review.snap new file mode 100644 index 0000000000..2e627d519f --- /dev/null +++ b/pkg/github/__toolsnaps__/unhide_pull_request_review.snap @@ -0,0 +1,40 @@ +{ + "annotations": { + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true, + "readOnlyHint": false, + "title": "Unhide Pull Request Review" + }, + "description": "Unhide (unminimize) the previously hidden body of a submitted pull request review. Requires triage or write access to the repository, or being its author.", + "inputSchema": { + "properties": { + "owner": { + "description": "Repository owner (username or organization)", + "type": "string" + }, + "pullNumber": { + "description": "The pull request number", + "minimum": 1, + "type": "number" + }, + "repo": { + "description": "Repository name", + "type": "string" + }, + "review_id": { + "description": "The numeric ID of the pull request review", + "minimum": 1, + "type": "number" + } + }, + "required": [ + "owner", + "repo", + "pullNumber", + "review_id" + ], + "type": "object" + }, + "name": "unhide_pull_request_review" +} \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/unhide_pull_request_review_comment.snap b/pkg/github/__toolsnaps__/unhide_pull_request_review_comment.snap new file mode 100644 index 0000000000..38ff864f15 --- /dev/null +++ b/pkg/github/__toolsnaps__/unhide_pull_request_review_comment.snap @@ -0,0 +1,34 @@ +{ + "annotations": { + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true, + "readOnlyHint": false, + "title": "Unhide Pull Request Review Comment" + }, + "description": "Unhide (unminimize) a previously hidden inline review comment on a pull request diff. Requires triage or write access to the repository, or being its author.", + "inputSchema": { + "properties": { + "comment_id": { + "description": "The numeric pull request review comment ID. Use the number from a #discussion_r... anchor, not the GraphQL thread node ID (PRRT_...).", + "minimum": 1, + "type": "number" + }, + "owner": { + "description": "Repository owner (username or organization)", + "type": "string" + }, + "repo": { + "description": "Repository name", + "type": "string" + } + }, + "required": [ + "owner", + "repo", + "comment_id" + ], + "type": "object" + }, + "name": "unhide_pull_request_review_comment" +} \ No newline at end of file diff --git a/pkg/github/actions.go b/pkg/github/actions.go index 3f5654db10..f40d8a31d3 100644 --- a/pkg/github/actions.go +++ b/pkg/github/actions.go @@ -17,7 +17,7 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -1103,8 +1103,7 @@ func rerunFailedJobs(ctx context.Context, client *github.Client, owner, repo str func cancelWorkflowRun(ctx context.Context, client *github.Client, owner, repo string, runID int64) (*mcp.CallToolResult, any, error) { resp, err := client.Actions.CancelWorkflowRunByID(ctx, owner, repo, runID) if err != nil { - var acceptedErr *github.AcceptedError - if !errors.As(err, &acceptedErr) { + if _, ok := errors.AsType[*github.AcceptedError](err); !ok { return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to cancel workflow run", resp, err), nil, nil } } diff --git a/pkg/github/actions_minimal_test.go b/pkg/github/actions_minimal_test.go index 4f0f8bd977..a2d547b33f 100644 --- a/pkg/github/actions_minimal_test.go +++ b/pkg/github/actions_minimal_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -79,7 +79,7 @@ func TestConvertToMinimalWorkflowJob(t *testing.T) { func TestConvertToMinimalActionsLists(t *testing.T) { t.Run("workflow runs", func(t *testing.T) { result := convertToMinimalWorkflowRuns(&github.WorkflowRuns{ - TotalCount: github.Ptr(2), + TotalCount: new(2), WorkflowRuns: []*github.WorkflowRun{actionsTestWorkflowRun(), nil}, }) assert.Equal(t, 2, result.TotalCount) @@ -88,7 +88,7 @@ func TestConvertToMinimalActionsLists(t *testing.T) { t.Run("workflow jobs", func(t *testing.T) { result := convertToMinimalWorkflowJobs(&github.Jobs{ - TotalCount: github.Ptr(2), + TotalCount: new(2), Jobs: []*github.WorkflowJob{actionsTestWorkflowJob(), nil}, }) assert.Equal(t, 2, result.TotalCount) @@ -110,94 +110,94 @@ func TestConvertToMinimalActionsLists(t *testing.T) { func actionsTestWorkflowRun() *github.WorkflowRun { repository := &github.Repository{ - ID: github.Ptr(int64(1296269)), - NodeID: github.Ptr("MDEwOlJlcG9zaXRvcnkxMjk2MjY5"), - Name: github.Ptr("octo-repo"), - FullName: github.Ptr("octo-org/octo-repo"), - Description: github.Ptr("A representative repository description included in the full API response."), - HTMLURL: github.Ptr("https://github.com/octo-org/octo-repo"), - URL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo"), - CloneURL: github.Ptr("https://github.com/octo-org/octo-repo.git"), - Language: github.Ptr("Go"), + ID: new(int64(1296269)), + NodeID: new("MDEwOlJlcG9zaXRvcnkxMjk2MjY5"), + Name: new("octo-repo"), + FullName: new("octo-org/octo-repo"), + Description: new("A representative repository description included in the full API response."), + HTMLURL: new("https://github.com/octo-org/octo-repo"), + URL: new("https://api.github.com/repos/octo-org/octo-repo"), + CloneURL: new("https://github.com/octo-org/octo-repo.git"), + Language: new("Go"), Topics: []string{"actions", "mcp", "automation"}, } return &github.WorkflowRun{ - ID: github.Ptr(int64(30433642)), - Name: github.Ptr("CI"), - NodeID: github.Ptr("MDEyOldvcmtmbG93IFJ1bjI2OTI4OQ=="), - HeadBranch: github.Ptr("feature/minimal-actions"), - HeadSHA: github.Ptr("acb5820ced9479c074f688cc328bf03f341a511d"), - Path: github.Ptr(".github/workflows/ci.yml"), - RunNumber: github.Ptr(562), - RunAttempt: github.Ptr(2), - Event: github.Ptr("pull_request"), - DisplayTitle: github.Ptr("Reduce GitHub Actions response payloads"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("failure"), - WorkflowID: github.Ptr(int64(161335)), - CheckSuiteID: github.Ptr(int64(42)), - CheckSuiteNodeID: github.Ptr("MDEwOkNoZWNrU3VpdGU0Mg=="), - URL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642"), - HTMLURL: github.Ptr("https://github.com/octo-org/octo-repo/actions/runs/30433642"), - JobsURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/jobs"), - LogsURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/logs"), - CheckSuiteURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/check-suites/42"), - ArtifactsURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/artifacts"), - CancelURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/cancel"), - RerunURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/rerun"), - PreviousAttemptURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/attempts/1"), - WorkflowURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/workflows/161335"), + ID: new(int64(30433642)), + Name: new("CI"), + NodeID: new("MDEyOldvcmtmbG93IFJ1bjI2OTI4OQ=="), + HeadBranch: new("feature/minimal-actions"), + HeadSHA: new("acb5820ced9479c074f688cc328bf03f341a511d"), + Path: new(".github/workflows/ci.yml"), + RunNumber: new(562), + RunAttempt: new(2), + Event: new("pull_request"), + DisplayTitle: new("Reduce GitHub Actions response payloads"), + Status: new("completed"), + Conclusion: new("failure"), + WorkflowID: new(int64(161335)), + CheckSuiteID: new(int64(42)), + CheckSuiteNodeID: new("MDEwOkNoZWNrU3VpdGU0Mg=="), + URL: new("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642"), + HTMLURL: new("https://github.com/octo-org/octo-repo/actions/runs/30433642"), + JobsURL: new("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/jobs"), + LogsURL: new("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/logs"), + CheckSuiteURL: new("https://api.github.com/repos/octo-org/octo-repo/check-suites/42"), + ArtifactsURL: new("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/artifacts"), + CancelURL: new("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/cancel"), + RerunURL: new("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/rerun"), + PreviousAttemptURL: new("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/attempts/1"), + WorkflowURL: new("https://api.github.com/repos/octo-org/octo-repo/actions/workflows/161335"), Repository: repository, HeadRepository: repository, Actor: &github.User{ - Login: github.Ptr("octocat"), - ID: github.Ptr(int64(1)), - NodeID: github.Ptr("MDQ6VXNlcjE="), - AvatarURL: github.Ptr("https://github.com/images/error/octocat_happy.gif"), - HTMLURL: github.Ptr("https://github.com/octocat"), - URL: github.Ptr("https://api.github.com/users/octocat"), - Name: github.Ptr("The Octocat"), - Bio: github.Ptr("A long biography that is not needed to identify the workflow run actor."), + Login: new("octocat"), + ID: new(int64(1)), + NodeID: new("MDQ6VXNlcjE="), + AvatarURL: new("https://github.com/images/error/octocat_happy.gif"), + HTMLURL: new("https://github.com/octocat"), + URL: new("https://api.github.com/users/octocat"), + Name: new("The Octocat"), + Bio: new("A long biography that is not needed to identify the workflow run actor."), }, TriggeringActor: &github.User{ - Login: github.Ptr("hubot"), - ID: github.Ptr(int64(2)), - HTMLURL: github.Ptr("https://github.com/hubot"), - URL: github.Ptr("https://api.github.com/users/hubot"), + Login: new("hubot"), + ID: new(int64(2)), + HTMLURL: new("https://github.com/hubot"), + URL: new("https://api.github.com/users/hubot"), }, PullRequests: []*github.PullRequest{ { - ID: github.Ptr(int64(1001)), - Number: github.Ptr(42), - Title: github.Ptr("Reduce GitHub Actions response payloads"), - Body: github.Ptr("A pull request body that is unnecessary in a workflow run response."), - HTMLURL: github.Ptr("https://github.com/octo-org/octo-repo/pull/42"), + ID: new(int64(1001)), + Number: new(42), + Title: new("Reduce GitHub Actions response payloads"), + Body: new("A pull request body that is unnecessary in a workflow run response."), + HTMLURL: new("https://github.com/octo-org/octo-repo/pull/42"), Head: &github.PullRequestBranch{ - Ref: github.Ptr("feature/minimal-actions"), - SHA: github.Ptr("acb5820ced9479c074f688cc328bf03f341a511d"), + Ref: new("feature/minimal-actions"), + SHA: new("acb5820ced9479c074f688cc328bf03f341a511d"), Repo: repository, }, Base: &github.PullRequestBranch{ - Ref: github.Ptr("main"), - SHA: github.Ptr("9a2f3ec"), + Ref: new("main"), + SHA: new("9a2f3ec"), Repo: repository, }, }, }, HeadCommit: &github.HeadCommit{ - Message: github.Ptr("Reduce GitHub Actions response payloads"), - URL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/commits/acb5820"), + Message: new("Reduce GitHub Actions response payloads"), + URL: new("https://api.github.com/repos/octo-org/octo-repo/commits/acb5820"), Author: &github.CommitAuthor{ - Name: github.Ptr("The Octocat"), - Email: github.Ptr("octocat@example.com"), + Name: new("The Octocat"), + Email: new("octocat@example.com"), }, }, ReferencedWorkflows: []*github.ReferencedWorkflow{ { - Path: github.Ptr(".github/workflows/reusable-tests.yml"), - SHA: github.Ptr("9f4f87d9790ab0f5c2c5ad2b74b886cab515a886"), - Ref: github.Ptr("refs/tags/v3"), + Path: new(".github/workflows/reusable-tests.yml"), + SHA: new("9f4f87d9790ab0f5c2c5ad2b74b886cab515a886"), + Ref: new("refs/tags/v3"), }, nil, }, @@ -209,42 +209,42 @@ func actionsTestWorkflowRun() *github.WorkflowRun { func actionsTestWorkflowJob() *github.WorkflowJob { return &github.WorkflowJob{ - ID: github.Ptr(int64(399444496)), - RunID: github.Ptr(int64(30433642)), - RunURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642"), - NodeID: github.Ptr("MDEyOldvcmtmbG93IEpvYjM5OTQ0NDQ5Ng=="), - HeadBranch: github.Ptr("feature/minimal-actions"), - HeadSHA: github.Ptr("acb5820ced9479c074f688cc328bf03f341a511d"), - URL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/actions/jobs/399444496"), - HTMLURL: github.Ptr("https://github.com/octo-org/octo-repo/runs/399444496"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("failure"), + ID: new(int64(399444496)), + RunID: new(int64(30433642)), + RunURL: new("https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642"), + NodeID: new("MDEyOldvcmtmbG93IEpvYjM5OTQ0NDQ5Ng=="), + HeadBranch: new("feature/minimal-actions"), + HeadSHA: new("acb5820ced9479c074f688cc328bf03f341a511d"), + URL: new("https://api.github.com/repos/octo-org/octo-repo/actions/jobs/399444496"), + HTMLURL: new("https://github.com/octo-org/octo-repo/runs/399444496"), + Status: new("completed"), + Conclusion: new("failure"), CreatedAt: actionsTestTimestamp(), StartedAt: actionsTestTimestamp(), CompletedAt: actionsTestTimestamp(), - Name: github.Ptr("test (ubuntu-latest, Go 1.24)"), - CheckRunURL: github.Ptr("https://api.github.com/repos/octo-org/octo-repo/check-runs/399444496"), + Name: new("test (ubuntu-latest, Go 1.24)"), + CheckRunURL: new("https://api.github.com/repos/octo-org/octo-repo/check-runs/399444496"), Labels: []string{"ubuntu-latest", "x64"}, - RunnerID: github.Ptr(int64(1)), - RunnerName: github.Ptr("GitHub Actions 1"), - RunnerGroupID: github.Ptr(int64(2)), - RunnerGroupName: github.Ptr("GitHub Actions"), - RunAttempt: github.Ptr(int64(2)), - WorkflowName: github.Ptr("CI"), + RunnerID: new(int64(1)), + RunnerName: new("GitHub Actions 1"), + RunnerGroupID: new(int64(2)), + RunnerGroupName: new("GitHub Actions"), + RunAttempt: new(int64(2)), + WorkflowName: new("CI"), Steps: []*github.TaskStep{ { - Name: github.Ptr("Set up job"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("success"), - Number: github.Ptr(int64(1)), + Name: new("Set up job"), + Status: new("completed"), + Conclusion: new("success"), + Number: new(int64(1)), StartedAt: actionsTestTimestamp(), CompletedAt: actionsTestTimestamp(), }, { - Name: github.Ptr("Run tests"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("failure"), - Number: github.Ptr(int64(2)), + Name: new("Run tests"), + Status: new("completed"), + Conclusion: new("failure"), + Number: new(int64(2)), StartedAt: actionsTestTimestamp(), CompletedAt: actionsTestTimestamp(), }, diff --git a/pkg/github/actions_test.go b/pkg/github/actions_test.go index f390c31771..1490389a58 100644 --- a/pkg/github/actions_test.go +++ b/pkg/github/actions_test.go @@ -9,7 +9,7 @@ import ( "github.com/github/github-mcp-server/internal/toolsnaps" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -46,19 +46,19 @@ func Test_ActionsList_ListWorkflows(t *testing.T) { mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposActionsWorkflowsByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { workflows := &github.Workflows{ - TotalCount: github.Ptr(2), + TotalCount: new(2), Workflows: []*github.Workflow{ { - ID: github.Ptr(int64(1)), - Name: github.Ptr("CI"), - Path: github.Ptr(".github/workflows/ci.yml"), - State: github.Ptr("active"), + ID: new(int64(1)), + Name: new("CI"), + Path: new(".github/workflows/ci.yml"), + State: new("active"), }, { - ID: github.Ptr(int64(2)), - Name: github.Ptr("Deploy"), - Path: github.Ptr(".github/workflows/deploy.yml"), - State: github.Ptr("active"), + ID: new(int64(2)), + Name: new("Deploy"), + Path: new(".github/workflows/deploy.yml"), + State: new("active"), }, }, } @@ -122,13 +122,13 @@ func Test_ActionsList_ListWorkflowRuns(t *testing.T) { mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposActionsWorkflowsRunsByOwnerByRepoByWorkflowID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { runs := &github.WorkflowRuns{ - TotalCount: github.Ptr(1), + TotalCount: new(1), WorkflowRuns: []*github.WorkflowRun{ { - ID: github.Ptr(int64(123)), - Name: github.Ptr("CI"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("success"), + ID: new(int64(123)), + Name: new("CI"), + Status: new("completed"), + Conclusion: new("success"), }, }, } @@ -167,18 +167,18 @@ func Test_ActionsList_ListWorkflowRuns(t *testing.T) { mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposActionsRunsByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { runs := &github.WorkflowRuns{ - TotalCount: github.Ptr(2), + TotalCount: new(2), WorkflowRuns: []*github.WorkflowRun{ { - ID: github.Ptr(int64(123)), - Name: github.Ptr("CI"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("success"), + ID: new(int64(123)), + Name: new("CI"), + Status: new("completed"), + Conclusion: new("success"), }, { - ID: github.Ptr(int64(456)), - Name: github.Ptr("Deploy"), - Status: github.Ptr("in_progress"), + ID: new(int64(456)), + Name: new("Deploy"), + Status: new("in_progress"), Conclusion: nil, }, }, @@ -217,7 +217,7 @@ func Test_ActionsList_ListWorkflowJobs(t *testing.T) { toolDef := ActionsList(translations.NullTranslationHelper) mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposActionsRunsJobsByOwnerByRepoByRunID: mockResponse(t, http.StatusOK, &github.Jobs{ - TotalCount: github.Ptr(1), + TotalCount: new(1), Jobs: []*github.WorkflowJob{actionsTestWorkflowJob()}, }), }) @@ -268,10 +268,10 @@ func Test_ActionsGet_GetWorkflow(t *testing.T) { mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposActionsWorkflowsByOwnerByRepoByWorkflowID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { workflow := &github.Workflow{ - ID: github.Ptr(int64(1)), - Name: github.Ptr("CI"), - Path: github.Ptr(".github/workflows/ci.yml"), - State: github.Ptr("active"), + ID: new(int64(1)), + Name: new("CI"), + Path: new(".github/workflows/ci.yml"), + State: new("active"), } w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(workflow) @@ -651,22 +651,22 @@ func Test_ActionsGetJobLogs_FailedJobs(t *testing.T) { mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposActionsRunsJobsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { jobs := &github.Jobs{ - TotalCount: github.Ptr(3), + TotalCount: new(3), Jobs: []*github.WorkflowJob{ { - ID: github.Ptr(int64(1)), - Name: github.Ptr("test-job-1"), - Conclusion: github.Ptr("success"), + ID: new(int64(1)), + Name: new("test-job-1"), + Conclusion: new("success"), }, { - ID: github.Ptr(int64(2)), - Name: github.Ptr("test-job-2"), - Conclusion: github.Ptr("failure"), + ID: new(int64(2)), + Name: new("test-job-2"), + Conclusion: new("failure"), }, { - ID: github.Ptr(int64(3)), - Name: github.Ptr("test-job-3"), - Conclusion: github.Ptr("failure"), + ID: new(int64(3)), + Name: new("test-job-3"), + Conclusion: new("failure"), }, }, } @@ -710,17 +710,17 @@ func Test_ActionsGetJobLogs_FailedJobs(t *testing.T) { mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposActionsRunsJobsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { jobs := &github.Jobs{ - TotalCount: github.Ptr(2), + TotalCount: new(2), Jobs: []*github.WorkflowJob{ { - ID: github.Ptr(int64(1)), - Name: github.Ptr("test-job-1"), - Conclusion: github.Ptr("success"), + ID: new(int64(1)), + Name: new("test-job-1"), + Conclusion: new("success"), }, { - ID: github.Ptr(int64(2)), - Name: github.Ptr("test-job-2"), - Conclusion: github.Ptr("success"), + ID: new(int64(2)), + Name: new("test-job-2"), + Conclusion: new("success"), }, }, } diff --git a/pkg/github/code_quality_test.go b/pkg/github/code_quality_test.go index e84e1c5e33..edd2ce65f3 100644 --- a/pkg/github/code_quality_test.go +++ b/pkg/github/code_quality_test.go @@ -6,7 +6,7 @@ import ( "net/http" "testing" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -65,11 +65,11 @@ func Test_GetCodeQualityFinding(t *testing.T) { // Setup mock finding for success case mockFinding := &codeQualityFinding{ - Number: github.Ptr(42), - State: github.Ptr("open"), + Number: new(42), + State: new("open"), Rule: &codeQualityRule{ - ID: github.Ptr("test-rule"), - Description: github.Ptr("Test Rule Description"), + ID: new("test-rule"), + Description: new("Test Rule Description"), }, } diff --git a/pkg/github/code_scanning.go b/pkg/github/code_scanning.go index 12b0b3f1e2..f2c4834e65 100644 --- a/pkg/github/code_scanning.go +++ b/pkg/github/code_scanning.go @@ -12,7 +12,7 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/github/code_scanning_test.go b/pkg/github/code_scanning_test.go index c9978d8ac5..4811b66f5a 100644 --- a/pkg/github/code_scanning_test.go +++ b/pkg/github/code_scanning_test.go @@ -8,7 +8,7 @@ import ( "github.com/github/github-mcp-server/internal/toolsnaps" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -32,10 +32,10 @@ func Test_GetCodeScanningAlert(t *testing.T) { // Setup mock alert for success case mockAlert := &github.Alert{ - Number: github.Ptr(42), - State: github.Ptr("open"), - Rule: &github.Rule{ID: github.Ptr("test-rule"), Description: github.Ptr("Test Rule Description")}, - HTMLURL: github.Ptr("https://github.com/owner/repo/security/code-scanning/42"), + Number: new(42), + State: new("open"), + Rule: &github.Rule{ID: new("test-rule"), Description: new("Test Rule Description")}, + HTMLURL: new("https://github.com/owner/repo/security/code-scanning/42"), } tests := []struct { @@ -144,16 +144,16 @@ func Test_ListCodeScanningAlerts(t *testing.T) { // Setup mock alerts for success case mockAlerts := []*github.Alert{ { - Number: github.Ptr(42), - State: github.Ptr("open"), - Rule: &github.Rule{ID: github.Ptr("test-rule-1"), Description: github.Ptr("Test Rule 1")}, - HTMLURL: github.Ptr("https://github.com/owner/repo/security/code-scanning/42"), + Number: new(42), + State: new("open"), + Rule: &github.Rule{ID: new("test-rule-1"), Description: new("Test Rule 1")}, + HTMLURL: new("https://github.com/owner/repo/security/code-scanning/42"), }, { - Number: github.Ptr(43), - State: github.Ptr("fixed"), - Rule: &github.Rule{ID: github.Ptr("test-rule-2"), Description: github.Ptr("Test Rule 2")}, - HTMLURL: github.Ptr("https://github.com/owner/repo/security/code-scanning/43"), + Number: new(43), + State: new("fixed"), + Rule: &github.Rule{ID: new("test-rule-2"), Description: new("Test Rule 2")}, + HTMLURL: new("https://github.com/owner/repo/security/code-scanning/43"), }, } diff --git a/pkg/github/comment_minimize.go b/pkg/github/comment_minimize.go new file mode 100644 index 0000000000..43eb0134d7 --- /dev/null +++ b/pkg/github/comment_minimize.go @@ -0,0 +1,323 @@ +package github + +import ( + "context" + "encoding/json" + "fmt" + "maps" + "slices" + "strings" + + ghErrors "github.com/github/github-mcp-server/pkg/errors" + "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" + "github.com/github/github-mcp-server/pkg/translations" + "github.com/github/github-mcp-server/pkg/utils" + "github.com/google/go-github/v92/github" + "github.com/google/jsonschema-go/jsonschema" + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/shurcooL/githubv4" +) + +// MinimizeCommentResult is the response returned by the hide and unhide comment tools. +type MinimizeCommentResult struct { + NodeID string `json:"node_id"` + IsMinimized bool `json:"is_minimized"` + MinimizedReason string `json:"minimized_reason,omitempty"` +} + +var commentClassifiers = []any{"SPAM", "ABUSE", "OFF_TOPIC", "OUTDATED", "DUPLICATE", "RESOLVED", "LOW_QUALITY"} + +const commentVisibilityPermissionNote = " Requires triage or write access to the repository, or being its author." + +// commentVisibilityTarget describes one kind of hideable object: how the tools that +// hide and unhide it are named and described, and how to find its GraphQL node ID. +type commentVisibilityTarget struct { + toolset inventory.ToolsetMetadata + featureRule inventory.FeatureRule + // name is appended to "hide_" and "unhide_" to form the tool names. + name string + title string + hideDescription string + unhideDescription string + properties func() map[string]*jsonschema.Schema + required []string + resolveNodeID func(ctx context.Context, client *github.Client, owner, repo string, args map[string]any) (string, *mcp.CallToolResult) +} + +var issueCommentVisibilityTarget = commentVisibilityTarget{ + toolset: ToolsetMetadataIssues, + featureRule: issuesGranularFeatureRule, + name: "issue_comment", + title: "Issue Comment", + hideDescription: "Hide (minimize) a comment on an issue, or a conversation comment on a pull request.", + unhideDescription: "Unhide (unminimize) a previously hidden comment on an issue, or conversation comment on a pull request.", + properties: func() map[string]*jsonschema.Schema { + return map[string]*jsonschema.Schema{ + "comment_id": { + Type: "number", + Description: "The numeric ID of the issue or pull request conversation comment", + Minimum: jsonschema.Ptr(1.0), + }, + } + }, + required: []string{"comment_id"}, + resolveNodeID: func(ctx context.Context, client *github.Client, owner, repo string, args map[string]any) (string, *mcp.CallToolResult) { + commentID, err := requiredPositiveBigInt(args, "comment_id") + if err != nil { + return "", utils.NewToolResultError(err.Error()) + } + comment, resp, err := client.Issues.GetComment(ctx, owner, repo, commentID) + return nodeIDFromResponse(ctx, "failed to get issue comment", comment.GetNodeID(), resp, err) + }, +} + +var pullRequestReviewCommentVisibilityTarget = commentVisibilityTarget{ + toolset: ToolsetMetadataPullRequests, + featureRule: pullRequestsGranularFeatureRule, + name: "pull_request_review_comment", + title: "Pull Request Review Comment", + hideDescription: "Hide (minimize) an inline review comment on a pull request diff.", + unhideDescription: "Unhide (unminimize) a previously hidden inline review comment on a pull request diff.", + properties: func() map[string]*jsonschema.Schema { + return map[string]*jsonschema.Schema{ + "comment_id": { + Type: "number", + Description: "The numeric pull request review comment ID. Use the number from a #discussion_r... anchor, not the GraphQL thread node ID (PRRT_...).", + Minimum: jsonschema.Ptr(1.0), + }, + } + }, + required: []string{"comment_id"}, + resolveNodeID: func(ctx context.Context, client *github.Client, owner, repo string, args map[string]any) (string, *mcp.CallToolResult) { + commentID, err := requiredPositiveBigInt(args, "comment_id") + if err != nil { + return "", utils.NewToolResultError(err.Error()) + } + comment, resp, err := client.PullRequests.GetComment(ctx, owner, repo, commentID) + return nodeIDFromResponse(ctx, "failed to get pull request review comment", comment.GetNodeID(), resp, err) + }, +} + +var pullRequestReviewVisibilityTarget = commentVisibilityTarget{ + toolset: ToolsetMetadataPullRequests, + featureRule: pullRequestsGranularFeatureRule, + name: "pull_request_review", + title: "Pull Request Review", + hideDescription: "Hide (minimize) the body of a submitted pull request review.", + unhideDescription: "Unhide (unminimize) the previously hidden body of a submitted pull request review.", + properties: func() map[string]*jsonschema.Schema { + return map[string]*jsonschema.Schema{ + "pullNumber": { + Type: "number", + Description: "The pull request number", + Minimum: jsonschema.Ptr(1.0), + }, + "review_id": { + Type: "number", + Description: "The numeric ID of the pull request review", + Minimum: jsonschema.Ptr(1.0), + }, + } + }, + required: []string{"pullNumber", "review_id"}, + resolveNodeID: func(ctx context.Context, client *github.Client, owner, repo string, args map[string]any) (string, *mcp.CallToolResult) { + pullNumber, err := RequiredInt(args, "pullNumber") + if err != nil { + return "", utils.NewToolResultError(err.Error()) + } + if pullNumber < 1 { + return "", utils.NewToolResultError("pullNumber must be greater than 0") + } + reviewID, err := requiredPositiveBigInt(args, "review_id") + if err != nil { + return "", utils.NewToolResultError(err.Error()) + } + review, resp, err := client.PullRequests.GetReview(ctx, owner, repo, pullNumber, reviewID) + return nodeIDFromResponse(ctx, "failed to get pull request review", review.GetNodeID(), resp, err) + }, +} + +// commentVisibilityTool builds the hide_ tool when hide is true, and the unhide_ tool otherwise. +func commentVisibilityTool(t translations.TranslationHelperFunc, target commentVisibilityTarget, hide bool) inventory.ServerTool { + action, titleAction, description := "unhide", "Unhide", target.unhideDescription + if hide { + action, titleAction, description = "hide", "Hide", target.hideDescription + } + name := action + "_" + target.name + + properties := map[string]*jsonschema.Schema{ + "owner": { + Type: "string", + Description: "Repository owner (username or organization)", + }, + "repo": { + Type: "string", + Description: "Repository name", + }, + } + maps.Copy(properties, target.properties()) + required := append([]string{"owner", "repo"}, target.required...) + if hide { + properties["classifier"] = &jsonschema.Schema{ + Type: "string", + Description: "The reason for hiding the comment", + Enum: commentClassifiers, + } + required = append(required, "classifier") + } + + st := NewTool( + target.toolset, + mcp.Tool{ + Name: name, + Description: t("TOOL_"+strings.ToUpper(name)+"_DESCRIPTION", description+commentVisibilityPermissionNote), + Annotations: &mcp.ToolAnnotations{ + Title: t("TOOL_"+strings.ToUpper(name)+"_USER_TITLE", titleAction+" "+target.title), + ReadOnlyHint: false, + DestructiveHint: jsonschema.Ptr(false), + OpenWorldHint: jsonschema.Ptr(true), + }, + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: properties, + Required: required, + }, + }, + scopes.RequireAll(scopes.Repo), + func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { + return setCommentVisibility(ctx, deps, target, args, hide), nil, nil + }, + ) + st.FeatureRule = target.featureRule + return st +} + +// setCommentVisibility resolves the object identified by args and hides or unhides it. +func setCommentVisibility(ctx context.Context, deps ToolDependencies, target commentVisibilityTarget, args map[string]any, hide bool) *mcp.CallToolResult { + owner, err := RequiredParam[string](args, "owner") + if err != nil { + return utils.NewToolResultError(err.Error()) + } + repo, err := RequiredParam[string](args, "repo") + if err != nil { + return utils.NewToolResultError(err.Error()) + } + var classifier string + if hide { + classifier, err = RequiredParam[string](args, "classifier") + if err != nil { + return utils.NewToolResultError(err.Error()) + } + classifier = strings.ToUpper(classifier) + if !slices.Contains(commentClassifiers, any(classifier)) { + return utils.NewToolResultError(fmt.Sprintf("invalid classifier %q: must be one of %v", classifier, commentClassifiers)) + } + } + + client, err := deps.GetClient(ctx) + if err != nil { + return utils.NewToolResultErrorFromErr("failed to get GitHub client", err) + } + + nodeID, errResult := target.resolveNodeID(ctx, client, owner, repo, args) + if errResult != nil { + return errResult + } + + gqlClient, err := deps.GetGQLClient(ctx) + if err != nil { + return utils.NewToolResultErrorFromErr("failed to get GitHub GraphQL client", err) + } + + var result MinimizeCommentResult + if hide { + result, errResult = minimizeComment(ctx, gqlClient, nodeID, classifier) + } else { + result, errResult = unminimizeComment(ctx, gqlClient, nodeID) + } + if errResult != nil { + return errResult + } + + r, err := json.Marshal(result) + if err != nil { + return utils.NewToolResultErrorFromErr("failed to marshal response", err) + } + return utils.NewToolResultText(string(r)) +} + +// requiredPositiveBigInt reads a required ID argument. The schema's minimum is not enforced +// when arguments are unmarshalled, so negative values are rejected here before any API call. +func requiredPositiveBigInt(args map[string]any, p string) (int64, error) { + v, err := RequiredBigInt(args, p) + if err != nil { + return 0, err + } + if v < 1 { + return 0, fmt.Errorf("%s must be greater than 0", p) + } + return v, nil +} + +// nodeIDFromResponse turns the result of a REST lookup into a GraphQL node ID, since the +// minimize mutations only accept node IDs. +func nodeIDFromResponse(ctx context.Context, errMessage, nodeID string, resp *github.Response, err error) (string, *mcp.CallToolResult) { + if resp != nil && resp.Body != nil { + defer func() { _ = resp.Body.Close() }() + } + if err != nil { + return "", ghErrors.NewGitHubAPIErrorResponse(ctx, errMessage, resp, err) + } + if nodeID == "" { + return "", utils.NewToolResultError(errMessage + ": response has no node ID") + } + return nodeID, nil +} + +func minimizeComment(ctx context.Context, client *githubv4.Client, nodeID, classifier string) (MinimizeCommentResult, *mcp.CallToolResult) { + var mutation struct { + MinimizeComment struct { + MinimizedComment struct { + IsMinimized githubv4.Boolean + MinimizedReason githubv4.String + } + } `graphql:"minimizeComment(input: $input)"` + } + + input := githubv4.MinimizeCommentInput{ + SubjectID: githubv4.ID(nodeID), + Classifier: githubv4.ReportedContentClassifiers(classifier), + } + if err := client.Mutate(ctx, &mutation, input, nil); err != nil { + return MinimizeCommentResult{}, ghErrors.NewGitHubGraphQLErrorResponse(ctx, "failed to minimize comment", err) + } + + return MinimizeCommentResult{ + NodeID: nodeID, + IsMinimized: bool(mutation.MinimizeComment.MinimizedComment.IsMinimized), + MinimizedReason: string(mutation.MinimizeComment.MinimizedComment.MinimizedReason), + }, nil +} + +func unminimizeComment(ctx context.Context, client *githubv4.Client, nodeID string) (MinimizeCommentResult, *mcp.CallToolResult) { + var mutation struct { + UnminimizeComment struct { + UnminimizedComment struct { + IsMinimized githubv4.Boolean + } + } `graphql:"unminimizeComment(input: $input)"` + } + + input := githubv4.UnminimizeCommentInput{ + SubjectID: githubv4.ID(nodeID), + } + if err := client.Mutate(ctx, &mutation, input, nil); err != nil { + return MinimizeCommentResult{}, ghErrors.NewGitHubGraphQLErrorResponse(ctx, "failed to unminimize comment", err) + } + + return MinimizeCommentResult{ + NodeID: nodeID, + IsMinimized: bool(mutation.UnminimizeComment.UnminimizedComment.IsMinimized), + }, nil +} diff --git a/pkg/github/comment_minimize_test.go b/pkg/github/comment_minimize_test.go new file mode 100644 index 0000000000..9c07c17879 --- /dev/null +++ b/pkg/github/comment_minimize_test.go @@ -0,0 +1,383 @@ +package github + +import ( + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/github/github-mcp-server/internal/githubv4mock" + "github.com/github/github-mcp-server/internal/toolsnaps" + "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/translations" + "github.com/google/go-github/v92/github" + "github.com/google/jsonschema-go/jsonschema" + "github.com/shurcooL/githubv4" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + getIssueCommentRoute = "GET /repos/{owner}/{repo}/issues/comments/{comment_id}" + getReviewCommentRoute = "GET /repos/{owner}/{repo}/pulls/comments/{comment_id}" + getReviewRoute = "GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}" +) + +// minimizedReason is the lowercase, hyphenated form GitHub returns (e.g. "off-topic"), not the classifier enum. +func minimizeCommentMatcher(nodeID, classifier, minimizedReason string) githubv4mock.Matcher { + return githubv4mock.NewMutationMatcher( + struct { + MinimizeComment struct { + MinimizedComment struct { + IsMinimized githubv4.Boolean + MinimizedReason githubv4.String + } + } `graphql:"minimizeComment(input: $input)"` + }{}, + githubv4.MinimizeCommentInput{ + SubjectID: githubv4.ID(nodeID), + Classifier: githubv4.ReportedContentClassifiers(classifier), + }, + nil, + githubv4mock.DataResponse(map[string]any{ + "minimizeComment": map[string]any{ + "minimizedComment": map[string]any{ + "isMinimized": true, + "minimizedReason": minimizedReason, + }, + }, + }), + ) +} + +func unminimizeCommentMatcher(nodeID string) githubv4mock.Matcher { + return githubv4mock.NewMutationMatcher( + struct { + UnminimizeComment struct { + UnminimizedComment struct { + IsMinimized githubv4.Boolean + } + } `graphql:"unminimizeComment(input: $input)"` + }{}, + githubv4.UnminimizeCommentInput{SubjectID: githubv4.ID(nodeID)}, + nil, + githubv4mock.DataResponse(map[string]any{ + "unminimizeComment": map[string]any{ + "unminimizedComment": map[string]any{"isMinimized": false}, + }, + }), + ) +} + +func minimizeCommentErrorMatcher(nodeID, classifier string) githubv4mock.Matcher { + return githubv4mock.NewMutationMatcher( + struct { + MinimizeComment struct { + MinimizedComment struct { + IsMinimized githubv4.Boolean + MinimizedReason githubv4.String + } + } `graphql:"minimizeComment(input: $input)"` + }{}, + githubv4.MinimizeCommentInput{ + SubjectID: githubv4.ID(nodeID), + Classifier: githubv4.ReportedContentClassifiers(classifier), + }, + nil, + githubv4mock.ErrorResponse("Resource not accessible by integration"), + ) +} + +func unminimizeCommentErrorMatcher(nodeID string) githubv4mock.Matcher { + return githubv4mock.NewMutationMatcher( + struct { + UnminimizeComment struct { + UnminimizedComment struct { + IsMinimized githubv4.Boolean + } + } `graphql:"unminimizeComment(input: $input)"` + }{}, + githubv4.UnminimizeCommentInput{SubjectID: githubv4.ID(nodeID)}, + nil, + githubv4mock.ErrorResponse("Resource not accessible by integration"), + ) +} + +func Test_CommentVisibilityToolSchemas(t *testing.T) { + tests := []struct { + tool inventory.ServerTool + name string + toolset inventory.ToolsetID + featureFlag string + expectedRequire []string + }{ + { + tool: GranularHideIssueComment(translations.NullTranslationHelper), + name: "hide_issue_comment", + toolset: ToolsetMetadataIssues.ID, + featureFlag: FeatureFlagIssuesGranular, + expectedRequire: []string{"owner", "repo", "comment_id", "classifier"}, + }, + { + tool: GranularUnhideIssueComment(translations.NullTranslationHelper), + name: "unhide_issue_comment", + toolset: ToolsetMetadataIssues.ID, + featureFlag: FeatureFlagIssuesGranular, + expectedRequire: []string{"owner", "repo", "comment_id"}, + }, + { + tool: GranularHidePullRequestReviewComment(translations.NullTranslationHelper), + name: "hide_pull_request_review_comment", + toolset: ToolsetMetadataPullRequests.ID, + featureFlag: FeatureFlagPullRequestsGranular, + expectedRequire: []string{"owner", "repo", "comment_id", "classifier"}, + }, + { + tool: GranularUnhidePullRequestReviewComment(translations.NullTranslationHelper), + name: "unhide_pull_request_review_comment", + toolset: ToolsetMetadataPullRequests.ID, + featureFlag: FeatureFlagPullRequestsGranular, + expectedRequire: []string{"owner", "repo", "comment_id"}, + }, + { + tool: GranularHidePullRequestReview(translations.NullTranslationHelper), + name: "hide_pull_request_review", + toolset: ToolsetMetadataPullRequests.ID, + featureFlag: FeatureFlagPullRequestsGranular, + expectedRequire: []string{"owner", "repo", "pullNumber", "review_id", "classifier"}, + }, + { + tool: GranularUnhidePullRequestReview(translations.NullTranslationHelper), + name: "unhide_pull_request_review", + toolset: ToolsetMetadataPullRequests.ID, + featureFlag: FeatureFlagPullRequestsGranular, + expectedRequire: []string{"owner", "repo", "pullNumber", "review_id"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tool := tc.tool.Tool + require.NoError(t, toolsnaps.Test(tool.Name, tool)) + + assert.Equal(t, tc.name, tool.Name) + assert.NotEmpty(t, tool.Description) + assert.False(t, tool.Annotations.ReadOnlyHint) + assert.Equal(t, tc.toolset, tc.tool.Toolset.ID) + assert.Equal(t, []inventory.FeatureFlag{inventory.FeatureFlag(tc.featureFlag)}, tc.tool.FeatureRule.Features()) + + schema := tool.InputSchema.(*jsonschema.Schema) + assert.ElementsMatch(t, tc.expectedRequire, schema.Required) + assert.Len(t, schema.Properties, len(tc.expectedRequire), "every property should be required") + }) + } +} + +func Test_HideAndUnhideComments(t *testing.T) { + issueComment := mockResponse(t, http.StatusOK, &github.IssueComment{ID: new(int64(1)), NodeID: new("IC_1")}) + reviewComment := mockResponse(t, http.StatusOK, &github.PullRequestComment{ID: new(int64(2)), NodeID: new("PRRC_2")}) + review := mockResponse(t, http.StatusOK, &github.PullRequestReview{ID: new(int64(3)), NodeID: new("PRR_3")}) + notFound := mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`) + + tests := []struct { + name string + tool inventory.ServerTool + restHandlers map[string]http.HandlerFunc + gqlMatchers []githubv4mock.Matcher + requestArgs map[string]any + expectedResult MinimizeCommentResult + expectedErrMsg string + }{ + { + name: "hide issue comment", + tool: GranularHideIssueComment(translations.NullTranslationHelper), + restHandlers: map[string]http.HandlerFunc{getIssueCommentRoute: issueComment}, + gqlMatchers: []githubv4mock.Matcher{minimizeCommentMatcher("IC_1", "SPAM", "spam")}, + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "comment_id": float64(1), "classifier": "spam"}, + expectedResult: MinimizeCommentResult{NodeID: "IC_1", IsMinimized: true, MinimizedReason: "spam"}, + }, + { + name: "unhide issue comment", + tool: GranularUnhideIssueComment(translations.NullTranslationHelper), + restHandlers: map[string]http.HandlerFunc{getIssueCommentRoute: issueComment}, + gqlMatchers: []githubv4mock.Matcher{unminimizeCommentMatcher("IC_1")}, + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "comment_id": float64(1)}, + expectedResult: MinimizeCommentResult{NodeID: "IC_1", IsMinimized: false}, + }, + { + name: "hide pull request review comment", + tool: GranularHidePullRequestReviewComment(translations.NullTranslationHelper), + restHandlers: map[string]http.HandlerFunc{getReviewCommentRoute: reviewComment}, + gqlMatchers: []githubv4mock.Matcher{minimizeCommentMatcher("PRRC_2", "OUTDATED", "outdated")}, + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "comment_id": float64(2), "classifier": "OUTDATED"}, + expectedResult: MinimizeCommentResult{NodeID: "PRRC_2", IsMinimized: true, MinimizedReason: "outdated"}, + }, + { + name: "unhide pull request review comment", + tool: GranularUnhidePullRequestReviewComment(translations.NullTranslationHelper), + restHandlers: map[string]http.HandlerFunc{getReviewCommentRoute: reviewComment}, + gqlMatchers: []githubv4mock.Matcher{unminimizeCommentMatcher("PRRC_2")}, + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "comment_id": float64(2)}, + expectedResult: MinimizeCommentResult{NodeID: "PRRC_2", IsMinimized: false}, + }, + { + name: "hide pull request review", + tool: GranularHidePullRequestReview(translations.NullTranslationHelper), + restHandlers: map[string]http.HandlerFunc{getReviewRoute: review}, + gqlMatchers: []githubv4mock.Matcher{minimizeCommentMatcher("PRR_3", "RESOLVED", "resolved")}, + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "pullNumber": float64(42), "review_id": float64(3), "classifier": "RESOLVED"}, + expectedResult: MinimizeCommentResult{NodeID: "PRR_3", IsMinimized: true, MinimizedReason: "resolved"}, + }, + { + name: "unhide pull request review", + tool: GranularUnhidePullRequestReview(translations.NullTranslationHelper), + restHandlers: map[string]http.HandlerFunc{getReviewRoute: review}, + gqlMatchers: []githubv4mock.Matcher{unminimizeCommentMatcher("PRR_3")}, + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "pullNumber": float64(42), "review_id": float64(3)}, + expectedResult: MinimizeCommentResult{NodeID: "PRR_3", IsMinimized: false}, + }, + { + name: "issue comment not found", + tool: GranularUnhideIssueComment(translations.NullTranslationHelper), + restHandlers: map[string]http.HandlerFunc{getIssueCommentRoute: notFound}, + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "comment_id": float64(1)}, + expectedErrMsg: "failed to get issue comment", + }, + { + name: "pull request review comment not found", + tool: GranularUnhidePullRequestReviewComment(translations.NullTranslationHelper), + restHandlers: map[string]http.HandlerFunc{getReviewCommentRoute: notFound}, + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "comment_id": float64(2)}, + expectedErrMsg: "failed to get pull request review comment", + }, + { + name: "pull request review not found", + tool: GranularUnhidePullRequestReview(translations.NullTranslationHelper), + restHandlers: map[string]http.HandlerFunc{getReviewRoute: notFound}, + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "pullNumber": float64(42), "review_id": float64(3)}, + expectedErrMsg: "failed to get pull request review", + }, + { + name: "response without node ID", + tool: GranularUnhideIssueComment(translations.NullTranslationHelper), + restHandlers: map[string]http.HandlerFunc{ + getIssueCommentRoute: mockResponse(t, http.StatusOK, &github.IssueComment{ID: new(int64(1))}), + }, + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "comment_id": float64(1)}, + expectedErrMsg: "response has no node ID", + }, + { + name: "hide mutation fails", + tool: GranularHideIssueComment(translations.NullTranslationHelper), + restHandlers: map[string]http.HandlerFunc{getIssueCommentRoute: issueComment}, + gqlMatchers: []githubv4mock.Matcher{minimizeCommentErrorMatcher("IC_1", "SPAM")}, + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "comment_id": float64(1), "classifier": "SPAM"}, + expectedErrMsg: "failed to minimize comment", + }, + { + name: "unhide mutation fails", + tool: GranularUnhideIssueComment(translations.NullTranslationHelper), + restHandlers: map[string]http.HandlerFunc{getIssueCommentRoute: issueComment}, + gqlMatchers: []githubv4mock.Matcher{unminimizeCommentErrorMatcher("IC_1")}, + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "comment_id": float64(1)}, + expectedErrMsg: "failed to unminimize comment", + }, + { + name: "invalid classifier is rejected before any API call", + tool: GranularHideIssueComment(translations.NullTranslationHelper), + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "comment_id": float64(1), "classifier": "BOGUS"}, + expectedErrMsg: `invalid classifier "BOGUS"`, + }, + { + name: "negative issue comment_id", + tool: GranularHideIssueComment(translations.NullTranslationHelper), + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "comment_id": float64(-1), "classifier": "SPAM"}, + expectedErrMsg: "comment_id must be greater than 0", + }, + { + name: "negative pull request review comment_id", + tool: GranularUnhidePullRequestReviewComment(translations.NullTranslationHelper), + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "comment_id": float64(-2)}, + expectedErrMsg: "comment_id must be greater than 0", + }, + { + name: "negative review_id", + tool: GranularUnhidePullRequestReview(translations.NullTranslationHelper), + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "pullNumber": float64(42), "review_id": float64(-3)}, + expectedErrMsg: "review_id must be greater than 0", + }, + { + name: "negative pullNumber", + tool: GranularUnhidePullRequestReview(translations.NullTranslationHelper), + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "pullNumber": float64(-42), "review_id": float64(3)}, + expectedErrMsg: "pullNumber must be greater than 0", + }, + { + name: "missing owner", + tool: GranularUnhideIssueComment(translations.NullTranslationHelper), + requestArgs: map[string]any{"repo": "repo", "comment_id": float64(1)}, + expectedErrMsg: "owner", + }, + { + name: "missing repo", + tool: GranularUnhideIssueComment(translations.NullTranslationHelper), + requestArgs: map[string]any{"owner": "owner", "comment_id": float64(1)}, + expectedErrMsg: "repo", + }, + { + name: "missing classifier", + tool: GranularHideIssueComment(translations.NullTranslationHelper), + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "comment_id": float64(1)}, + expectedErrMsg: "classifier", + }, + { + name: "missing issue comment_id", + tool: GranularUnhideIssueComment(translations.NullTranslationHelper), + requestArgs: map[string]any{"owner": "owner", "repo": "repo"}, + expectedErrMsg: "comment_id", + }, + { + name: "missing pull request review comment_id", + tool: GranularUnhidePullRequestReviewComment(translations.NullTranslationHelper), + requestArgs: map[string]any{"owner": "owner", "repo": "repo"}, + expectedErrMsg: "comment_id", + }, + { + name: "missing pullNumber", + tool: GranularUnhidePullRequestReview(translations.NullTranslationHelper), + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "review_id": float64(3)}, + expectedErrMsg: "pullNumber", + }, + { + name: "missing review_id", + tool: GranularUnhidePullRequestReview(translations.NullTranslationHelper), + requestArgs: map[string]any{"owner": "owner", "repo": "repo", "pullNumber": float64(42)}, + expectedErrMsg: "review_id", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + deps := BaseDeps{ + Client: mustNewGHClient(t, MockHTTPClientWithHandlers(tc.restHandlers)), + GQLClient: githubv4.NewClient(githubv4mock.NewMockedHTTPClient(tc.gqlMatchers...)), + } + handler := tc.tool.Handler(deps) + + request := createMCPRequest(tc.requestArgs) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + require.NoError(t, err) + + if tc.expectedErrMsg != "" { + require.True(t, result.IsError) + assert.Contains(t, getErrorResult(t, result).Text, tc.expectedErrMsg) + return + } + + require.False(t, result.IsError, getTextResult(t, result).Text) + var got MinimizeCommentResult + require.NoError(t, json.Unmarshal([]byte(getTextResult(t, result).Text), &got)) + assert.Equal(t, tc.expectedResult, got) + }) + } +} diff --git a/pkg/github/context_tools_test.go b/pkg/github/context_tools_test.go index 0825158abb..a3d09ed652 100644 --- a/pkg/github/context_tools_test.go +++ b/pkg/github/context_tools_test.go @@ -10,7 +10,7 @@ import ( "github.com/github/github-mcp-server/internal/githubv4mock" "github.com/github/github-mcp-server/internal/toolsnaps" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" @@ -30,19 +30,19 @@ func Test_GetMe(t *testing.T) { // Setup mock user response mockUser := &github.User{ - Login: github.Ptr("testuser"), - Name: github.Ptr("Test User"), - Email: github.Ptr("test@example.com"), - Bio: github.Ptr("GitHub user for testing"), - Company: github.Ptr("Test Company"), - Location: github.Ptr("Test Location"), - HTMLURL: github.Ptr("https://github.com/testuser"), + Login: new("testuser"), + Name: new("Test User"), + Email: new("test@example.com"), + Bio: new("GitHub user for testing"), + Company: new("Test Company"), + Location: new("Test Location"), + HTMLURL: new("https://github.com/testuser"), CreatedAt: &github.Timestamp{Time: time.Now().Add(-365 * 24 * time.Hour)}, - Type: github.Ptr("User"), - Hireable: github.Ptr(true), - TwitterUsername: github.Ptr("testuser_twitter"), + Type: new("User"), + Hireable: new(true), + TwitterUsername: new("testuser_twitter"), Plan: &github.Plan{ - Name: github.Ptr("pro"), + Name: new("pro"), }, } @@ -144,8 +144,8 @@ func Test_GetMe_OmittedArguments(t *testing.T) { t.Parallel() mockUser := &github.User{ - Login: github.Ptr("testuser"), - HTMLURL: github.Ptr("https://github.com/testuser"), + Login: new("testuser"), + HTMLURL: new("https://github.com/testuser"), CreatedAt: &github.Timestamp{Time: time.Now()}, } mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ @@ -175,8 +175,8 @@ func Test_GetMe_IFC_FeatureFlag(t *testing.T) { serverTool := GetMe(translations.NullTranslationHelper) mockUser := &github.User{ - Login: github.Ptr("testuser"), - HTMLURL: github.Ptr("https://github.com/testuser"), + Login: new("testuser"), + HTMLURL: new("https://github.com/testuser"), CreatedAt: &github.Timestamp{Time: time.Now()}, } mockedHTTPClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ @@ -246,19 +246,19 @@ func Test_GetTeams(t *testing.T) { assert.True(t, tool.Annotations.ReadOnlyHint, "get_teams tool should be read-only") mockUser := &github.User{ - Login: github.Ptr("testuser"), - Name: github.Ptr("Test User"), - Email: github.Ptr("test@example.com"), - Bio: github.Ptr("GitHub user for testing"), - Company: github.Ptr("Test Company"), - Location: github.Ptr("Test Location"), - HTMLURL: github.Ptr("https://github.com/testuser"), + Login: new("testuser"), + Name: new("Test User"), + Email: new("test@example.com"), + Bio: new("GitHub user for testing"), + Company: new("Test Company"), + Location: new("Test Location"), + HTMLURL: new("https://github.com/testuser"), CreatedAt: &github.Timestamp{Time: time.Now().Add(-365 * 24 * time.Hour)}, - Type: github.Ptr("User"), - Hireable: github.Ptr(true), - TwitterUsername: github.Ptr("testuser_twitter"), + Type: new("User"), + Hireable: new(true), + TwitterUsername: new("testuser_twitter"), Plan: &github.Plan{ - Name: github.Ptr("pro"), + Name: new("pro"), }, } diff --git a/pkg/github/copilot.go b/pkg/github/copilot.go index e5db071499..f507a16537 100644 --- a/pkg/github/copilot.go +++ b/pkg/github/copilot.go @@ -18,7 +18,7 @@ import ( "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/go-viper/mapstructure/v2" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" @@ -39,7 +39,7 @@ func (d *mvpDescription) String() string { sb.WriteString("\n\n") sb.WriteString("This tool can help with the following outcomes:\n") for _, outcome := range d.outcomes { - sb.WriteString(fmt.Sprintf("- %s\n", outcome)) + fmt.Fprintf(&sb, "- %s\n", outcome) } } @@ -47,7 +47,7 @@ func (d *mvpDescription) String() string { sb.WriteString("\n\n") sb.WriteString("More information can be found at:\n") for _, link := range d.referenceLinks { - sb.WriteString(fmt.Sprintf("- %s\n", link)) + fmt.Fprintf(&sb, "- %s\n", link) } } diff --git a/pkg/github/copilot_test.go b/pkg/github/copilot_test.go index 3abaaf00c6..8bb0722141 100644 --- a/pkg/github/copilot_test.go +++ b/pkg/github/copilot_test.go @@ -13,7 +13,7 @@ import ( "github.com/github/github-mcp-server/internal/githubv4mock" "github.com/github/github-mcp-server/internal/toolsnaps" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" @@ -900,20 +900,20 @@ func Test_RequestCopilotReview(t *testing.T) { // Setup mock PR for success case mockPR := &github.PullRequest{ - Number: github.Ptr(42), - Title: github.Ptr("Test PR"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"), + Number: new(42), + Title: new("Test PR"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/pull/42"), Head: &github.PullRequestBranch{ - SHA: github.Ptr("abcd1234"), - Ref: github.Ptr("feature-branch"), + SHA: new("abcd1234"), + Ref: new("feature-branch"), }, Base: &github.PullRequestBranch{ - Ref: github.Ptr("main"), + Ref: new("main"), }, - Body: github.Ptr("This is a test PR"), + Body: new("This is a test PR"), User: &github.User{ - Login: github.Ptr("testuser"), + Login: new("testuser"), }, } @@ -965,10 +965,10 @@ func Test_RequestCopilotReview(t *testing.T) { mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber: mockResponse(t, http.StatusNotFound, map[string]any{"message": "Not Found"}), GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, &github.Repository{ - Name: github.Ptr("repo"), + Name: new("repo"), Permissions: &github.RepositoryPermissions{ - Pull: github.Ptr(true), - Push: github.Ptr(false), + Pull: new(true), + Push: new(false), }, }), }), @@ -985,10 +985,10 @@ func Test_RequestCopilotReview(t *testing.T) { mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber: mockResponse(t, http.StatusForbidden, map[string]any{"message": "Forbidden"}), GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, &github.Repository{ - Name: github.Ptr("repo"), + Name: new("repo"), Permissions: &github.RepositoryPermissions{ - Pull: github.Ptr(true), - Push: github.Ptr(false), + Pull: new(true), + Push: new(false), }, }), }), @@ -1005,10 +1005,10 @@ func Test_RequestCopilotReview(t *testing.T) { mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber: mockResponse(t, http.StatusNotFound, map[string]any{"message": "Not Found"}), GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, &github.Repository{ - Name: github.Ptr("repo"), + Name: new("repo"), Permissions: &github.RepositoryPermissions{ - Pull: github.Ptr(true), - Push: github.Ptr(true), + Pull: new(true), + Push: new(true), }, }), }), diff --git a/pkg/github/custom_properties.go b/pkg/github/custom_properties.go index 0e71aa8ecc..734fe176ce 100644 --- a/pkg/github/custom_properties.go +++ b/pkg/github/custom_properties.go @@ -11,7 +11,7 @@ import ( "github.com/github/github-mcp-server/pkg/inventory" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/github/custom_properties_test.go b/pkg/github/custom_properties_test.go index be04872038..394e183d9b 100644 --- a/pkg/github/custom_properties_test.go +++ b/pkg/github/custom_properties_test.go @@ -7,7 +7,7 @@ import ( "net/http" "testing" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -96,7 +96,7 @@ func Test_CustomPropertiesRead(t *testing.T) { }) t.Run("enterprise level: returns property definitions", func(t *testing.T) { - mockProps := []*github.CustomProperty{{PropertyName: github.Ptr("compliance"), ValueType: "true_false"}} + mockProps := []*github.CustomProperty{{PropertyName: new("compliance"), ValueType: "true_false"}} client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ "GET /enterprises/{enterprise}/properties/schema": mockResponse(t, http.StatusOK, mockProps), })) diff --git a/pkg/github/dependabot.go b/pkg/github/dependabot.go index 9cec4cb814..4ba7d6c4c3 100644 --- a/pkg/github/dependabot.go +++ b/pkg/github/dependabot.go @@ -13,7 +13,7 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/github/dependabot_test.go b/pkg/github/dependabot_test.go index dba0a5603f..a699980778 100644 --- a/pkg/github/dependabot_test.go +++ b/pkg/github/dependabot_test.go @@ -8,7 +8,7 @@ import ( "github.com/github/github-mcp-server/internal/toolsnaps" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -26,9 +26,9 @@ func Test_GetDependabotAlert(t *testing.T) { // Setup mock alert for success case mockAlert := &github.DependabotAlert{ - Number: github.Ptr(42), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/security/dependabot/42"), + Number: new(42), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/security/dependabot/42"), } tests := []struct { @@ -137,19 +137,19 @@ func Test_ListDependabotAlerts(t *testing.T) { // Setup mock alerts for success case criticalAlert := github.DependabotAlert{ - Number: github.Ptr(1), - HTMLURL: github.Ptr("https://github.com/owner/repo/security/dependabot/1"), - State: github.Ptr("open"), + Number: new(1), + HTMLURL: new("https://github.com/owner/repo/security/dependabot/1"), + State: new("open"), SecurityAdvisory: &github.DependabotSecurityAdvisory{ - Severity: github.Ptr("critical"), + Severity: new("critical"), }, } highSeverityAlert := github.DependabotAlert{ - Number: github.Ptr(2), - HTMLURL: github.Ptr("https://github.com/owner/repo/security/dependabot/2"), - State: github.Ptr("fixed"), + Number: new(2), + HTMLURL: new("https://github.com/owner/repo/security/dependabot/2"), + State: new("fixed"), SecurityAdvisory: &github.DependabotSecurityAdvisory{ - Severity: github.Ptr("high"), + Severity: new("high"), }, } diff --git a/pkg/github/dependencies.go b/pkg/github/dependencies.go index 333b1d9c07..21bbd99553 100644 --- a/pkg/github/dependencies.go +++ b/pkg/github/dependencies.go @@ -16,7 +16,7 @@ import ( "github.com/github/github-mcp-server/pkg/raw" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - gogithub "github.com/google/go-github/v89/github" + gogithub "github.com/google/go-github/v92/github" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" ) diff --git a/pkg/github/discussions.go b/pkg/github/discussions.go index 4b40569c39..a650a76d14 100644 --- a/pkg/github/discussions.go +++ b/pkg/github/discussions.go @@ -13,7 +13,7 @@ import ( "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/go-viper/mapstructure/v2" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" @@ -99,16 +99,16 @@ type WithCategoryNoOrder struct { func fragmentToDiscussion(fragment NodeFragment) *github.Discussion { return &github.Discussion{ - Number: github.Ptr(int(fragment.Number)), - Title: github.Ptr(sanitize.PlainText(string(fragment.Title))), - HTMLURL: github.Ptr(string(fragment.URL)), + Number: new(int(fragment.Number)), + Title: new(sanitize.PlainText(string(fragment.Title))), + HTMLURL: new(string(fragment.URL)), CreatedAt: &github.Timestamp{Time: fragment.CreatedAt.Time}, UpdatedAt: &github.Timestamp{Time: fragment.UpdatedAt.Time}, User: &github.User{ - Login: github.Ptr(string(fragment.Author.Login)), + Login: new(string(fragment.Author.Login)), }, DiscussionCategory: &github.DiscussionCategory{ - Name: github.Ptr(string(fragment.Category.Name)), + Name: new(string(fragment.Category.Name)), }, } } diff --git a/pkg/github/discussions_test.go b/pkg/github/discussions_test.go index 111372a9ef..c502f2aa47 100644 --- a/pkg/github/discussions_test.go +++ b/pkg/github/discussions_test.go @@ -9,7 +9,7 @@ import ( "github.com/github/github-mcp-server/internal/githubv4mock" "github.com/github/github-mcp-server/internal/toolsnaps" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" diff --git a/pkg/github/feature_flags.go b/pkg/github/feature_flags.go index 94f335ebe5..321d8fa3bb 100644 --- a/pkg/github/feature_flags.go +++ b/pkg/github/feature_flags.go @@ -6,9 +6,6 @@ import ( "github.com/github/github-mcp-server/pkg/inventory" ) -// MCPAppsFeatureFlag is the feature flag name for MCP Apps (interactive UI forms). -const MCPAppsFeatureFlag = "remote_mcp_ui_apps" - // MCPAppsDisableFormDeferralFeatureFlag disables handing write-tool calls off // to MCP App forms while preserving MCP Apps UI metadata and result views. const MCPAppsDisableFormDeferralFeatureFlag = "mcp_apps_disable_form_deferral" @@ -47,7 +44,6 @@ const FeatureFlagThreadResolutionReason = "thread_resolution_reason" // Only flags in this list are accepted; unknown flags are silently ignored. // This is the single source of truth for which flags are user-controllable. var AllowedFeatureFlags = []string{ - MCPAppsFeatureFlag, MCPAppsDisableFormDeferralFeatureFlag, FeatureFlagCSVOutput, FeatureFlagIFCLabels, @@ -64,7 +60,6 @@ var AllowedFeatureFlags = []string{ // This is the single source of truth for what "insiders" means in terms of // feature flag expansion. var InsidersFeatureFlags = []string{ - MCPAppsFeatureFlag, FeatureFlagCSVOutput, FeatureFlagFileBlame, FeatureFlagIssueDependencies, diff --git a/pkg/github/feature_flags_benchmark_test.go b/pkg/github/feature_flags_benchmark_test.go index 79aa17d55d..97d4951ca6 100644 --- a/pkg/github/feature_flags_benchmark_test.go +++ b/pkg/github/feature_flags_benchmark_test.go @@ -146,7 +146,7 @@ func featureBenchmarkDistributions() []featureBenchmarkDistribution { { name: "mixed", enabled: map[string]bool{ - MCPAppsFeatureFlag: true, + FeatureFlagCSVOutput: true, FeatureFlagFileBlame: true, FeatureFlagIssuesGranular: true, FeatureFlagIssueDependencies: true, diff --git a/pkg/github/feature_flags_test.go b/pkg/github/feature_flags_test.go index cc3fbf0837..241d02c5dd 100644 --- a/pkg/github/feature_flags_test.go +++ b/pkg/github/feature_flags_test.go @@ -149,12 +149,12 @@ func TestResolveFeatureFlags(t *testing.T) { name: "no features, no insiders", enabledFeatures: nil, expectedFlags: nil, - unexpectedFlags: []string{MCPAppsFeatureFlag}, + unexpectedFlags: []string{FeatureFlagCSVOutput}, }, { name: "explicit feature enabled", - enabledFeatures: []string{MCPAppsFeatureFlag}, - expectedFlags: []string{MCPAppsFeatureFlag}, + enabledFeatures: []string{FeatureFlagCSVOutput}, + expectedFlags: []string{FeatureFlagCSVOutput}, }, { name: "MCP Apps form deferral can be disabled directly", @@ -191,8 +191,8 @@ func TestResolveFeatureFlags(t *testing.T) { }, { name: "mix of known and unknown flags", - enabledFeatures: []string{MCPAppsFeatureFlag, "unknown_flag"}, - expectedFlags: []string{MCPAppsFeatureFlag}, + enabledFeatures: []string{FeatureFlagCSVOutput, "unknown_flag"}, + expectedFlags: []string{FeatureFlagCSVOutput}, unexpectedFlags: []string{"unknown_flag"}, }, { @@ -214,7 +214,7 @@ func TestResolveFeatureFlags(t *testing.T) { }, { name: "explicit plus insiders deduplicates", - enabledFeatures: []string{MCPAppsFeatureFlag}, + enabledFeatures: []string{FeatureFlagCSVOutput}, insidersMode: true, expectedFlags: InsidersFeatureFlags, }, diff --git a/pkg/github/fields_filtering_test.go b/pkg/github/fields_filtering_test.go index cd08dcef68..cc3e684013 100644 --- a/pkg/github/fields_filtering_test.go +++ b/pkg/github/fields_filtering_test.go @@ -9,7 +9,7 @@ import ( "github.com/github/github-mcp-server/internal/githubv4mock" "github.com/github/github-mcp-server/pkg/inventory" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -20,16 +20,16 @@ import ( func mockListCommits() []*github.RepositoryCommit { return []*github.RepositoryCommit{ { - SHA: github.Ptr("abc123def456"), - HTMLURL: github.Ptr("https://github.com/owner/repo/commit/abc123def456"), + SHA: new("abc123def456"), + HTMLURL: new("https://github.com/owner/repo/commit/abc123def456"), Commit: &github.Commit{ - Message: github.Ptr("First commit with a reasonably long message to add bytes"), + Message: new("First commit with a reasonably long message to add bytes"), Author: &github.CommitAuthor{ - Name: github.Ptr("Test User"), - Email: github.Ptr("test@example.com"), + Name: new("Test User"), + Email: new("test@example.com"), }, }, - Author: &github.User{Login: github.Ptr("testuser")}, + Author: &github.User{Login: new("testuser")}, }, } } @@ -79,8 +79,8 @@ func mockListReleases() []*github.RepositoryRelease { { ID: 1, TagName: "v1.0.0", - Name: github.Ptr("First Release"), - Body: github.Ptr("Release notes with a reasonably long body to add bytes"), + Name: new("First Release"), + Body: new("Release notes with a reasonably long body to add bytes"), HTMLURL: "https://github.com/owner/repo/releases/tag/v1.0.0", }, } @@ -130,12 +130,12 @@ func Test_ListReleases_FieldsTelemetry(t *testing.T) { func mockListPullRequests() []*github.PullRequest { return []*github.PullRequest{ { - Number: github.Ptr(42), - Title: github.Ptr("First PR"), - Body: github.Ptr("PR body with a reasonably long description to add bytes"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"), - User: &github.User{Login: github.Ptr("user1")}, + Number: new(42), + Title: new("First PR"), + Body: new("PR body with a reasonably long description to add bytes"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/pull/42"), + User: &github.User{Login: new("user1")}, }, } } @@ -187,16 +187,16 @@ func Test_ListPullRequests_FieldsTelemetry(t *testing.T) { // does not attempt the follow-up GraphQL field-values enrichment. func mockIssueSearchResult() *github.IssuesSearchResult { return &github.IssuesSearchResult{ - Total: github.Ptr(1), - IncompleteResults: github.Ptr(false), + Total: new(1), + IncompleteResults: new(false), Issues: []*github.Issue{ { - Number: github.Ptr(42), - Title: github.Ptr("A result"), - Body: github.Ptr("Body with a reasonably long description to add bytes"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"), - User: &github.User{Login: github.Ptr("user1")}, + Number: new(42), + Title: new("A result"), + Body: new("Body with a reasonably long description to add bytes"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/pull/42"), + User: &github.User{Login: new("user1")}, }, }, } diff --git a/pkg/github/gists.go b/pkg/github/gists.go index 441e59ea7c..44baba5c6d 100644 --- a/pkg/github/gists.go +++ b/pkg/github/gists.go @@ -13,7 +13,7 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -231,8 +231,8 @@ func CreateGist(t translations.TranslationHelperFunc) inventory.ServerTool { gist := github.CreateGistRequest{ Files: files, - Public: github.Ptr(public), - Description: github.Ptr(description), + Public: new(public), + Description: new(description), } client, err := deps.GetClient(ctx) @@ -327,8 +327,8 @@ func UpdateGist(t translations.TranslationHelperFunc) inventory.ServerTool { files := make(map[github.GistFilename]*github.UpdateGistFile) files[github.GistFilename(filename)] = &github.UpdateGistFile{ - Filename: github.Ptr(filename), - Content: github.Ptr(content), + Filename: new(filename), + Content: new(content), } // Only set Description when the caller actually provided it, so @@ -336,7 +336,7 @@ func UpdateGist(t translations.TranslationHelperFunc) inventory.ServerTool { // explicit empty string still clears it. var descriptionPtr *string if _, ok := args["description"]; ok { - descriptionPtr = github.Ptr(description) + descriptionPtr = new(description) } gist := github.UpdateGistRequest{ diff --git a/pkg/github/gists_test.go b/pkg/github/gists_test.go index a0e153b033..9b3f72ba5a 100644 --- a/pkg/github/gists_test.go +++ b/pkg/github/gists_test.go @@ -9,7 +9,7 @@ import ( "github.com/github/github-mcp-server/internal/toolsnaps" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -37,30 +37,30 @@ func Test_ListGists(t *testing.T) { // Setup mock gists for success case mockGists := []*github.Gist{ { - ID: github.Ptr("gist1"), - Description: github.Ptr("First Gist"), - HTMLURL: github.Ptr("https://gist.github.com/user/gist1"), - Public: github.Ptr(true), + ID: new("gist1"), + Description: new("First Gist"), + HTMLURL: new("https://gist.github.com/user/gist1"), + Public: new(true), CreatedAt: &github.Timestamp{Time: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)}, - Owner: &github.User{Login: github.Ptr("user")}, + Owner: &github.User{Login: new("user")}, Files: map[github.GistFilename]github.GistFile{ "file1.txt": { - Filename: github.Ptr("file1.txt"), - Content: github.Ptr("content of file 1"), + Filename: new("file1.txt"), + Content: new("content of file 1"), }, }, }, { - ID: github.Ptr("gist2"), - Description: github.Ptr("Second Gist"), - HTMLURL: github.Ptr("https://gist.github.com/testuser/gist2"), - Public: github.Ptr(false), + ID: new("gist2"), + Description: new("Second Gist"), + HTMLURL: new("https://gist.github.com/testuser/gist2"), + Public: new(false), CreatedAt: &github.Timestamp{Time: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC)}, - Owner: &github.User{Login: github.Ptr("testuser")}, + Owner: &github.User{Login: new("testuser")}, Files: map[github.GistFilename]github.GistFile{ "file2.js": { - Filename: github.Ptr("file2.js"), - Content: github.Ptr("console.log('hello');"), + Filename: new("file2.js"), + Content: new("console.log('hello');"), }, }, }, @@ -202,16 +202,16 @@ func Test_GetGist(t *testing.T) { // Setup mock gist for success case mockGist := github.Gist{ - ID: github.Ptr("gist1"), - Description: github.Ptr("First Gist"), - HTMLURL: github.Ptr("https://gist.github.com/user/gist1"), - Public: github.Ptr(true), + ID: new("gist1"), + Description: new("First Gist"), + HTMLURL: new("https://gist.github.com/user/gist1"), + Public: new(true), CreatedAt: &github.Timestamp{Time: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)}, - Owner: &github.User{Login: github.Ptr("user")}, + Owner: &github.User{Login: new("user")}, Files: map[github.GistFilename]github.GistFile{ github.GistFilename("file1.txt"): { - Filename: github.Ptr("file1.txt"), - Content: github.Ptr("content of file 1"), + Filename: new("file1.txt"), + Content: new("content of file 1"), }, }, } @@ -315,16 +315,16 @@ func Test_CreateGist(t *testing.T) { // Setup mock data for test cases createdGist := &github.Gist{ - ID: github.Ptr("new-gist-id"), - Description: github.Ptr("Test Gist"), - HTMLURL: github.Ptr("https://gist.github.com/user/new-gist-id"), - Public: github.Ptr(false), + ID: new("new-gist-id"), + Description: new("Test Gist"), + HTMLURL: new("https://gist.github.com/user/new-gist-id"), + Public: new(false), CreatedAt: &github.Timestamp{Time: time.Now()}, - Owner: &github.User{Login: github.Ptr("user")}, + Owner: &github.User{Login: new("user")}, Files: map[github.GistFilename]github.GistFile{ "test.go": { - Filename: github.Ptr("test.go"), - Content: github.Ptr("package main\n\nfunc main() {\n\tfmt.Println(\"Hello, Gist!\")\n}"), + Filename: new("test.go"), + Content: new("package main\n\nfunc main() {\n\tfmt.Println(\"Hello, Gist!\")\n}"), }, }, } @@ -454,16 +454,16 @@ func Test_UpdateGist(t *testing.T) { // Setup mock data for test cases updatedGist := &github.Gist{ - ID: github.Ptr("existing-gist-id"), - Description: github.Ptr("Updated Test Gist"), - HTMLURL: github.Ptr("https://gist.github.com/user/existing-gist-id"), - Public: github.Ptr(true), + ID: new("existing-gist-id"), + Description: new("Updated Test Gist"), + HTMLURL: new("https://gist.github.com/user/existing-gist-id"), + Public: new(true), UpdatedAt: &github.Timestamp{Time: time.Now()}, - Owner: &github.User{Login: github.Ptr("user")}, + Owner: &github.User{Login: new("user")}, Files: map[github.GistFilename]github.GistFile{ "updated.go": { - Filename: github.Ptr("updated.go"), - Content: github.Ptr("package main\n\nfunc main() {\n\tfmt.Println(\"Updated Gist!\")\n}"), + Filename: new("updated.go"), + Content: new("package main\n\nfunc main() {\n\tfmt.Println(\"Updated Gist!\")\n}"), }, }, } @@ -586,8 +586,8 @@ func Test_UpdateGist_DescriptionOnlySentWhenProvided(t *testing.T) { serverTool := UpdateGist(translations.NullTranslationHelper) updatedGist := &github.Gist{ - ID: github.Ptr("existing-gist-id"), - HTMLURL: github.Ptr("https://gist.github.com/user/existing-gist-id"), + ID: new("existing-gist-id"), + HTMLURL: new("https://gist.github.com/user/existing-gist-id"), } cases := []struct { diff --git a/pkg/github/git.go b/pkg/github/git.go index 0f65b9324d..b066dfe757 100644 --- a/pkg/github/git.go +++ b/pkg/github/git.go @@ -12,7 +12,7 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/github/git_test.go b/pkg/github/git_test.go index 67921d6045..e914617642 100644 --- a/pkg/github/git_test.go +++ b/pkg/github/git_test.go @@ -9,7 +9,7 @@ import ( "github.com/github/github-mcp-server/internal/toolsnaps" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -35,27 +35,27 @@ func Test_GetRepositoryTree(t *testing.T) { // Setup mock data mockRepo := &github.Repository{ - DefaultBranch: github.Ptr("main"), + DefaultBranch: new("main"), } mockTree := &github.Tree{ - SHA: github.Ptr("abc123"), - Truncated: github.Ptr(false), + SHA: new("abc123"), + Truncated: new(false), Entries: []*github.TreeEntry{ { - Path: github.Ptr("README.md"), - Mode: github.Ptr("100644"), - Type: github.Ptr("blob"), - SHA: github.Ptr("file1sha"), - Size: github.Ptr(123), - URL: github.Ptr("https://api.github.com/repos/owner/repo/git/blobs/file1sha"), + Path: new("README.md"), + Mode: new("100644"), + Type: new("blob"), + SHA: new("file1sha"), + Size: new(123), + URL: new("https://api.github.com/repos/owner/repo/git/blobs/file1sha"), }, { - Path: github.Ptr("src/main.go"), - Mode: github.Ptr("100644"), - Type: github.Ptr("blob"), - SHA: github.Ptr("file2sha"), - Size: github.Ptr(456), - URL: github.Ptr("https://api.github.com/repos/owner/repo/git/blobs/file2sha"), + Path: new("src/main.go"), + Mode: new("100644"), + Type: new("blob"), + SHA: new("file2sha"), + Size: new(456), + URL: new("https://api.github.com/repos/owner/repo/git/blobs/file2sha"), }, }, } diff --git a/pkg/github/granular_tools_test.go b/pkg/github/granular_tools_test.go index 425f954ef9..ebe7249cd4 100644 --- a/pkg/github/granular_tools_test.go +++ b/pkg/github/granular_tools_test.go @@ -14,7 +14,7 @@ import ( transportpkg "github.com/github/github-mcp-server/pkg/http/transport" "github.com/github/github-mcp-server/pkg/inventory" "github.com/github/github-mcp-server/pkg/translations" - gogithub "github.com/google/go-github/v89/github" + gogithub "github.com/google/go-github/v92/github" "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -56,6 +56,8 @@ func TestGranularToolSnaps(t *testing.T) { GranularRemoveIssueReaction, GranularAddIssueCommentReaction, GranularRemoveIssueCommentReaction, + GranularHideIssueComment, + GranularUnhideIssueComment, GranularUpdatePullRequestTitle, GranularUpdatePullRequestBody, GranularUpdatePullRequestState, @@ -69,6 +71,10 @@ func TestGranularToolSnaps(t *testing.T) { GranularUnresolveReviewThread, GranularAddPullRequestReviewCommentReaction, GranularRemovePullRequestReviewCommentReaction, + GranularHidePullRequestReviewComment, + GranularUnhidePullRequestReviewComment, + GranularHidePullRequestReview, + GranularUnhidePullRequestReview, } for _, constructor := range toolConstructors { @@ -105,6 +111,8 @@ func TestIssuesGranularToolset(t *testing.T) { "remove_issue_reaction", "add_issue_comment_reaction", "remove_issue_comment_reaction", + "hide_issue_comment", + "unhide_issue_comment", } for _, name := range expected { assert.Contains(t, toolNames, name) @@ -142,6 +150,10 @@ func TestPullRequestsGranularToolset(t *testing.T) { "unresolve_review_thread", "add_pull_request_review_comment_reaction", "remove_pull_request_review_comment_reaction", + "hide_pull_request_review_comment", + "unhide_pull_request_review_comment", + "hide_pull_request_review", + "unhide_pull_request_review", } for _, name := range expected { assert.Contains(t, toolNames, name) @@ -160,9 +172,9 @@ func TestPullRequestsGranularToolset(t *testing.T) { func TestGranularCreateIssue(t *testing.T) { mockIssue := &gogithub.Issue{ - Number: gogithub.Ptr(1), - Title: gogithub.Ptr("Test Issue"), - Body: gogithub.Ptr("Test body"), + Number: new(1), + Title: new("Test Issue"), + Body: new("Test body"), } tests := []struct { @@ -221,8 +233,8 @@ func TestGranularCreateIssue(t *testing.T) { func TestGranularUpdateIssueTitle(t *testing.T) { client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ PatchReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, &gogithub.Issue{ - Number: gogithub.Ptr(42), - Title: gogithub.Ptr("New Title"), + Number: new(42), + Title: new("New Title"), }), })) deps := BaseDeps{Client: client} @@ -245,8 +257,8 @@ func TestGranularUpdateIssueBody(t *testing.T) { PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, map[string]any{ "body": "Updated body", }).andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{ - Number: gogithub.Ptr(1), - Body: gogithub.Ptr("Updated body"), + Number: new(1), + Body: new("Updated body"), })), })) deps := BaseDeps{Client: client} @@ -268,7 +280,7 @@ func TestGranularUpdateIssueAssignees(t *testing.T) { client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, map[string]any{ "assignees": []any{"user1", "user2"}, - }).andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: gogithub.Ptr(1)})), + }).andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: new(1)})), })) deps := BaseDeps{Client: client} serverTool := GranularUpdateIssueAssignees(translations.NullTranslationHelper) @@ -362,7 +374,7 @@ func TestGranularUpdateIssueAssigneesObjectForm(t *testing.T) { t.Run(tc.name, func(t *testing.T) { client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, tc.expectedReq). - andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: gogithub.Ptr(1)})), + andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: new(1)})), })) deps := BaseDeps{Client: client} serverTool := GranularUpdateIssueAssignees(translations.NullTranslationHelper) @@ -505,7 +517,7 @@ func TestGranularUpdateIssueLabels(t *testing.T) { t.Run(tc.name, func(t *testing.T) { client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, tc.expectedReq). - andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: gogithub.Ptr(1)})), + andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: new(1)})), })) deps := BaseDeps{Client: client} serverTool := GranularUpdateIssueLabels(translations.NullTranslationHelper) @@ -583,7 +595,7 @@ func TestGranularUpdateIssueLabelsSuggest(t *testing.T) { t.Run(tc.name, func(t *testing.T) { client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, tc.expectedReq). - andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: gogithub.Ptr(1)})), + andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: new(1)})), })) deps := BaseDeps{Client: client} serverTool := GranularUpdateIssueLabels(translations.NullTranslationHelper) @@ -732,7 +744,7 @@ func TestGranularUpdateIssueLabelsConfidence(t *testing.T) { client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, tc.expectedReq). - andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: gogithub.Ptr(1)})), + andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: new(1)})), })) deps := BaseDeps{Client: client} serverTool := GranularUpdateIssueLabels(translations.NullTranslationHelper) @@ -750,7 +762,7 @@ func TestGranularUpdateIssueMilestone(t *testing.T) { client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, map[string]any{ "milestone": float64(5), - }).andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: gogithub.Ptr(1)})), + }).andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: new(1)})), })) deps := BaseDeps{Client: client} serverTool := GranularUpdateIssueMilestone(translations.NullTranslationHelper) @@ -819,7 +831,7 @@ func TestGranularUpdateIssueType(t *testing.T) { t.Run(tc.name, func(t *testing.T) { client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, tc.expectedReq). - andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: gogithub.Ptr(1)})), + andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: new(1)})), })) deps := BaseDeps{Client: client} serverTool := GranularUpdateIssueType(translations.NullTranslationHelper) @@ -918,7 +930,7 @@ func TestGranularUpdateIssueTypeSuggest(t *testing.T) { t.Run(tc.name, func(t *testing.T) { client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, tc.expectedReq). - andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: gogithub.Ptr(1)})), + andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: new(1)})), })) deps := BaseDeps{Client: client} serverTool := GranularUpdateIssueType(translations.NullTranslationHelper) @@ -1056,7 +1068,7 @@ func TestGranularUpdateIssueTypeConfidence(t *testing.T) { t.Run(tc.name, func(t *testing.T) { client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, tc.expectedReq). - andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: gogithub.Ptr(1)})), + andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: new(1)})), })) deps := BaseDeps{Client: client} serverTool := GranularUpdateIssueType(translations.NullTranslationHelper) @@ -1155,8 +1167,8 @@ func TestGranularUpdateIssueState(t *testing.T) { client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, tc.expectedReq). andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{ - Number: gogithub.Ptr(1), - State: gogithub.Ptr(tc.requestArgs["state"].(string)), + Number: new(1), + State: new(tc.requestArgs["state"].(string)), })), })) deps := BaseDeps{Client: client} @@ -1237,7 +1249,7 @@ func TestGranularUpdateIssueStateSuggest(t *testing.T) { t.Run(tc.name, func(t *testing.T) { client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, tc.expectedReq). - andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: gogithub.Ptr(1)})), + andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: new(1)})), })) deps := BaseDeps{Client: client} serverTool := GranularUpdateIssueState(translations.NullTranslationHelper) @@ -1301,11 +1313,11 @@ func TestGranularUpdateIssueStateDuplicate(t *testing.T) { t.Run(tc.name, func(t *testing.T) { client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, &gogithub.Issue{ - ID: gogithub.Ptr(duplicateIssueID), - Number: gogithub.Ptr(42), + ID: new(duplicateIssueID), + Number: new(42), }), PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, tc.expectedReq). - andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: gogithub.Ptr(1)})), + andThen(mockResponse(t, http.StatusOK, &gogithub.Issue{Number: new(1)})), })) deps := BaseDeps{Client: client} serverTool := GranularUpdateIssueState(translations.NullTranslationHelper) @@ -1416,8 +1428,8 @@ func TestGranularUpdatePullRequestTitle(t *testing.T) { PatchReposPullsByOwnerByRepoByPullNumber: expectRequestBody(t, map[string]any{ "title": "New PR Title", }).andThen(mockResponse(t, http.StatusOK, &gogithub.PullRequest{ - Number: gogithub.Ptr(1), - Title: gogithub.Ptr("New PR Title"), + Number: new(1), + Title: new("New PR Title"), })), })) deps := BaseDeps{Client: client} @@ -1440,8 +1452,8 @@ func TestGranularUpdatePullRequestBody(t *testing.T) { PatchReposPullsByOwnerByRepoByPullNumber: expectRequestBody(t, map[string]any{ "body": "Updated description", }).andThen(mockResponse(t, http.StatusOK, &gogithub.PullRequest{ - Number: gogithub.Ptr(1), - Body: gogithub.Ptr("Updated description"), + Number: new(1), + Body: new("Updated description"), })), })) deps := BaseDeps{Client: client} @@ -1464,8 +1476,8 @@ func TestGranularUpdatePullRequestState(t *testing.T) { PatchReposPullsByOwnerByRepoByPullNumber: expectRequestBody(t, map[string]any{ "state": "closed", }).andThen(mockResponse(t, http.StatusOK, &gogithub.PullRequest{ - Number: gogithub.Ptr(1), - State: gogithub.Ptr("closed"), + Number: new(1), + State: new("closed"), })), })) deps := BaseDeps{Client: client} @@ -1488,7 +1500,7 @@ func TestGranularRequestPullRequestReviewers(t *testing.T) { PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber: expectRequestBody(t, map[string]any{ "reviewers": []any{"user1"}, "team_reviewers": []any{"team1"}, - }).andThen(mockResponse(t, http.StatusOK, &gogithub.PullRequest{Number: gogithub.Ptr(1)})), + }).andThen(mockResponse(t, http.StatusOK, &gogithub.PullRequest{Number: new(1)})), })) deps := BaseDeps{Client: client} serverTool := GranularRequestPullRequestReviewers(translations.NullTranslationHelper) @@ -1653,49 +1665,15 @@ func TestGranularUpdatePullRequestDraftState(t *testing.T) { func TestGranularAddPullRequestReviewComment(t *testing.T) { mockedClient := githubv4mock.NewMockedHTTPClient( - githubv4mock.NewQueryMatcher( - struct { - Viewer struct { - Login githubv4.String - } - }{}, - nil, - githubv4mock.DataResponse(map[string]any{ - "viewer": map[string]any{"login": "testuser"}, - }), - ), - githubv4mock.NewQueryMatcher( - struct { - Repository struct { - PullRequest struct { - Reviews struct { - Nodes []struct { - ID githubv4.ID - State githubv4.PullRequestReviewState - URL githubv4.URI - } - } `graphql:"reviews(first: 1, author: $author)"` - } `graphql:"pullRequest(number: $prNum)"` - } `graphql:"repository(owner: $owner, name: $name)"` - }{}, - map[string]any{ - "author": githubv4.String("testuser"), - "owner": githubv4.String("owner"), - "name": githubv4.String("repo"), - "prNum": githubv4.Int(1), + viewerIDQuery("U_testuser"), + getPendingReviewsQuery(getPendingReviewsQueryParams{ + owner: "owner", + repo: "repo", + prNum: 1, + reviews: []pendingReviewQueryReview{ + {id: "PRR_123", authorID: "U_testuser"}, }, - githubv4mock.DataResponse(map[string]any{ - "repository": map[string]any{ - "pullRequest": map[string]any{ - "reviews": map[string]any{ - "nodes": []map[string]any{ - {"id": "PRR_123", "state": "PENDING", "url": "https://github.com/owner/repo/pull/1#pullrequestreview-123"}, - }, - }, - }, - }, - }), - ), + }), githubv4mock.NewMutationMatcher( struct { AddPullRequestReviewThread struct { @@ -1705,7 +1683,7 @@ func TestGranularAddPullRequestReviewComment(t *testing.T) { } `graphql:"addPullRequestReviewThread(input: $input)"` }{}, githubv4.AddPullRequestReviewThreadInput{ - Path: githubv4.String("src/main.go"), + Path: githubv4.NewString("src/main.go"), Body: githubv4.String("This needs a fix"), SubjectType: githubv4mock.Ptr(githubv4.PullRequestReviewThreadSubjectTypeLine), Line: githubv4mock.Ptr(githubv4.Int(42)), @@ -1750,10 +1728,10 @@ func TestGranularResolveReviewThread(t *testing.T) { { name: "enabled variant forwards resolution reason", withResolutionReason: true, - resolutionReason: gogithub.Ptr("addressed"), - expectedReason: gogithub.Ptr("addressed"), + resolutionReason: new("addressed"), + expectedReason: new("addressed"), }, - {name: "default variant omits resolution reason", resolutionReason: gogithub.Ptr("addressed")}, + {name: "default variant omits resolution reason", resolutionReason: new("addressed")}, {name: "without resolution reason"}, } @@ -2410,8 +2388,8 @@ func TestGranularSetIssueFields(t *testing.T) { func TestGranularAddIssueReaction(t *testing.T) { mockReaction := &gogithub.Reaction{ - ID: gogithub.Ptr(int64(12345)), - Content: gogithub.Ptr("+1"), + ID: new(int64(12345)), + Content: new("+1"), } tests := []struct { @@ -2546,8 +2524,8 @@ func TestGranularRemoveIssueReaction(t *testing.T) { func TestGranularAddIssueCommentReaction(t *testing.T) { mockReaction := &gogithub.Reaction{ - ID: gogithub.Ptr(int64(67890)), - Content: gogithub.Ptr("heart"), + ID: new(int64(67890)), + Content: new("heart"), } tests := []struct { @@ -2672,8 +2650,8 @@ func TestGranularRemoveIssueCommentReaction(t *testing.T) { func TestGranularAddPullRequestReviewCommentReaction(t *testing.T) { mockReaction := &gogithub.Reaction{ - ID: gogithub.Ptr(int64(54321)), - Content: gogithub.Ptr("rocket"), + ID: new(int64(54321)), + Content: new("rocket"), } tests := []struct { diff --git a/pkg/github/helper_test.go b/pkg/github/helper_test.go index 0d089c8430..80a2e523f2 100644 --- a/pkg/github/helper_test.go +++ b/pkg/github/helper_test.go @@ -10,7 +10,7 @@ import ( "strings" "testing" - gogithub "github.com/google/go-github/v89/github" + gogithub "github.com/google/go-github/v92/github" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/assert" testifymock "github.com/stretchr/testify/mock" diff --git a/pkg/github/ifc_labels.go b/pkg/github/ifc_labels.go index d91a9b16e2..423362a0dc 100644 --- a/pkg/github/ifc_labels.go +++ b/pkg/github/ifc_labels.go @@ -4,7 +4,7 @@ import ( "context" "github.com/github/github-mcp-server/pkg/ifc" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/github/issue_dependencies.go b/pkg/github/issue_dependencies.go index dc78c9eccc..e553bab365 100644 --- a/pkg/github/issue_dependencies.go +++ b/pkg/github/issue_dependencies.go @@ -12,7 +12,7 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/github/issues.go b/pkg/github/issues.go index f1de84eb27..80483d7905 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -20,7 +20,7 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" @@ -1507,8 +1507,8 @@ func AddIssueComment(t translations.TranslationHelperFunc) inventory.ServerTool var commentResponse *MinimalResponse if hasBody { - comment := &github.IssueComment{ - Body: github.Ptr(body), + comment := github.IssueCommentRequest{ + Body: body, } createdComment, resp, err := client.Issues.CreateComment(ctx, owner, repo, issueNumber, comment) if err != nil { @@ -1621,8 +1621,8 @@ func UpdateIssueComment(t translations.TranslationHelperFunc) inventory.ServerTo return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil } - updatedComment, resp, err := client.Issues.EditComment(ctx, owner, repo, commentID, &github.IssueComment{ - Body: github.Ptr(body), + updatedComment, resp, err := client.Issues.UpdateComment(ctx, owner, repo, commentID, github.IssueCommentRequest{ + Body: body, }) if resp != nil && resp.Body != nil { defer func() { _ = resp.Body.Close() }() @@ -1780,7 +1780,7 @@ func SubIssueWrite(t translations.TranslationHelperFunc) inventory.ServerTool { func AddSubIssue(ctx context.Context, client *github.Client, owner string, repo string, issueNumber int, subIssueID int, replaceParent bool) (*mcp.CallToolResult, error) { subIssueRequest := github.SubIssueRequest{ SubIssueID: int64(subIssueID), - ReplaceParent: github.Ptr(replaceParent), + ReplaceParent: new(replaceParent), } subIssue, resp, err := client.SubIssue.Add(ctx, owner, repo, int64(issueNumber), subIssueRequest) @@ -2092,10 +2092,10 @@ func sanitizeIssueTitleAndBody(issue *github.Issue) { return } if issue.Title != nil { - issue.Title = github.Ptr(sanitize.PlainText(*issue.Title)) + issue.Title = new(sanitize.PlainText(*issue.Title)) } if issue.Body != nil { - issue.Body = github.Ptr(sanitize.Content(*issue.Body)) + issue.Body = new(sanitize.Content(*issue.Body)) } } @@ -2104,10 +2104,10 @@ func sanitizeSubIssueTitleAndBody(issue *github.SubIssue) { return } if issue.Title != nil { - issue.Title = github.Ptr(sanitize.PlainText(*issue.Title)) + issue.Title = new(sanitize.PlainText(*issue.Title)) } if issue.Body != nil { - issue.Body = github.Ptr(sanitize.Content(*issue.Body)) + issue.Body = new(sanitize.Content(*issue.Body)) } } @@ -3102,7 +3102,7 @@ func CreateIssue(ctx context.Context, client *github.Client, owner string, repo // Create the issue request issueRequest := github.CreateIssueRequest{ Title: title, - Body: github.Ptr(body), + Body: new(body), Assignees: assignees, Labels: labels, IssueFieldValues: issueFieldValues, @@ -3113,7 +3113,7 @@ func CreateIssue(ctx context.Context, client *github.Client, owner string, repo } if issueType != "" { - issueRequest.Type = github.Ptr(issueType) + issueRequest.Type = new(issueType) } issue, resp, err := client.Issues.Create(ctx, owner, repo, issueRequest) @@ -3185,11 +3185,11 @@ func UpdateIssue(ctx context.Context, client *github.Client, gqlClient *githubv4 // Set optional parameters if provided if title != "" { - issueRequest.Title = github.Ptr(title) + issueRequest.Title = new(title) } if body != "" { - issueRequest.Body = github.Ptr(body) + issueRequest.Body = new(body) } if updateOptions.LabelsProvided { @@ -3205,7 +3205,7 @@ func UpdateIssue(ctx context.Context, client *github.Client, gqlClient *githubv4 } if issueType != "" { - issueRequest.Type = github.Ptr(issueType) + issueRequest.Type = new(issueType) } // Field IDs to clear via DELETE after the PATCH. See the post-PATCH loop diff --git a/pkg/github/issues_delete_test.go b/pkg/github/issues_delete_test.go index 11239e3a99..d514d31084 100644 --- a/pkg/github/issues_delete_test.go +++ b/pkg/github/issues_delete_test.go @@ -10,7 +10,7 @@ import ( "testing" "github.com/github/github-mcp-server/internal/githubv4mock" - gogithub "github.com/google/go-github/v89/github" + gogithub "github.com/google/go-github/v92/github" "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -24,7 +24,7 @@ func Test_IssueRequest_EmptyFieldValues_OmittedByJSON(t *testing.T) { t.Parallel() req := &gogithub.UpdateIssueRequest{ - Title: gogithub.Ptr("still here"), + Title: new("still here"), IssueFieldValues: []*gogithub.IssueRequestFieldValue{}, } body, err := json.Marshal(req) @@ -44,10 +44,10 @@ func Test_UpdateIssue_DeleteLastFieldValueCallsDeleteEndpoint(t *testing.T) { t.Parallel() mockIssue := &gogithub.Issue{ - Number: gogithub.Ptr(42), - Title: gogithub.Ptr("Test issue"), - State: gogithub.Ptr("open"), - HTMLURL: gogithub.Ptr("https://github.com/owner/repo/issues/42"), + Number: new(42), + Title: new("Test issue"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/42"), } var ( @@ -145,10 +145,10 @@ func Test_UpdateIssue_DeleteOneOfManyUsesSetSemantics(t *testing.T) { t.Parallel() mockIssue := &gogithub.Issue{ - Number: gogithub.Ptr(42), - Title: gogithub.Ptr("Test issue"), - State: gogithub.Ptr("open"), - HTMLURL: gogithub.Ptr("https://github.com/owner/repo/issues/42"), + Number: new(42), + Title: new("Test issue"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/42"), } var ( @@ -242,10 +242,10 @@ func Test_UpdateIssue_DeleteAbsentFieldIsNoOp(t *testing.T) { t.Parallel() mockIssue := &gogithub.Issue{ - Number: gogithub.Ptr(42), - Title: gogithub.Ptr("Test issue"), - State: gogithub.Ptr("open"), - HTMLURL: gogithub.Ptr("https://github.com/owner/repo/issues/42"), + Number: new(42), + Title: new("Test issue"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/42"), } var ( @@ -334,10 +334,10 @@ func Test_UpdateIssue_DeleteFallbackContinuesOnPartialFailure(t *testing.T) { t.Parallel() mockIssue := &gogithub.Issue{ - Number: gogithub.Ptr(42), - Title: gogithub.Ptr("Test issue"), - State: gogithub.Ptr("open"), - HTMLURL: gogithub.Ptr("https://github.com/owner/repo/issues/42"), + Number: new(42), + Title: new("Test issue"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/42"), } var ( diff --git a/pkg/github/issues_granular.go b/pkg/github/issues_granular.go index f22a8a1536..cd32b9c59f 100644 --- a/pkg/github/issues_granular.go +++ b/pkg/github/issues_granular.go @@ -13,7 +13,7 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" @@ -1901,3 +1901,13 @@ func GranularRemoveIssueCommentReaction(t translations.TranslationHelperFunc) in st.FeatureRule = issuesGranularFeatureRule return st } + +// GranularHideIssueComment hides (minimizes) an issue or pull request conversation comment. +func GranularHideIssueComment(t translations.TranslationHelperFunc) inventory.ServerTool { + return commentVisibilityTool(t, issueCommentVisibilityTarget, true) +} + +// GranularUnhideIssueComment unhides (unminimizes) an issue or pull request conversation comment. +func GranularUnhideIssueComment(t translations.TranslationHelperFunc) inventory.ServerTool { + return commentVisibilityTool(t, issueCommentVisibilityTarget, false) +} diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go index 7138148c21..f279b41243 100644 --- a/pkg/github/issues_test.go +++ b/pkg/github/issues_test.go @@ -20,7 +20,7 @@ import ( transportpkg "github.com/github/github-mcp-server/pkg/http/transport" "github.com/github/github-mcp-server/pkg/inventory" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" @@ -140,38 +140,38 @@ func Test_GetIssue(t *testing.T) { // Setup mock issue for success case mockIssue := &github.Issue{ - Number: github.Ptr(42), - Title: github.Ptr("Test Issue"), - Body: github.Ptr("This is a test issue"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42"), + Number: new(42), + Title: new("Test Issue"), + Body: new("This is a test issue"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/42"), User: &github.User{ - Login: github.Ptr("testuser"), + Login: new("testuser"), }, Assignees: []*github.User{ - {Login: github.Ptr("octocat")}, - {Login: github.Ptr("mona")}, + {Login: new("octocat")}, + {Login: new("mona")}, }, Repository: &github.Repository{ - Name: github.Ptr("repo"), + Name: new("repo"), Owner: &github.User{ - Login: github.Ptr("owner"), + Login: new("owner"), }, }, } mockIssue2 := &github.Issue{ - Number: github.Ptr(422), - Title: github.Ptr("Test Issue 2"), - Body: github.Ptr("This is a test issue 2"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42"), + Number: new(422), + Title: new("Test Issue 2"), + Body: new("This is a test issue 2"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/42"), User: &github.User{ - Login: github.Ptr("testuser2"), + Login: new("testuser2"), }, Repository: &github.Repository{ - Name: github.Ptr("repo2"), + Name: new("repo2"), Owner: &github.User{ - Login: github.Ptr("owner2"), + Login: new("owner2"), }, }, } @@ -318,16 +318,16 @@ func Test_IssueRead_IFC_InsidersMode(t *testing.T) { serverTool := IssueRead(translations.NullTranslationHelper) mockIssue := &github.Issue{ - Number: github.Ptr(1), - Title: github.Ptr("Test"), - Body: github.Ptr("body"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/octocat/repo/issues/1"), - User: &github.User{Login: github.Ptr("u")}, + Number: new(1), + Title: new("Test"), + Body: new("body"), + State: new("open"), + HTMLURL: new("https://github.com/octocat/repo/issues/1"), + User: &github.User{Login: new("u")}, } mockComments := []*github.IssueComment{ - {Body: github.Ptr("hello"), User: &github.User{Login: github.Ptr("u")}}, + {Body: new("hello"), User: &github.User{Login: new("u")}}, } makeMockClient := func(isPrivate bool, repoStatus int) *http.Client { @@ -435,13 +435,13 @@ func Test_GetIssue_FieldValues(t *testing.T) { serverTool := IssueRead(translations.NullTranslationHelper) mockIssueWithFields := &github.Issue{ - Number: github.Ptr(99), - Title: github.Ptr("Issue with field values"), - Body: github.Ptr("body"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/99"), + Number: new(99), + Title: new("Issue with field values"), + Body: new("body"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/99"), User: &github.User{ - Login: github.Ptr("testuser"), + Login: new("testuser"), }, IssueFieldValues: []*github.IssueFieldValue{ { @@ -506,14 +506,14 @@ func Test_GetIssue_FieldValues_Enriched(t *testing.T) { serverTool := IssueRead(translations.NullTranslationHelper) mockIssueWithFields := &github.Issue{ - Number: github.Ptr(99), - NodeID: github.Ptr("I_node_99"), - Title: github.Ptr("Issue with field values"), - Body: github.Ptr("body"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/99"), + Number: new(99), + NodeID: new("I_node_99"), + Title: new("Issue with field values"), + Body: new("body"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/99"), User: &github.User{ - Login: github.Ptr("testuser"), + Login: new("testuser"), }, IssueFieldValues: []*github.IssueFieldValue{ { @@ -593,20 +593,20 @@ func Test_GetIssue_FieldValues_Enriched(t *testing.T) { // With no parent and no sub-issues, the routing booleans are explicit false and the // optional relationship payloads are omitted. - assert.Equal(t, github.Ptr(false), returnedIssue.HasParent, "has_parent should be false without a parent") - assert.Equal(t, github.Ptr(false), returnedIssue.HasChildren, "has_children should be false without sub-issues") + assert.Equal(t, new(false), returnedIssue.HasParent, "has_parent should be false without a parent") + assert.Equal(t, new(false), returnedIssue.HasChildren, "has_children should be false without sub-issues") assert.Nil(t, returnedIssue.Parent, "parent should be omitted when there is no parent") assert.Nil(t, returnedIssue.SubIssuesSummary, "sub_issues_summary should be omitted with no sub-issues") } func Test_GetIssue_HierarchyEnrichment(t *testing.T) { mockIssue := &github.Issue{ - Number: github.Ptr(2990), - NodeID: github.Ptr("I_node_2990"), - Title: github.Ptr("Child issue"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/2990"), - User: &github.User{Login: github.Ptr("author")}, + Number: new(2990), + NodeID: new("I_node_2990"), + Title: new("Child issue"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/2990"), + User: &github.User{Login: new("author")}, } parentNode := map[string]any{ @@ -632,8 +632,8 @@ func Test_GetIssue_HierarchyEnrichment(t *testing.T) { parent: parentNode, summary: map[string]any{"total": 4, "completed": 1, "percentCompleted": 25}, assertResponse: func(t *testing.T, issue MinimalIssue) { - assert.Equal(t, github.Ptr(true), issue.HasParent) - assert.Equal(t, github.Ptr(true), issue.HasChildren) + assert.Equal(t, new(true), issue.HasParent) + assert.Equal(t, new(true), issue.HasChildren) require.NotNil(t, issue.Parent) assert.Equal(t, 2820, issue.Parent.Number) assert.Equal(t, "Parent issue", issue.Parent.Title) @@ -650,7 +650,7 @@ func Test_GetIssue_HierarchyEnrichment(t *testing.T) { parent: nil, summary: map[string]any{"total": 0, "completed": 0, "percentCompleted": 0}, assertResponse: func(t *testing.T, issue MinimalIssue) { - assert.Equal(t, github.Ptr(false), issue.HasParent) + assert.Equal(t, new(false), issue.HasParent) assert.Nil(t, issue.Parent) }, }, @@ -659,7 +659,7 @@ func Test_GetIssue_HierarchyEnrichment(t *testing.T) { parent: nil, summary: map[string]any{"total": 0, "completed": 1, "percentCompleted": 0}, assertResponse: func(t *testing.T, issue MinimalIssue) { - assert.Equal(t, github.Ptr(false), issue.HasChildren) + assert.Equal(t, new(false), issue.HasChildren) assert.Nil(t, issue.SubIssuesSummary) }, }, @@ -713,12 +713,12 @@ func Test_GetIssue_HierarchyEnrichment(t *testing.T) { func Test_GetIssue_HierarchyEnrichment_Lockdown(t *testing.T) { mockIssue := &github.Issue{ - Number: github.Ptr(2990), - NodeID: github.Ptr("I_node_2990"), - Title: github.Ptr("Child issue"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/2990"), - User: &github.User{Login: github.Ptr("author")}, + Number: new(2990), + NodeID: new("I_node_2990"), + Title: new("Child issue"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/2990"), + User: &github.User{Login: new("author")}, } parentNode := map[string]any{ @@ -780,17 +780,17 @@ func Test_GetIssue_HierarchyEnrichment_Lockdown(t *testing.T) { require.NoError(t, json.Unmarshal([]byte(getTextResult(t, result).Text), &returnedIssue)) require.Nil(t, returnedIssue.Parent, "parent reference should be omitted under lockdown when it cannot be verified safe") - assert.Equal(t, github.Ptr(true), returnedIssue.HasParent, "has_parent should still be true so agents can route to get_parent") + assert.Equal(t, new(true), returnedIssue.HasParent, "has_parent should still be true so agents can route to get_parent") } func Test_GetIssue_HierarchyEnrichment_QueryFailureReturnsBaseIssue(t *testing.T) { mockIssue := &github.Issue{ - Number: github.Ptr(2990), - NodeID: github.Ptr("I_node_2990"), - Title: github.Ptr("Child issue"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/2990"), - User: &github.User{Login: github.Ptr("author")}, + Number: new(2990), + NodeID: new("I_node_2990"), + Title: new("Child issue"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/2990"), + User: &github.User{Login: new("author")}, } restClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ @@ -833,12 +833,12 @@ func Test_GetIssue_HierarchyEnrichment_QueryFailureReturnsBaseIssue(t *testing.T func Test_GetIssue_ClosedByPullRequests(t *testing.T) { mockIssue := &github.Issue{ - Number: github.Ptr(2990), - NodeID: github.Ptr("I_node_2990"), - Title: github.Ptr("Broken thing"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/2990"), - User: &github.User{Login: github.Ptr("author")}, + Number: new(2990), + NodeID: new("I_node_2990"), + Title: new("Broken thing"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/2990"), + User: &github.User{Login: new("author")}, } tests := []struct { @@ -994,12 +994,12 @@ func closingPullRequestFixtures(n int) []map[string]any { func Test_GetIssue_ClosedByPullRequests_Lockdown(t *testing.T) { mockIssue := &github.Issue{ - Number: github.Ptr(2990), - NodeID: github.Ptr("I_node_2990"), - Title: github.Ptr("Broken thing"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/2990"), - User: &github.User{Login: github.Ptr("author")}, + Number: new(2990), + NodeID: new("I_node_2990"), + Title: new("Broken thing"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/2990"), + User: &github.User{Login: new("author")}, } restClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ @@ -1093,29 +1093,29 @@ func Test_SearchIssues(t *testing.T) { // Setup mock search results mockSearchResult := &github.IssuesSearchResult{ - Total: github.Ptr(2), - IncompleteResults: github.Ptr(false), + Total: new(2), + IncompleteResults: new(false), Issues: []*github.Issue{ { - Number: github.Ptr(42), - Title: github.Ptr("Bug: Something is broken"), - Body: github.Ptr("This is a bug report"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42"), - Comments: github.Ptr(5), + Number: new(42), + Title: new("Bug: Something is broken"), + Body: new("This is a bug report"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/42"), + Comments: new(5), User: &github.User{ - Login: github.Ptr("user1"), + Login: new("user1"), }, }, { - Number: github.Ptr(43), - Title: github.Ptr("Feature: Add new functionality"), - Body: github.Ptr("This is a feature request"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/43"), - Comments: github.Ptr(3), + Number: new(43), + Title: new("Feature: Add new functionality"), + Body: new("This is a feature request"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/43"), + Comments: new(3), User: &github.User{ - Login: github.Ptr("user2"), + Login: new("user2"), }, }, }, @@ -1439,11 +1439,11 @@ func Test_SearchIssues_IFC_InsidersMode(t *testing.T) { makeIssue := func(owner, repo string, number int) *github.Issue { return &github.Issue{ - Number: github.Ptr(number), - Title: github.Ptr("issue"), - State: github.Ptr("open"), - RepositoryURL: github.Ptr("https://api.github.com/repos/" + owner + "/" + repo), - User: &github.User{Login: github.Ptr("u")}, + Number: new(number), + Title: new("issue"), + State: new("open"), + RepositoryURL: new("https://api.github.com/repos/" + owner + "/" + repo), + User: &github.User{Login: new("u")}, } } @@ -1720,17 +1720,17 @@ func Test_SearchIssues_FieldValuesEnrichment(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockSearchResult := &github.IssuesSearchResult{ - Total: github.Ptr(1), - IncompleteResults: github.Ptr(false), + Total: new(1), + IncompleteResults: new(false), Issues: []*github.Issue{ { - Number: github.Ptr(42), - Title: github.Ptr("Bug: Something is broken"), - Body: github.Ptr("Details"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42"), - NodeID: github.Ptr("I_node_42"), - User: &github.User{Login: github.Ptr("user1")}, + Number: new(42), + Title: new("Bug: Something is broken"), + Body: new("Details"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/42"), + NodeID: new("I_node_42"), + User: &github.User{Login: new("user1")}, IssueFieldValues: []*github.IssueFieldValue{ {IssueFieldID: 99, DataType: "text", Value: "raw REST value"}, }, @@ -1851,15 +1851,15 @@ func Test_CreateIssue(t *testing.T) { // Setup mock issue for success case mockIssue := &github.Issue{ - Number: github.Ptr(123), - Title: github.Ptr("Test Issue"), - Body: github.Ptr("This is a test issue"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/123"), - Assignees: []*github.User{{Login: github.Ptr("user1")}, {Login: github.Ptr("user2")}}, + Number: new(123), + Title: new("Test Issue"), + Body: new("This is a test issue"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/123"), + Assignees: []*github.User{{Login: new("user1")}, {Login: new("user2")}}, Labels: []*github.Label{{Name: "bug"}, {Name: "help wanted"}}, - Milestone: &github.Milestone{Number: github.Ptr(5)}, - Type: &github.IssueType{Name: github.Ptr("Bug")}, + Milestone: &github.Milestone{Number: new(5)}, + Type: &github.IssueType{Name: new("Bug")}, } tests := []struct { @@ -1903,10 +1903,10 @@ func Test_CreateIssue(t *testing.T) { name: "successful issue creation with minimal fields", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ PostReposIssuesByOwnerByRepo: mockResponse(t, http.StatusCreated, &github.Issue{ - Number: github.Ptr(124), - Title: github.Ptr("Minimal Issue"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/124"), - State: github.Ptr("open"), + Number: new(124), + Title: new("Minimal Issue"), + HTMLURL: new("https://github.com/owner/repo/issues/124"), + State: new("open"), }), }), requestArgs: map[string]any{ @@ -1918,10 +1918,10 @@ func Test_CreateIssue(t *testing.T) { }, expectError: false, expectedIssue: &github.Issue{ - Number: github.Ptr(124), - Title: github.Ptr("Minimal Issue"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/124"), - State: github.Ptr("open"), + Number: new(124), + Title: new("Minimal Issue"), + HTMLURL: new("https://github.com/owner/repo/issues/124"), + State: new("open"), }, }, { @@ -1936,10 +1936,10 @@ func Test_CreateIssue(t *testing.T) { }, }).andThen( mockResponse(t, http.StatusCreated, &github.Issue{ - Number: github.Ptr(125), - Title: github.Ptr("Issue with fields"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/125"), - State: github.Ptr("open"), + Number: new(125), + Title: new("Issue with fields"), + HTMLURL: new("https://github.com/owner/repo/issues/125"), + State: new("open"), }), ), }), @@ -1987,10 +1987,10 @@ func Test_CreateIssue(t *testing.T) { }, expectError: false, expectedIssue: &github.Issue{ - Number: github.Ptr(125), - Title: github.Ptr("Issue with fields"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/125"), - State: github.Ptr("open"), + Number: new(125), + Title: new("Issue with fields"), + HTMLURL: new("https://github.com/owner/repo/issues/125"), + State: new("open"), }, }, { @@ -2132,9 +2132,9 @@ func TestIssueWriteReportsUnappliedLabels(t *testing.T) { responseLabels = append(responseLabels, &github.Label{Name: label}) } responseIssue := &github.Issue{ - ID: github.Ptr(int64(123)), - Number: github.Ptr(123), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/123"), + ID: new(int64(123)), + Number: new(123), + HTMLURL: new("https://github.com/owner/repo/issues/123"), Labels: responseLabels, } @@ -2202,9 +2202,9 @@ func Test_IssueWrite_MCPAppsFeature_UIGate(t *testing.T) { t.Parallel() mockIssue := &github.Issue{ - Number: github.Ptr(1), - Title: github.Ptr("Test"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/1"), + Number: new(1), + Title: new("Test"), + HTMLURL: new("https://github.com/owner/repo/issues/1"), } serverTool := IssueWrite(translations.NullTranslationHelper) @@ -2216,7 +2216,7 @@ func Test_IssueWrite_MCPAppsFeature_UIGate(t *testing.T) { deps := BaseDeps{ Client: client, GQLClient: githubv4.NewClient(nil), - featureChecker: featureCheckerFor(MCPAppsFeatureFlag), + featureChecker: featureCheckerFor(), } handler := serverTool.Handler(deps) @@ -2568,7 +2568,7 @@ func TestIssueWriteCreateWithParentPreservesSupportedFields(t *testing.T) { ) restHTTPClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ "GET /repos/{owner}/{repo}/issue-types": mockResponse(t, http.StatusOK, []*github.IssueType{ - {Name: github.Ptr("Bug"), NodeID: github.Ptr("ISSUE_TYPE_bug")}, + {Name: new("Bug"), NodeID: new("ISSUE_TYPE_bug")}, }), }) restCounter := &countingRoundTripper{next: restHTTPClient.Transport} @@ -4629,36 +4629,36 @@ func Test_UpdateIssue(t *testing.T) { // Mock issues for reuse across test cases mockBaseIssue := &github.Issue{ - Number: github.Ptr(123), - Title: github.Ptr("Title"), - Body: github.Ptr("Description"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/123"), - Assignees: []*github.User{{Login: github.Ptr("assignee1")}, {Login: github.Ptr("assignee2")}}, + Number: new(123), + Title: new("Title"), + Body: new("Description"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/123"), + Assignees: []*github.User{{Login: new("assignee1")}, {Login: new("assignee2")}}, Labels: []*github.Label{{Name: "bug"}, {Name: "priority"}}, - Milestone: &github.Milestone{Number: github.Ptr(5)}, - Type: &github.IssueType{Name: github.Ptr("Bug")}, + Milestone: &github.Milestone{Number: new(5)}, + Type: &github.IssueType{Name: new("Bug")}, } mockUpdatedIssue := &github.Issue{ - Number: github.Ptr(123), - Title: github.Ptr("Updated Title"), - Body: github.Ptr("Updated Description"), - State: github.Ptr("closed"), - StateReason: github.Ptr("duplicate"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/123"), - Assignees: []*github.User{{Login: github.Ptr("assignee1")}, {Login: github.Ptr("assignee2")}}, + Number: new(123), + Title: new("Updated Title"), + Body: new("Updated Description"), + State: new("closed"), + StateReason: new("duplicate"), + HTMLURL: new("https://github.com/owner/repo/issues/123"), + Assignees: []*github.User{{Login: new("assignee1")}, {Login: new("assignee2")}}, Labels: []*github.Label{{Name: "bug"}, {Name: "priority"}}, - Milestone: &github.Milestone{Number: github.Ptr(5)}, - Type: &github.IssueType{Name: github.Ptr("Bug")}, + Milestone: &github.Milestone{Number: new(5)}, + Type: &github.IssueType{Name: new("Bug")}, } mockReopenedIssue := &github.Issue{ - Number: github.Ptr(123), - Title: github.Ptr("Title"), - State: github.Ptr("open"), - StateReason: github.Ptr("reopened"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/123"), + Number: new(123), + Title: new("Title"), + State: new("open"), + StateReason: new("reopened"), + HTMLURL: new("https://github.com/owner/repo/issues/123"), } // Mock GraphQL responses for reuse across test cases @@ -4745,8 +4745,8 @@ func Test_UpdateIssue(t *testing.T) { "assignees": []any{}, }).andThen( mockResponse(t, http.StatusOK, &github.Issue{ - Number: github.Ptr(123), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/123"), + Number: new(123), + HTMLURL: new("https://github.com/owner/repo/issues/123"), }), ), }), @@ -4761,7 +4761,7 @@ func Test_UpdateIssue(t *testing.T) { }, expectError: false, expectedIssue: &github.Issue{ - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/123"), + HTMLURL: new("https://github.com/owner/repo/issues/123"), }, }, { @@ -5047,15 +5047,15 @@ func Test_UpdateIssue(t *testing.T) { "type": "Bug", }).andThen( mockResponse(t, http.StatusOK, &github.Issue{ - Number: github.Ptr(123), - Title: github.Ptr("Updated Title"), - Body: github.Ptr("Updated Description"), + Number: new(123), + Title: new("Updated Title"), + Body: new("Updated Description"), Labels: []*github.Label{{Name: "bug"}, {Name: "priority"}}, - Assignees: []*github.User{{Login: github.Ptr("assignee1")}, {Login: github.Ptr("assignee2")}}, - Milestone: &github.Milestone{Number: github.Ptr(5)}, - Type: &github.IssueType{Name: github.Ptr("Bug")}, - State: github.Ptr("open"), // Still open after REST update - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/123"), + Assignees: []*github.User{{Login: new("assignee1")}, {Login: new("assignee2")}}, + Milestone: &github.Milestone{Number: new(5)}, + Type: &github.IssueType{Name: new("Bug")}, + State: new("open"), // Still open after REST update + HTMLURL: new("https://github.com/owner/repo/issues/123"), }), ), }), @@ -5214,8 +5214,8 @@ func Test_UpdateIssue(t *testing.T) { func Test_UpdateIssueClearsLabelsAndAssignees(t *testing.T) { serverTool := IssueWrite(translations.NullTranslationHelper) updatedIssue := &github.Issue{ - Number: github.Ptr(8), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/8"), + Number: new(8), + HTMLURL: new("https://github.com/owner/repo/issues/8"), } client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ @@ -5321,18 +5321,18 @@ func Test_GetIssueComments(t *testing.T) { // Setup mock comments for success case mockComments := []*github.IssueComment{ { - ID: github.Ptr(int64(123)), - Body: github.Ptr("This is the first comment"), + ID: new(int64(123)), + Body: new("This is the first comment"), User: &github.User{ - Login: github.Ptr("user1"), + Login: new("user1"), }, CreatedAt: &github.Timestamp{Time: time.Now().Add(-time.Hour * 24)}, }, { - ID: github.Ptr(int64(456)), - Body: github.Ptr("This is the second comment"), + ID: new(int64(456)), + Body: new("This is the second comment"), User: &github.User{ - Login: github.Ptr("user2"), + Login: new("user2"), }, CreatedAt: &github.Timestamp{Time: time.Now().Add(-time.Hour)}, }, @@ -5401,14 +5401,14 @@ func Test_GetIssueComments(t *testing.T) { mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposIssuesCommentsByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, []*github.IssueComment{ { - ID: github.Ptr(int64(789)), - Body: github.Ptr("Maintainer comment"), - User: &github.User{Login: github.Ptr("maintainer")}, + ID: new(int64(789)), + Body: new("Maintainer comment"), + User: &github.User{Login: new("maintainer")}, }, { - ID: github.Ptr(int64(790)), - Body: github.Ptr("External user comment"), - User: &github.User{Login: github.Ptr("testuser")}, + ID: new(int64(790)), + Body: new("External user comment"), + User: &github.User{Login: new("testuser")}, }, }), }), @@ -5421,9 +5421,9 @@ func Test_GetIssueComments(t *testing.T) { expectError: false, expectedComments: []*github.IssueComment{ { - ID: github.Ptr(int64(789)), - Body: github.Ptr("Maintainer comment"), - User: &github.User{Login: github.Ptr("maintainer")}, + ID: new(int64(789)), + Body: new("Maintainer comment"), + User: &github.User{Login: new("maintainer")}, }, }, lockdownEnabled: true, @@ -5773,19 +5773,19 @@ func Test_AddSubIssue(t *testing.T) { // Setup mock issue for success case (matches GitHub API response format) mockIssue := &github.Issue{ - Number: github.Ptr(42), - Title: github.Ptr("can't \"quote\" AT&T\u200B"), - Body: github.Ptr("This is **Markdown**\u200B"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42"), + Number: new(42), + Title: new("can't \"quote\" AT&T\u200B"), + Body: new("This is **Markdown**\u200B"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/42"), User: &github.User{ - Login: github.Ptr("testuser"), + Login: new("testuser"), }, Labels: []*github.Label{ { Name: "enhancement", Color: "84b6eb", - Description: github.Ptr("New feature or request"), + Description: new("New feature or request"), }, }, } @@ -5998,33 +5998,33 @@ func Test_GetSubIssues(t *testing.T) { // Setup mock sub-issues for success case mockSubIssues := []*github.Issue{ { - Number: github.Ptr(123), - Title: github.Ptr("can't \"quote\" AT&T\u200B"), - Body: github.Ptr("This is **Markdown**\u200B"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/123"), + Number: new(123), + Title: new("can't \"quote\" AT&T\u200B"), + Body: new("This is **Markdown**\u200B"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/123"), User: &github.User{ - Login: github.Ptr("user1"), + Login: new("user1"), }, Labels: []*github.Label{ { Name: "bug", Color: "d73a4a", - Description: github.Ptr("Something isn't working"), + Description: new("Something isn't working"), }, }, }, { - Number: github.Ptr(124), - Title: github.Ptr("Sub-issue 2"), - Body: github.Ptr("This is the second sub-issue"), - State: github.Ptr("closed"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/124"), + Number: new(124), + Title: new("Sub-issue 2"), + Body: new("This is the second sub-issue"), + State: new("closed"), + HTMLURL: new("https://github.com/owner/repo/issues/124"), User: &github.User{ - Login: github.Ptr("user2"), + Login: new("user2"), }, Assignees: []*github.User{ - {Login: github.Ptr("assignee1")}, + {Login: new("assignee1")}, }, }, } @@ -6321,17 +6321,17 @@ func TestAddIssueCommentHandler(t *testing.T) { serverTool := AddIssueComment(translations.NullTranslationHelper) mockComment := &github.IssueComment{ - ID: github.Ptr(int64(456)), - Body: github.Ptr("This is a comment"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42#issuecomment-456"), + ID: new(int64(456)), + Body: new("This is a comment"), + HTMLURL: new("https://github.com/owner/repo/issues/42#issuecomment-456"), } mockReaction := &github.Reaction{ - ID: github.Ptr(int64(789)), - Content: github.Ptr("heart"), + ID: new(int64(789)), + Content: new("heart"), } mockIssueComment := &github.IssueComment{ - ID: github.Ptr(int64(999)), - IssueURL: github.Ptr("https://api.github.com/repos/owner/repo/issues/42"), + ID: new(int64(999)), + IssueURL: new("https://api.github.com/repos/owner/repo/issues/42"), } commentCreatedAfterReactionFailure := &atomic.Bool{} @@ -6346,7 +6346,9 @@ func TestAddIssueCommentHandler(t *testing.T) { { name: "successful comment on issue", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ - PostReposIssuesCommentsByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusCreated, mockComment), + PostReposIssuesCommentsByOwnerByRepoByIssueNumber: expectRequestBody(t, map[string]any{ + "body": "This is a comment", + }).andThen(mockResponse(t, http.StatusCreated, mockComment)), }), requestArgs: map[string]any{ "owner": "owner", @@ -6385,8 +6387,8 @@ func TestAddIssueCommentHandler(t *testing.T) { name: "issue comment reaction requires matching issue_number", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposIssuesCommentByOwnerByRepoByCommentID: mockResponse(t, http.StatusOK, &github.IssueComment{ - ID: github.Ptr(int64(999)), - IssueURL: github.Ptr("https://api.github.com/repos/owner/repo/issues/43"), + ID: new(int64(999)), + IssueURL: new("https://api.github.com/repos/owner/repo/issues/43"), }), }), requestArgs: map[string]any{ @@ -6715,9 +6717,9 @@ func TestUpdateIssueCommentHandler(t *testing.T) { t.Parallel() updatedComment := &github.IssueComment{ - ID: github.Ptr(int64(456)), - Body: github.Ptr("Updated comment"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42#issuecomment-456"), + ID: new(int64(456)), + Body: new("Updated comment"), + HTMLURL: new("https://github.com/owner/repo/issues/42#issuecomment-456"), } tests := []struct { @@ -6845,19 +6847,19 @@ func Test_RemoveSubIssue(t *testing.T) { // Setup mock issue for success case (matches GitHub API response format - the updated parent issue) mockIssue := &github.Issue{ - Number: github.Ptr(42), - Title: github.Ptr("can't \"quote\" AT&T\u200B"), - Body: github.Ptr("This is **Markdown**\u200B"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42"), + Number: new(42), + Title: new("can't \"quote\" AT&T\u200B"), + Body: new("This is **Markdown**\u200B"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/42"), User: &github.User{ - Login: github.Ptr("testuser"), + Login: new("testuser"), }, Labels: []*github.Label{ { Name: "enhancement", Color: "84b6eb", - Description: github.Ptr("New feature or request"), + Description: new("New feature or request"), }, }, } @@ -7053,19 +7055,19 @@ func Test_ReprioritizeSubIssue(t *testing.T) { // Setup mock issue for success case (matches GitHub API response format - the updated parent issue) mockIssue := &github.Issue{ - Number: github.Ptr(42), - Title: github.Ptr("can't \"quote\" AT&T\u200B"), - Body: github.Ptr("This is **Markdown**\u200B"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/42"), + Number: new(42), + Title: new("can't \"quote\" AT&T\u200B"), + Body: new("This is **Markdown**\u200B"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/issues/42"), User: &github.User{ - Login: github.Ptr("testuser"), + Login: new("testuser"), }, Labels: []*github.Label{ { Name: "enhancement", Color: "84b6eb", - Description: github.Ptr("New feature or request"), + Description: new("New feature or request"), }, }, } @@ -7308,16 +7310,16 @@ func Test_ListIssueTypes(t *testing.T) { // Setup mock issue types for success case mockIssueTypes := []*github.IssueType{ { - ID: github.Ptr(int64(1)), - Name: github.Ptr("bug"), - Description: github.Ptr("Something isn't working"), - Color: github.Ptr("d73a4a"), + ID: new(int64(1)), + Name: new("bug"), + Description: new("Something isn't working"), + Color: new("d73a4a"), }, { - ID: github.Ptr(int64(2)), - Name: github.Ptr("feature"), - Description: github.Ptr("New feature or enhancement"), - Color: github.Ptr("a2eeef"), + ID: new(int64(2)), + Name: new("feature"), + Description: new("New feature or enhancement"), + Color: new("a2eeef"), }, } diff --git a/pkg/github/minimal_types.go b/pkg/github/minimal_types.go index 2eba9a1628..2bb9efba35 100644 --- a/pkg/github/minimal_types.go +++ b/pkg/github/minimal_types.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/github/github-mcp-server/pkg/sanitize" @@ -1211,19 +1211,19 @@ func convertToMinimalProject(fullProject *github.ProjectV2) *MinimalProject { } return &MinimalProject{ - ID: github.Ptr(fullProject.GetID()), - NodeID: github.Ptr(fullProject.GetNodeID()), + ID: new(fullProject.GetID()), + NodeID: new(fullProject.GetNodeID()), Owner: convertToMinimalUser(fullProject.GetOwner()), Creator: convertToMinimalUser(fullProject.GetCreator()), - Title: github.Ptr(fullProject.GetTitle()), - Description: github.Ptr(fullProject.GetDescription()), - Public: github.Ptr(fullProject.GetPublic()), - ClosedAt: github.Ptr(fullProject.GetClosedAt()), - CreatedAt: github.Ptr(fullProject.GetCreatedAt()), - UpdatedAt: github.Ptr(fullProject.GetUpdatedAt()), - DeletedAt: github.Ptr(fullProject.GetDeletedAt()), - Number: github.Ptr(fullProject.GetNumber()), - ShortDescription: github.Ptr(fullProject.GetShortDescription()), + Title: new(fullProject.GetTitle()), + Description: new(fullProject.GetDescription()), + Public: new(fullProject.GetPublic()), + ClosedAt: new(fullProject.GetClosedAt()), + CreatedAt: new(fullProject.GetCreatedAt()), + UpdatedAt: new(fullProject.GetUpdatedAt()), + DeletedAt: new(fullProject.GetDeletedAt()), + Number: new(fullProject.GetNumber()), + ShortDescription: new(fullProject.GetShortDescription()), DeletedBy: convertToMinimalUser(fullProject.GetDeletedBy()), } } diff --git a/pkg/github/notifications.go b/pkg/github/notifications.go index a637951f0a..117e96c806 100644 --- a/pkg/github/notifications.go +++ b/pkg/github/notifications.go @@ -14,7 +14,7 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/github/notifications_test.go b/pkg/github/notifications_test.go index f2cbaff9f4..09ee191914 100644 --- a/pkg/github/notifications_test.go +++ b/pkg/github/notifications_test.go @@ -8,7 +8,7 @@ import ( "github.com/github/github-mcp-server/internal/toolsnaps" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -35,8 +35,8 @@ func Test_ListNotifications(t *testing.T) { // All fields are optional, so Required should be empty assert.Empty(t, schema.Required) mockNotification := &github.Notification{ - ID: github.Ptr("123"), - Reason: github.Ptr("mention"), + ID: new("123"), + Reason: new("mention"), } tests := []struct { @@ -156,8 +156,8 @@ func Test_ManageNotificationSubscription(t *testing.T) { assert.Contains(t, schema.Properties, "action") assert.Equal(t, []string{"notificationID", "action"}, schema.Required) - mockSub := &github.Subscription{Ignored: github.Ptr(true)} - mockSubWatch := &github.Subscription{Ignored: github.Ptr(false), Subscribed: github.Ptr(true)} + mockSub := &github.Subscription{Ignored: new(true)} + mockSubWatch := &github.Subscription{Ignored: new(false), Subscribed: new(true)} tests := []struct { name string @@ -179,7 +179,7 @@ func Test_ManageNotificationSubscription(t *testing.T) { "action": "ignore", }, expectError: false, - expectIgnored: github.Ptr(true), + expectIgnored: new(true), }, { name: "watch subscription", @@ -191,7 +191,7 @@ func Test_ManageNotificationSubscription(t *testing.T) { "action": "watch", }, expectError: false, - expectIgnored: github.Ptr(false), + expectIgnored: new(false), }, { name: "delete subscription", @@ -296,8 +296,8 @@ func Test_ManageRepositoryNotificationSubscription(t *testing.T) { assert.Contains(t, schema.Properties, "action") assert.Equal(t, []string{"owner", "repo", "action"}, schema.Required) - mockSub := &github.Subscription{Ignored: github.Ptr(true)} - mockWatchSub := &github.Subscription{Ignored: github.Ptr(false), Subscribed: github.Ptr(true)} + mockSub := &github.Subscription{Ignored: new(true)} + mockWatchSub := &github.Subscription{Ignored: new(false), Subscribed: new(true)} tests := []struct { name string @@ -321,7 +321,7 @@ func Test_ManageRepositoryNotificationSubscription(t *testing.T) { "action": "ignore", }, expectError: false, - expectIgnored: github.Ptr(true), + expectIgnored: new(true), }, { name: "watch subscription", @@ -334,8 +334,8 @@ func Test_ManageRepositoryNotificationSubscription(t *testing.T) { "action": "watch", }, expectError: false, - expectIgnored: github.Ptr(false), - expectSubscribed: github.Ptr(true), + expectIgnored: new(false), + expectSubscribed: new(true), }, { name: "delete subscription", @@ -679,7 +679,7 @@ func Test_GetNotificationDetails(t *testing.T) { assert.Contains(t, schema.Properties, "notificationID") assert.Equal(t, []string{"notificationID"}, schema.Required) - mockThread := &github.Notification{ID: github.Ptr("123"), Reason: github.Ptr("mention")} + mockThread := &github.Notification{ID: new("123"), Reason: new("mention")} tests := []struct { name string diff --git a/pkg/github/params.go b/pkg/github/params.go index a03a6a1581..f4da2d6814 100644 --- a/pkg/github/params.go +++ b/pkg/github/params.go @@ -6,7 +6,7 @@ import ( "math" "strconv" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" ) diff --git a/pkg/github/params_test.go b/pkg/github/params_test.go index 55a5526f77..3c0d7f43d1 100644 --- a/pkg/github/params_test.go +++ b/pkg/github/params_test.go @@ -5,7 +5,7 @@ import ( "math" "testing" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/github/projects.go b/pkg/github/projects.go index 28e15d3bd1..4cb6a875cd 100644 --- a/pkg/github/projects.go +++ b/pkg/github/projects.go @@ -19,7 +19,7 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" @@ -625,8 +625,7 @@ Use this tool to get details about individual projects, project fields, project } resolvedIDs, resolveErr := resolveFieldNamesToIDs(ctx, gqlClient, owner, ownerType, projectNumber, fieldNames, "fields") if resolveErr != nil { - var structured *ghErrors.StructuredResolutionError - if errors.As(resolveErr, &structured) { + if structured, ok := errors.AsType[*ghErrors.StructuredResolutionError](resolveErr); ok { return ghErrors.NewStructuredResolutionErrorResponse(structured), nil, nil } return utils.NewToolResultError(resolveErr.Error()), nil, nil @@ -984,8 +983,7 @@ func ProjectsWrite(t translations.TranslationHelperFunc) inventory.ServerTool { // Resolve the item by (item_owner, item_repo, issue_number). resolvedItemID, resolveErr := resolveItemIDFromIssueArgs(ctx, gqlClient, owner, ownerType, projectNumber, args) if resolveErr != nil { - var structured *ghErrors.StructuredResolutionError - if errors.As(resolveErr, &structured) { + if structured, ok := errors.AsType[*ghErrors.StructuredResolutionError](resolveErr); ok { return ghErrors.NewStructuredResolutionErrorResponse(structured), nil, nil } return utils.NewToolResultError(resolveErr.Error()), nil, nil @@ -1252,8 +1250,7 @@ func listProjectItems(ctx context.Context, client *github.Client, gqlClient *git if len(fieldNames) > 0 { resolvedIDs, resolveErr := resolveFieldNamesToIDs(ctx, gqlClient, owner, ownerType, projectNumber, fieldNames, "fields") if resolveErr != nil { - var structured *ghErrors.StructuredResolutionError - if errors.As(resolveErr, &structured) { + if structured, ok := errors.AsType[*ghErrors.StructuredResolutionError](resolveErr); ok { return ghErrors.NewStructuredResolutionErrorResponse(structured), nil, nil } return utils.NewToolResultError(resolveErr.Error()), nil, nil @@ -1446,8 +1443,7 @@ func fetchProjectItem(ctx context.Context, client *github.Client, owner, ownerTy func updateProjectItem(ctx context.Context, client *github.Client, gqlClient *githubv4.Client, owner, ownerType string, projectNumber int, itemID int64, fieldValue map[string]any) (*mcp.CallToolResult, any, error) { updatePayload, issueField, err := buildUpdateProjectItem(ctx, gqlClient, owner, ownerType, projectNumber, fieldValue) if err != nil { - var structured *ghErrors.StructuredResolutionError - if errors.As(err, &structured) { + if structured, ok := errors.AsType[*ghErrors.StructuredResolutionError](err); ok { return ghErrors.NewStructuredResolutionErrorResponse(structured), nil, nil } return utils.NewToolResultError(err.Error()), nil, nil @@ -1469,8 +1465,7 @@ func updateProjectItem(ctx context.Context, client *github.Client, gqlClient *gi issueID, resolveErr := projectItemIssueID(projectItem) if resolveErr != nil { - var structured *ghErrors.StructuredResolutionError - if errors.As(resolveErr, &structured) { + if structured, ok := errors.AsType[*ghErrors.StructuredResolutionError](resolveErr); ok { return ghErrors.NewStructuredResolutionErrorResponse(structured), nil, nil } return utils.NewToolResultError(resolveErr.Error()), nil, nil @@ -2136,8 +2131,7 @@ func createProjectView(ctx context.Context, gqlClient *githubv4.Client, args map } configuration, err := projectViewVisibleFieldsInput(ctx, gqlClient, args, owner, ownerType, projectNumber) if err != nil { - var structured *ghErrors.StructuredResolutionError - if errors.As(err, &structured) { + if structured, ok := errors.AsType[*ghErrors.StructuredResolutionError](err); ok { return ghErrors.NewStructuredResolutionErrorResponse(structured), nil, nil } return utils.NewToolResultError(err.Error()), nil, nil @@ -2267,8 +2261,7 @@ func updateProjectView(ctx context.Context, gqlClient *githubv4.Client, args map configuration, err := projectViewVisibleFieldsInput(ctx, gqlClient, args, owner, ownerType, projectNumber) if err != nil { - var structured *ghErrors.StructuredResolutionError - if errors.As(err, &structured) { + if structured, ok := errors.AsType[*ghErrors.StructuredResolutionError](err); ok { return ghErrors.NewStructuredResolutionErrorResponse(structured), nil, nil } return utils.NewToolResultError(err.Error()), nil, nil diff --git a/pkg/github/projects_batch.go b/pkg/github/projects_batch.go index 28493a4bf2..3c4e16c979 100644 --- a/pkg/github/projects_batch.go +++ b/pkg/github/projects_batch.go @@ -11,7 +11,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" ) @@ -182,8 +182,7 @@ func updateProjectItemsBatch(ctx context.Context, client *github.Client, gqlClie } func batchTopLevelError(err error) *mcp.CallToolResult { - var structured *ghErrors.StructuredResolutionError - if errors.As(err, &structured) { + if structured, ok := errors.AsType[*ghErrors.StructuredResolutionError](err); ok { return ghErrors.NewStructuredResolutionErrorResponse(structured) } return utils.NewToolResultError(err.Error()) @@ -796,8 +795,7 @@ func resolveIssueRefs(ctx context.Context, gqlClient *githubv4.Client, projectID } func batchErrorFromResolution(err error) *batchItemError { - var structured *ghErrors.StructuredResolutionError - if errors.As(err, &structured) { + if structured, ok := errors.AsType[*ghErrors.StructuredResolutionError](err); ok { return &batchItemError{ Code: structured.Kind, Message: fmt.Sprintf("%s: %s", structured.Kind, structured.Name), diff --git a/pkg/github/projects_batch_mutation.go b/pkg/github/projects_batch_mutation.go index 0c478aabf9..13913e53bf 100644 --- a/pkg/github/projects_batch_mutation.go +++ b/pkg/github/projects_batch_mutation.go @@ -105,7 +105,7 @@ func executeAliasedMutation(ctx context.Context, gqlClient *githubv4.Client, kin outcomes := make([]mutationAliasOutcome, len(inputs)) elem := mutationPtr.Elem() for i := range inputs { - result, ok := elem.Field(i).Interface().(projectV2ItemMutationResult) + result, ok := reflect.TypeAssert[projectV2ItemMutationResult](elem.Field(i)) if !ok || result.ProjectV2Item.ID == "" { continue } diff --git a/pkg/github/projects_batch_test.go b/pkg/github/projects_batch_test.go index 985cd5bfc7..7e17849e4e 100644 --- a/pkg/github/projects_batch_test.go +++ b/pkg/github/projects_batch_test.go @@ -396,10 +396,10 @@ func Test_ProjectsWrite_UpdateProjectItems_NumericItemIDDeduplicatesRESTLookup(t } gqlClient := newTestGQLClient(transport) - var restCalls int32 + var restCalls atomic.Int32 restClient := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetOrgsProjectsV2ItemsByProjectByItemID: func(w http.ResponseWriter, _ *http.Request) { - atomic.AddInt32(&restCalls, 1) + restCalls.Add(1) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"id":1001,"node_id":"PVTI_item1001"}`)) @@ -427,7 +427,7 @@ func Test_ProjectsWrite_UpdateProjectItems_NumericItemIDDeduplicatesRESTLookup(t require.NoError(t, json.Unmarshal([]byte(getTextResult(t, result).Text), &response)) assert.Equal(t, float64(1), response["succeeded"]) assert.Equal(t, float64(1), response["failed"]) - assert.Equal(t, int32(1), atomic.LoadInt32(&restCalls), "the same numeric item_id must only be resolved once") + assert.Equal(t, int32(1), restCalls.Load(), "the same numeric item_id must only be resolved once") results := response["results"].([]any) assert.Equal(t, "duplicate_target", results[1].(map[string]any)["error"].(map[string]any)["code"]) } @@ -581,10 +581,10 @@ func Test_ProjectsWrite_UpdateProjectItems_DuplicateTargetRejected(t *testing.T) }, } gqlClient := newTestGQLClient(transport) - var restCalls int32 + var restCalls atomic.Int32 restClient := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetOrgsProjectsV2ItemsByProjectByItemID: func(w http.ResponseWriter, _ *http.Request) { - atomic.AddInt32(&restCalls, 1) + restCalls.Add(1) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"id":1001,"node_id":"PVTI_item1"}`)) }, @@ -616,7 +616,7 @@ func Test_ProjectsWrite_UpdateProjectItems_DuplicateTargetRejected(t *testing.T) second := results[1].(map[string]any) assert.Equal(t, "failed", second["status"]) assert.Equal(t, "duplicate_target", second["error"].(map[string]any)["code"]) - assert.Equal(t, int32(1), atomic.LoadInt32(&restCalls)) + assert.Equal(t, int32(1), restCalls.Load()) assert.Len(t, transport.mutationCalls, 1) } diff --git a/pkg/github/projects_resolver.go b/pkg/github/projects_resolver.go index 33e82d1de5..b4fba6c33e 100644 --- a/pkg/github/projects_resolver.go +++ b/pkg/github/projects_resolver.go @@ -606,7 +606,7 @@ func resolveFieldNamesToIDsFromFields(all []ResolvedField, names []string, owner for i, field := range resolved { id, parseErr := parseInt64(field.ID) if parseErr != nil { - return nil, fmt.Errorf("resolved field %q has non-numeric ID %q; pass it via '%s' instead", names[i], field.ID, idParameter) + return nil, fmt.Errorf("resolved field %q has non-numeric ID %q; pass it via '%s' instead", names[i], field.ID, idParameter) //nolint:gosec // G602: resolveFieldsByName returns exactly one field per name in order, or an error. } out = append(out, id) } diff --git a/pkg/github/projects_test.go b/pkg/github/projects_test.go index 8b40a569e0..6305569c3a 100644 --- a/pkg/github/projects_test.go +++ b/pkg/github/projects_test.go @@ -13,7 +13,7 @@ import ( transportpkg "github.com/github/github-mcp-server/pkg/http/transport" "github.com/github/github-mcp-server/pkg/inventory" "github.com/github/github-mcp-server/pkg/translations" - gogithub "github.com/google/go-github/v89/github" + gogithub "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" @@ -1488,7 +1488,7 @@ func Test_BuildIssueFieldUpdate(t *testing.T) { func Test_ProjectItemIssueID_RejectsNonIssueItems(t *testing.T) { for _, contentType := range []string{"PullRequest", "DraftIssue"} { t.Run(contentType, func(t *testing.T) { - item := &gogithub.ProjectV2Item{ContentType: gogithub.Ptr(gogithub.ProjectV2ItemContentType(contentType))} + item := &gogithub.ProjectV2Item{ContentType: new(gogithub.ProjectV2ItemContentType(contentType))} _, err := projectItemIssueID(item) var structured *ghErrors.StructuredResolutionError require.ErrorAs(t, err, &structured) diff --git a/pkg/github/pullrequests.go b/pkg/github/pullrequests.go index 5cee8b3231..98354ac70b 100644 --- a/pkg/github/pullrequests.go +++ b/pkg/github/pullrequests.go @@ -8,7 +8,7 @@ import ( "net/http" "github.com/go-viper/mapstructure/v2" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" @@ -777,17 +777,17 @@ func CreatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo } newPR := &github.CreatePullRequest{ - Title: github.Ptr(title), + Title: new(title), Head: head, Base: base, } if body != "" { - newPR.Body = github.Ptr(body) + newPR.Body = new(body) } - newPR.Draft = github.Ptr(draft) - newPR.MaintainerCanModify = github.Ptr(maintainerCanModify) + newPR.Draft = new(draft) + newPR.MaintainerCanModify = new(maintainerCanModify) client, err := deps.GetClient(ctx) if err != nil { @@ -969,35 +969,35 @@ func UpdatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo if title, ok, err := OptionalParamOK[string](args, "title"); err != nil { return utils.NewToolResultError(err.Error()), nil, nil } else if ok { - update.Title = github.Ptr(title) + update.Title = new(title) restUpdateNeeded = true } if body, ok, err := OptionalParamOK[string](args, "body"); err != nil { return utils.NewToolResultError(err.Error()), nil, nil } else if ok { - update.Body = github.Ptr(body) + update.Body = new(body) restUpdateNeeded = true } if state, ok, err := OptionalParamOK[string](args, "state"); err != nil { return utils.NewToolResultError(err.Error()), nil, nil } else if ok { - update.State = github.Ptr(state) + update.State = new(state) restUpdateNeeded = true } if base, ok, err := OptionalParamOK[string](args, "base"); err != nil { return utils.NewToolResultError(err.Error()), nil, nil } else if ok { - update.Base = &github.PullRequestBranch{Ref: github.Ptr(base)} + update.Base = &github.PullRequestBranch{Ref: new(base)} restUpdateNeeded = true } if maintainerCanModify, ok, err := OptionalParamOK[bool](args, "maintainer_can_modify"); err != nil { return utils.NewToolResultError(err.Error()), nil, nil } else if ok { - update.MaintainerCanModify = github.Ptr(maintainerCanModify) + update.MaintainerCanModify = new(maintainerCanModify) restUpdateNeeded = true } @@ -1732,7 +1732,7 @@ func UpdatePullRequestBranch(t translations.TranslationHelperFunc) inventory.Ser } opts := &github.PullRequestBranchUpdateOptions{} if expectedHeadSHA != "" { - opts.ExpectedHeadSHA = github.Ptr(expectedHeadSHA) + opts.ExpectedHeadSHA = new(expectedHeadSHA) } client, err := deps.GetClient(ctx) @@ -1989,57 +1989,9 @@ func CreatePullRequestReview(ctx context.Context, client *githubv4.Client, param } func SubmitPendingPullRequestReview(ctx context.Context, client *githubv4.Client, params PullRequestReviewWriteParams) (*mcp.CallToolResult, error) { - // First we'll get the current user - var getViewerQuery struct { - Viewer struct { - Login githubv4.String - } - } - - if err := client.Query(ctx, &getViewerQuery, nil); err != nil { - return ghErrors.NewGitHubGraphQLErrorResponse(ctx, - "failed to get current user", - err, - ), nil - } - - var getLatestReviewForViewerQuery struct { - Repository struct { - PullRequest struct { - Reviews struct { - Nodes []struct { - ID githubv4.ID - State githubv4.PullRequestReviewState - URL githubv4.URI - } - } `graphql:"reviews(first: 1, author: $author)"` - } `graphql:"pullRequest(number: $prNum)"` - } `graphql:"repository(owner: $owner, name: $name)"` - } - - vars := map[string]any{ - "author": githubv4.String(getViewerQuery.Viewer.Login), - "owner": githubv4.String(params.Owner), - "name": githubv4.String(params.Repo), - "prNum": githubv4.Int(params.PullNumber), - } - - if err := client.Query(ctx, &getLatestReviewForViewerQuery, vars); err != nil { - return ghErrors.NewGitHubGraphQLErrorResponse(ctx, - "failed to get latest review for current user", - err, - ), nil - } - - // Validate there is one review and the state is pending - if len(getLatestReviewForViewerQuery.Repository.PullRequest.Reviews.Nodes) == 0 { - return utils.NewToolResultError("No pending review found for the viewer"), nil - } - - review := getLatestReviewForViewerQuery.Repository.PullRequest.Reviews.Nodes[0] - if review.State != githubv4.PullRequestReviewStatePending { - errText := fmt.Sprintf("The latest review, found at %s is not pending", review.URL) - return utils.NewToolResultError(errText), nil + review, result := getPendingPullRequestReviewForViewer(ctx, client, params.Owner, params.Repo, params.PullNumber) + if result != nil { + return result, nil } // Prepare the mutation @@ -2055,7 +2007,7 @@ func SubmitPendingPullRequestReview(ctx context.Context, client *githubv4.Client ctx, &submitPullRequestReviewMutation, githubv4.SubmitPullRequestReviewInput{ - PullRequestReviewID: &review.ID, + PullRequestReviewID: review, Event: githubv4.PullRequestReviewEvent(params.Event), Body: newGQLStringlikePtr[githubv4.String](¶ms.Body), }, @@ -2074,57 +2026,9 @@ func SubmitPendingPullRequestReview(ctx context.Context, client *githubv4.Client } func DeletePendingPullRequestReview(ctx context.Context, client *githubv4.Client, params PullRequestReviewWriteParams) (*mcp.CallToolResult, error) { - // First we'll get the current user - var getViewerQuery struct { - Viewer struct { - Login githubv4.String - } - } - - if err := client.Query(ctx, &getViewerQuery, nil); err != nil { - return ghErrors.NewGitHubGraphQLErrorResponse(ctx, - "failed to get current user", - err, - ), nil - } - - var getLatestReviewForViewerQuery struct { - Repository struct { - PullRequest struct { - Reviews struct { - Nodes []struct { - ID githubv4.ID - State githubv4.PullRequestReviewState - URL githubv4.URI - } - } `graphql:"reviews(first: 1, author: $author)"` - } `graphql:"pullRequest(number: $prNum)"` - } `graphql:"repository(owner: $owner, name: $name)"` - } - - vars := map[string]any{ - "author": githubv4.String(getViewerQuery.Viewer.Login), - "owner": githubv4.String(params.Owner), - "name": githubv4.String(params.Repo), - "prNum": githubv4.Int(params.PullNumber), - } - - if err := client.Query(ctx, &getLatestReviewForViewerQuery, vars); err != nil { - return ghErrors.NewGitHubGraphQLErrorResponse(ctx, - "failed to get latest review for current user", - err, - ), nil - } - - // Validate there is one review and the state is pending - if len(getLatestReviewForViewerQuery.Repository.PullRequest.Reviews.Nodes) == 0 { - return utils.NewToolResultError("No pending review found for the viewer"), nil - } - - review := getLatestReviewForViewerQuery.Repository.PullRequest.Reviews.Nodes[0] - if review.State != githubv4.PullRequestReviewStatePending { - errText := fmt.Sprintf("The latest review, found at %s is not pending", review.URL) - return utils.NewToolResultError(errText), nil + review, result := getPendingPullRequestReviewForViewer(ctx, client, params.Owner, params.Repo, params.PullNumber) + if result != nil { + return result, nil } // Prepare the mutation @@ -2140,7 +2044,7 @@ func DeletePendingPullRequestReview(ctx context.Context, client *githubv4.Client ctx, &deletePullRequestReviewMutation, githubv4.DeletePullRequestReviewInput{ - PullRequestReviewID: &review.ID, + PullRequestReviewID: review, }, nil, ); err != nil { @@ -2235,57 +2139,9 @@ type AddCommentToPendingReviewParams struct { // AddCommentToPendingReviewCall adds a review comment to the viewer's pending pull request review. func AddCommentToPendingReviewCall(ctx context.Context, client *githubv4.Client, params AddCommentToPendingReviewParams) (*mcp.CallToolResult, error) { - // Get the current user - var getViewerQuery struct { - Viewer struct { - Login githubv4.String - } - } - - if err := client.Query(ctx, &getViewerQuery, nil); err != nil { - return ghErrors.NewGitHubGraphQLErrorResponse(ctx, - "failed to get current user", - err, - ), nil - } - - var getLatestReviewForViewerQuery struct { - Repository struct { - PullRequest struct { - Reviews struct { - Nodes []struct { - ID githubv4.ID - State githubv4.PullRequestReviewState - URL githubv4.URI - } - } `graphql:"reviews(first: 1, author: $author)"` - } `graphql:"pullRequest(number: $prNum)"` - } `graphql:"repository(owner: $owner, name: $name)"` - } - - vars := map[string]any{ - "author": githubv4.String(getViewerQuery.Viewer.Login), - "owner": githubv4.String(params.Owner), - "name": githubv4.String(params.Repo), - "prNum": githubv4.Int(params.PullNumber), - } - - if err := client.Query(ctx, &getLatestReviewForViewerQuery, vars); err != nil { - return ghErrors.NewGitHubGraphQLErrorResponse(ctx, - "failed to get latest review for current user", - err, - ), nil - } - - // Validate there is one review and the state is pending - if len(getLatestReviewForViewerQuery.Repository.PullRequest.Reviews.Nodes) == 0 { - return utils.NewToolResultError("No pending review found for the viewer"), nil - } - - review := getLatestReviewForViewerQuery.Repository.PullRequest.Reviews.Nodes[0] - if review.State != githubv4.PullRequestReviewStatePending { - errText := fmt.Sprintf("The latest review, found at %s is not pending", review.URL) - return utils.NewToolResultError(errText), nil + review, result := getPendingPullRequestReviewForViewer(ctx, client, params.Owner, params.Repo, params.PullNumber) + if result != nil { + return result, nil } // Create a new review thread comment on the review. @@ -2301,14 +2157,14 @@ func AddCommentToPendingReviewCall(ctx context.Context, client *githubv4.Client, ctx, &addPullRequestReviewThreadMutation, githubv4.AddPullRequestReviewThreadInput{ - Path: githubv4.String(params.Path), + Path: newGQLStringlikePtr[githubv4.String](¶ms.Path), Body: githubv4.String(params.Body), SubjectType: newGQLStringlikePtr[githubv4.PullRequestReviewThreadSubjectType](¶ms.SubjectType), Line: newGQLIntPtr(params.Line), Side: newGQLStringlikePtr[githubv4.DiffSide](params.Side), StartLine: newGQLIntPtr(params.StartLine), StartSide: newGQLStringlikePtr[githubv4.DiffSide](params.StartSide), - PullRequestReviewID: &review.ID, + PullRequestReviewID: review, }, nil, ); err != nil { @@ -2326,6 +2182,100 @@ func AddCommentToPendingReviewCall(ctx context.Context, client *githubv4.Client, return utils.NewToolResultText("pull request review comment successfully added to pending review"), nil } +type pendingReviewAuthor struct { + Bot struct { + ID githubv4.ID `graphql:"botId: id"` + } `graphql:"... on Bot"` + EnterpriseUserAccount struct { + ID githubv4.ID `graphql:"enterpriseUserAccountId: id"` + } `graphql:"... on EnterpriseUserAccount"` + Mannequin struct { + ID githubv4.ID `graphql:"mannequinId: id"` + } `graphql:"... on Mannequin"` + Organization struct { + ID githubv4.ID `graphql:"organizationId: id"` + } `graphql:"... on Organization"` + User struct { + ID githubv4.ID `graphql:"userId: id"` + } `graphql:"... on User"` +} + +func (a pendingReviewAuthor) id() githubv4.ID { + for _, id := range []githubv4.ID{ + a.Bot.ID, + a.EnterpriseUserAccount.ID, + a.Mannequin.ID, + a.Organization.ID, + a.User.ID, + } { + if id != nil { + return id + } + } + return nil +} + +func getPendingPullRequestReviewForViewer(ctx context.Context, client *githubv4.Client, owner, repo string, pullNumber int32) (*githubv4.ID, *mcp.CallToolResult) { + var getViewerQuery struct { + Viewer struct { + ID githubv4.ID + } + } + + if err := client.Query(ctx, &getViewerQuery, nil); err != nil { + return nil, ghErrors.NewGitHubGraphQLErrorResponse(ctx, + "failed to get current user", + err, + ) + } + + vars := map[string]any{ + "after": (*githubv4.String)(nil), + "owner": githubv4.String(owner), + "name": githubv4.String(repo), + "prNum": githubv4.Int(pullNumber), + "states": []githubv4.PullRequestReviewState{githubv4.PullRequestReviewStatePending}, + } + + for { + var getPendingReviewsQuery struct { + Repository struct { + PullRequest struct { + Reviews struct { + Nodes []struct { + ID githubv4.ID + Author pendingReviewAuthor + } + PageInfo struct { + HasNextPage githubv4.Boolean + EndCursor githubv4.String + } + } `graphql:"reviews(first: 100, after: $after, states: $states)"` + } `graphql:"pullRequest(number: $prNum)"` + } `graphql:"repository(owner: $owner, name: $name)"` + } + + if err := client.Query(ctx, &getPendingReviewsQuery, vars); err != nil { + return nil, ghErrors.NewGitHubGraphQLErrorResponse(ctx, + "failed to get pending pull request reviews", + err, + ) + } + + for _, review := range getPendingReviewsQuery.Repository.PullRequest.Reviews.Nodes { + if review.Author.id() == getViewerQuery.Viewer.ID { + reviewID := review.ID + return &reviewID, nil + } + } + + if !getPendingReviewsQuery.Repository.PullRequest.Reviews.PageInfo.HasNextPage { + return nil, utils.NewToolResultError("No pending review found for the viewer") + } + vars["after"] = githubv4.NewString(getPendingReviewsQuery.Repository.PullRequest.Reviews.PageInfo.EndCursor) + } +} + // AddCommentToPendingReview creates a tool to add a comment to a pull request review. func AddCommentToPendingReview(t translations.TranslationHelperFunc) inventory.ServerTool { schema := &jsonschema.Schema{ diff --git a/pkg/github/pullrequests_granular.go b/pkg/github/pullrequests_granular.go index e670da9a34..cf7fa93c1d 100644 --- a/pkg/github/pullrequests_granular.go +++ b/pkg/github/pullrequests_granular.go @@ -12,7 +12,7 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - gogithub "github.com/google/go-github/v89/github" + gogithub "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" @@ -973,3 +973,23 @@ func GranularRemovePullRequestReviewCommentReaction(t translations.TranslationHe st.FeatureRule = pullRequestsGranularFeatureRule return st } + +// GranularHidePullRequestReviewComment hides (minimizes) an inline pull request review comment. +func GranularHidePullRequestReviewComment(t translations.TranslationHelperFunc) inventory.ServerTool { + return commentVisibilityTool(t, pullRequestReviewCommentVisibilityTarget, true) +} + +// GranularUnhidePullRequestReviewComment unhides (unminimizes) an inline pull request review comment. +func GranularUnhidePullRequestReviewComment(t translations.TranslationHelperFunc) inventory.ServerTool { + return commentVisibilityTool(t, pullRequestReviewCommentVisibilityTarget, false) +} + +// GranularHidePullRequestReview hides (minimizes) the body of a pull request review. +func GranularHidePullRequestReview(t translations.TranslationHelperFunc) inventory.ServerTool { + return commentVisibilityTool(t, pullRequestReviewVisibilityTarget, true) +} + +// GranularUnhidePullRequestReview unhides (unminimizes) the body of a pull request review. +func GranularUnhidePullRequestReview(t translations.TranslationHelperFunc) inventory.ServerTool { + return commentVisibilityTool(t, pullRequestReviewVisibilityTarget, false) +} diff --git a/pkg/github/pullrequests_sha_pinning_test.go b/pkg/github/pullrequests_sha_pinning_test.go index 77e2d5152a..02f8b758a3 100644 --- a/pkg/github/pullrequests_sha_pinning_test.go +++ b/pkg/github/pullrequests_sha_pinning_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -17,9 +17,9 @@ func Test_MergePullRequestSHAPinning(t *testing.T) { const pinnedSHA = "0123456789abcdef0123456789abcdef01234567" mockMergeResult := &github.PullRequestMergeResult{ - SHA: github.Ptr("merge-result-sha"), - Merged: github.Ptr(true), - Message: github.Ptr("Pull Request successfully merged"), + SHA: new("merge-result-sha"), + Merged: new(true), + Message: new("Pull Request successfully merged"), } t.Run("expected head SHA is sent as REST sha", func(t *testing.T) { diff --git a/pkg/github/pullrequests_test.go b/pkg/github/pullrequests_test.go index c0e392aea6..a6bb796839 100644 --- a/pkg/github/pullrequests_test.go +++ b/pkg/github/pullrequests_test.go @@ -12,7 +12,7 @@ import ( "github.com/github/github-mcp-server/internal/githubv4mock" "github.com/github/github-mcp-server/internal/toolsnaps" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" @@ -36,20 +36,20 @@ func Test_GetPullRequest(t *testing.T) { // Setup mock PR for success case mockPR := &github.PullRequest{ - Number: github.Ptr(42), - Title: github.Ptr("Test PR"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"), + Number: new(42), + Title: new("Test PR"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/pull/42"), Head: &github.PullRequestBranch{ - SHA: github.Ptr("abcd1234"), - Ref: github.Ptr("feature-branch"), + SHA: new("abcd1234"), + Ref: new("feature-branch"), }, Base: &github.PullRequestBranch{ - Ref: github.Ptr("main"), + Ref: new("main"), }, - Body: github.Ptr("This is a test PR"), + Body: new("This is a test PR"), User: &github.User{ - Login: github.Ptr("testuser"), + Login: new("testuser"), }, } @@ -203,32 +203,32 @@ func Test_UpdatePullRequest(t *testing.T) { // Setup mock PR for success case mockUpdatedPR := &github.PullRequest{ - Number: github.Ptr(42), - Title: github.Ptr("Updated Test PR Title"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"), - Body: github.Ptr("Updated test PR body."), - MaintainerCanModify: github.Ptr(false), - Draft: github.Ptr(false), + Number: new(42), + Title: new("Updated Test PR Title"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/pull/42"), + Body: new("Updated test PR body."), + MaintainerCanModify: new(false), + Draft: new(false), Base: &github.PullRequestBranch{ - Ref: github.Ptr("develop"), + Ref: new("develop"), }, } mockClosedPR := &github.PullRequest{ - Number: github.Ptr(42), - Title: github.Ptr("Test PR"), - State: github.Ptr("closed"), // State updated + Number: new(42), + Title: new("Test PR"), + State: new("closed"), // State updated } // Mock PR for when there are no updates but we still need a response mockPRWithReviewers := &github.PullRequest{ - Number: github.Ptr(42), - Title: github.Ptr("Test PR"), - State: github.Ptr("open"), + Number: new(42), + Title: new("Test PR"), + State: new("open"), RequestedReviewers: []*github.User{ - {Login: github.Ptr("reviewer1")}, - {Login: github.Ptr("reviewer2")}, + {Login: new("reviewer1")}, + {Login: new("reviewer2")}, }, } @@ -430,15 +430,15 @@ func Test_UpdatePullRequest(t *testing.T) { func Test_UpdatePullRequest_Draft(t *testing.T) { // Setup mock PR for success case mockUpdatedPR := &github.PullRequest{ - Number: github.Ptr(42), - Title: github.Ptr("Test PR Title"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"), - Body: github.Ptr("Test PR body."), - MaintainerCanModify: github.Ptr(false), - Draft: github.Ptr(false), // Updated to ready for review + Number: new(42), + Title: new("Test PR Title"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/pull/42"), + Body: new("Test PR body."), + MaintainerCanModify: new(false), + Draft: new(false), // Updated to ready for review Base: &github.PullRequestBranch{ - Ref: github.Ptr("main"), + Ref: new("main"), }, } @@ -635,16 +635,16 @@ func Test_ListPullRequests(t *testing.T) { // Setup mock PRs for success case mockPRs := []*github.PullRequest{ { - Number: github.Ptr(42), - Title: github.Ptr("First PR"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"), + Number: new(42), + Title: new("First PR"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/pull/42"), }, { - Number: github.Ptr(43), - Title: github.Ptr("Second PR"), - State: github.Ptr("closed"), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/43"), + Number: new(43), + Title: new("Second PR"), + State: new("closed"), + HTMLURL: new("https://github.com/owner/repo/pull/43"), }, } @@ -765,9 +765,9 @@ func Test_MergePullRequest(t *testing.T) { // Setup mock merge result for success case mockMergeResult := &github.PullRequestMergeResult{ - Merged: github.Ptr(true), - Message: github.Ptr("Pull Request successfully merged"), - SHA: github.Ptr("abcd1234efgh5678"), + Merged: new(true), + Message: new("Pull Request successfully merged"), + SHA: new("abcd1234efgh5678"), } tests := []struct { @@ -879,29 +879,29 @@ func Test_SearchPullRequests(t *testing.T) { assert.ElementsMatch(t, schema.Required, []string{"query"}) mockSearchResult := &github.IssuesSearchResult{ - Total: github.Ptr(2), - IncompleteResults: github.Ptr(false), + Total: new(2), + IncompleteResults: new(false), Issues: []*github.Issue{ { - Number: github.Ptr(42), - Title: github.Ptr("Test PR 1"), - Body: github.Ptr("Updated tests."), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/1"), - Comments: github.Ptr(5), + Number: new(42), + Title: new("Test PR 1"), + Body: new("Updated tests."), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/pull/1"), + Comments: new(5), User: &github.User{ - Login: github.Ptr("user1"), + Login: new("user1"), }, }, { - Number: github.Ptr(43), - Title: github.Ptr("Test PR 2"), - Body: github.Ptr("Updated build scripts."), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/2"), - Comments: github.Ptr(3), + Number: new(43), + Title: new("Test PR 2"), + Body: new("Updated build scripts."), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/pull/2"), + Comments: new(3), User: &github.User{ - Login: github.Ptr("user2"), + Login: new("user2"), }, }, }, @@ -1168,20 +1168,20 @@ func Test_GetPullRequestFiles(t *testing.T) { // Setup mock PR files for success case mockFiles := []*github.CommitFile{ { - Filename: github.Ptr("file1.go"), - Status: github.Ptr("modified"), - Additions: github.Ptr(10), - Deletions: github.Ptr(5), - Changes: github.Ptr(15), - Patch: github.Ptr("@@ -1,5 +1,10 @@"), + Filename: new("file1.go"), + Status: new("modified"), + Additions: new(10), + Deletions: new(5), + Changes: new(15), + Patch: new("@@ -1,5 +1,10 @@"), }, { - Filename: github.Ptr("file2.go"), - Status: github.Ptr("added"), - Additions: github.Ptr(20), - Deletions: github.Ptr(0), - Changes: github.Ptr(20), - Patch: github.Ptr("@@ -0,0 +1,20 @@"), + Filename: new("file2.go"), + Status: new("added"), + Additions: new(20), + Deletions: new(0), + Changes: new(20), + Patch: new("@@ -0,0 +1,20 @@"), }, } @@ -1261,8 +1261,8 @@ func Test_GetPullRequestFiles(t *testing.T) { name: "lockdown enabled - author lacks push access", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, &github.PullRequest{ - Number: github.Ptr(42), - User: &github.User{Login: github.Ptr("reader")}, + Number: new(42), + User: &github.User{Login: new("reader")}, }), }), requestArgs: map[string]any{ @@ -1280,8 +1280,8 @@ func Test_GetPullRequestFiles(t *testing.T) { name: "lockdown enabled - author has push access", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, &github.PullRequest{ - Number: github.Ptr(42), - User: &github.User{Login: github.Ptr("writer")}, + Number: new(42), + User: &github.User{Login: new("writer")}, }), GetReposPullsFilesByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockFiles), }), @@ -1391,39 +1391,39 @@ func Test_GetPullRequestCommits(t *testing.T) { authorDate := time.Date(2026, 4, 1, 12, 0, 0, 0, time.UTC) mockCommits := []*github.RepositoryCommit{ { - SHA: github.Ptr("abc123def456"), - HTMLURL: github.Ptr("https://github.com/owner/repo/commit/abc123def456"), + SHA: new("abc123def456"), + HTMLURL: new("https://github.com/owner/repo/commit/abc123def456"), Commit: &github.Commit{ - Message: github.Ptr("feat: add commit listing"), + Message: new("feat: add commit listing"), Author: &github.CommitAuthor{ - Name: github.Ptr("Test User"), - Email: github.Ptr("test@example.com"), + Name: new("Test User"), + Email: new("test@example.com"), Date: &github.Timestamp{Time: authorDate}, }, Committer: &github.CommitAuthor{ - Name: github.Ptr("Merge Bot"), - Email: github.Ptr("merge@example.com"), + Name: new("Merge Bot"), + Email: new("merge@example.com"), Date: &github.Timestamp{Time: authorDate.Add(30 * time.Minute)}, }, }, Author: &github.User{ - Login: github.Ptr("test-user"), - ID: github.Ptr(int64(12345)), - HTMLURL: github.Ptr("https://github.com/test-user"), - AvatarURL: github.Ptr("https://github.com/test-user.png"), + Login: new("test-user"), + ID: new(int64(12345)), + HTMLURL: new("https://github.com/test-user"), + AvatarURL: new("https://github.com/test-user.png"), }, Committer: &github.User{ - Login: github.Ptr("merge-bot"), - ID: github.Ptr(int64(67890)), - HTMLURL: github.Ptr("https://github.com/merge-bot"), - AvatarURL: github.Ptr("https://github.com/merge-bot.png"), + Login: new("merge-bot"), + ID: new(int64(67890)), + HTMLURL: new("https://github.com/merge-bot"), + AvatarURL: new("https://github.com/merge-bot.png"), }, }, { - SHA: github.Ptr("def456abc789"), - HTMLURL: github.Ptr("https://github.com/owner/repo/commit/def456abc789"), + SHA: new("def456abc789"), + HTMLURL: new("https://github.com/owner/repo/commit/def456abc789"), Commit: &github.Commit{ - Message: github.Ptr("fix: handle pagination"), + Message: new("fix: handle pagination"), }, }, } @@ -1504,8 +1504,8 @@ func Test_GetPullRequestCommits(t *testing.T) { name: "lockdown enabled - author lacks push access", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, &github.PullRequest{ - Number: github.Ptr(42), - User: &github.User{Login: github.Ptr("reader")}, + Number: new(42), + User: &github.User{Login: new("reader")}, }), }), requestArgs: map[string]any{ @@ -1523,8 +1523,8 @@ func Test_GetPullRequestCommits(t *testing.T) { name: "lockdown enabled - author has push access", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, &github.PullRequest{ - Number: github.Ptr(42), - User: &github.User{Login: github.Ptr("writer")}, + Number: new(42), + User: &github.User{Login: new("writer")}, }), GetReposPullsCommitsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockCommits), }), @@ -1543,8 +1543,8 @@ func Test_GetPullRequestCommits(t *testing.T) { name: "lockdown enabled - trusted bot author lacks push access", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, &github.PullRequest{ - Number: github.Ptr(42), - User: &github.User{Login: github.Ptr("github-actions[bot]")}, + Number: new(42), + User: &github.User{Login: new("github-actions[bot]")}, }), GetReposPullsCommitsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockCommits), }), @@ -1653,54 +1653,54 @@ func Test_GetPullRequestStatus(t *testing.T) { // Setup mock PR for successful PR fetch mockPR := &github.PullRequest{ - Number: github.Ptr(42), - Title: github.Ptr("Test PR"), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"), + Number: new(42), + Title: new("Test PR"), + HTMLURL: new("https://github.com/owner/repo/pull/42"), Head: &github.PullRequestBranch{ - SHA: github.Ptr("abcd1234"), - Ref: github.Ptr("feature-branch"), + SHA: new("abcd1234"), + Ref: new("feature-branch"), }, } statusCreatedAt := &github.Timestamp{Time: time.Date(2026, time.August, 11, 9, 30, 0, 0, time.UTC)} statusUpdatedAt := &github.Timestamp{Time: time.Date(2026, time.August, 11, 9, 35, 0, 0, time.UTC)} mockStatus := &github.CombinedStatus{ - Name: github.Ptr("abcd1234"), - State: github.Ptr("success"), - SHA: github.Ptr("abcd1234"), - TotalCount: github.Ptr(2), - CommitURL: github.Ptr("https://api.github.com/repos/owner/repo/commits/abcd1234"), - RepositoryURL: github.Ptr( + Name: new("abcd1234"), + State: new("success"), + SHA: new("abcd1234"), + TotalCount: new(2), + CommitURL: new("https://api.github.com/repos/owner/repo/commits/abcd1234"), + RepositoryURL: new( "https://api.github.com/repos/owner/repo", ), Statuses: []*github.RepoStatus{ { - ID: github.Ptr(int64(101)), - NodeID: github.Ptr("SC_kwDOStatus101"), - URL: github.Ptr("https://api.github.com/repos/owner/repo/statuses/abcd1234"), - State: github.Ptr("success"), - Context: github.Ptr("continuous-integration/travis-ci"), - Description: github.Ptr("Build succeeded"), - TargetURL: github.Ptr("https://travis-ci.org/owner/repo/builds/123"), - AvatarURL: github.Ptr("https://avatars.githubusercontent.com/in/123"), + ID: new(int64(101)), + NodeID: new("SC_kwDOStatus101"), + URL: new("https://api.github.com/repos/owner/repo/statuses/abcd1234"), + State: new("success"), + Context: new("continuous-integration/travis-ci"), + Description: new("Build succeeded"), + TargetURL: new("https://travis-ci.org/owner/repo/builds/123"), + AvatarURL: new("https://avatars.githubusercontent.com/in/123"), Creator: &github.User{ - Login: github.Ptr("ci-bot"), + Login: new("ci-bot"), }, CreatedAt: statusCreatedAt, UpdatedAt: statusUpdatedAt, }, { - State: github.Ptr("success"), - Context: github.Ptr("codecov/patch"), - Description: github.Ptr("Coverage increased"), - TargetURL: github.Ptr("https://codecov.io/gh/owner/repo/pull/42"), + State: new("success"), + Context: new("codecov/patch"), + Description: new("Coverage increased"), + TargetURL: new("https://codecov.io/gh/owner/repo/pull/42"), }, }, } emptyStatus := &github.CombinedStatus{ - State: github.Ptr("pending"), - SHA: github.Ptr("abcd1234"), - TotalCount: github.Ptr(0), + State: new("pending"), + SHA: new("abcd1234"), + TotalCount: new(0), Statuses: []*github.RepoStatus{nil}, } @@ -1881,32 +1881,32 @@ func Test_GetPullRequestCheckRuns(t *testing.T) { // Setup mock PR for successful PR fetch mockPR := &github.PullRequest{ - Number: github.Ptr(42), - Title: github.Ptr("Test PR"), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"), + Number: new(42), + Title: new("Test PR"), + HTMLURL: new("https://github.com/owner/repo/pull/42"), Head: &github.PullRequestBranch{ - SHA: github.Ptr("abcd1234"), - Ref: github.Ptr("feature-branch"), + SHA: new("abcd1234"), + Ref: new("feature-branch"), }, } // Setup mock check runs for success case mockCheckRuns := &github.ListCheckRunsResults{ - Total: github.Ptr(2), + Total: new(2), CheckRuns: []*github.CheckRun{ { - ID: github.Ptr(int64(1)), - Name: github.Ptr("build"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("success"), - HTMLURL: github.Ptr("https://github.com/owner/repo/runs/1"), + ID: new(int64(1)), + Name: new("build"), + Status: new("completed"), + Conclusion: new("success"), + HTMLURL: new("https://github.com/owner/repo/runs/1"), }, { - ID: github.Ptr(int64(2)), - Name: github.Ptr("test"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("success"), - HTMLURL: github.Ptr("https://github.com/owner/repo/runs/2"), + ID: new(int64(2)), + Name: new("test"), + Status: new("completed"), + Conclusion: new("success"), + HTMLURL: new("https://github.com/owner/repo/runs/2"), }, }, } @@ -2036,8 +2036,8 @@ func Test_UpdatePullRequestBranch(t *testing.T) { // Setup mock update result for success case mockUpdateResult := &github.PullRequestBranchUpdateResponse{ - Message: github.Ptr("Branch was updated successfully"), - URL: github.Ptr("https://api.github.com/repos/owner/repo/pulls/42"), + Message: new("Branch was updated successfully"), + URL: new("https://api.github.com/repos/owner/repo/pulls/42"), } tests := []struct { @@ -2579,25 +2579,25 @@ func Test_GetPullRequestReviews(t *testing.T) { // Setup mock PR reviews for success case mockReviews := []*github.PullRequestReview{ { - ID: github.Ptr(int64(201)), - State: github.Ptr("APPROVED"), - Body: github.Ptr("LGTM"), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42#pullrequestreview-201"), + ID: new(int64(201)), + State: new("APPROVED"), + Body: new("LGTM"), + HTMLURL: new("https://github.com/owner/repo/pull/42#pullrequestreview-201"), User: &github.User{ - Login: github.Ptr("approver"), + Login: new("approver"), }, - CommitID: github.Ptr("abcdef123456"), + CommitID: new("abcdef123456"), SubmittedAt: &github.Timestamp{Time: time.Now().Add(-24 * time.Hour)}, }, { - ID: github.Ptr(int64(202)), - State: github.Ptr("CHANGES_REQUESTED"), - Body: github.Ptr("Please address the following issues"), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42#pullrequestreview-202"), + ID: new(int64(202)), + State: new("CHANGES_REQUESTED"), + Body: new("Please address the following issues"), + HTMLURL: new("https://github.com/owner/repo/pull/42#pullrequestreview-202"), User: &github.User{ - Login: github.Ptr("reviewer"), + Login: new("reviewer"), }, - CommitID: github.Ptr("abcdef123456"), + CommitID: new("abcdef123456"), SubmittedAt: &github.Timestamp{Time: time.Now().Add(-12 * time.Hour)}, }, } @@ -2674,16 +2674,16 @@ func Test_GetPullRequestReviews(t *testing.T) { mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposPullsReviewsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, []*github.PullRequestReview{ { - ID: github.Ptr(int64(2030)), - State: github.Ptr("APPROVED"), - Body: github.Ptr("Maintainer review"), - User: &github.User{Login: github.Ptr("maintainer")}, + ID: new(int64(2030)), + State: new("APPROVED"), + Body: new("Maintainer review"), + User: &github.User{Login: new("maintainer")}, }, { - ID: github.Ptr(int64(2031)), - State: github.Ptr("COMMENTED"), - Body: github.Ptr("External reviewer"), - User: &github.User{Login: github.Ptr("testuser")}, + ID: new(int64(2031)), + State: new("COMMENTED"), + Body: new("External reviewer"), + User: &github.User{Login: new("testuser")}, }, }), }), @@ -2696,10 +2696,10 @@ func Test_GetPullRequestReviews(t *testing.T) { expectError: false, expectedReviews: []*github.PullRequestReview{ { - ID: github.Ptr(int64(2030)), - State: github.Ptr("APPROVED"), - Body: github.Ptr("Maintainer review"), - User: &github.User{Login: github.Ptr("maintainer")}, + ID: new(int64(2030)), + State: new("APPROVED"), + Body: new("Maintainer review"), + User: &github.User{Login: new("maintainer")}, }, }, lockdownEnabled: true, @@ -2709,15 +2709,15 @@ func Test_GetPullRequestReviews(t *testing.T) { mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposPullsReviewsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, []*github.PullRequestReview{ { - ID: github.Ptr(int64(2040)), - State: github.Ptr("APPROVED"), - Body: github.Ptr("Ghost review"), - User: &github.User{Login: github.Ptr("")}, + ID: new(int64(2040)), + State: new("APPROVED"), + Body: new("Ghost review"), + User: &github.User{Login: new("")}, }, { - ID: github.Ptr(int64(2041)), - State: github.Ptr("COMMENTED"), - Body: github.Ptr("Another ghost review"), + ID: new(int64(2041)), + State: new("COMMENTED"), + Body: new("Another ghost review"), }, }), }), @@ -2814,23 +2814,23 @@ func Test_CreatePullRequest(t *testing.T) { // Setup mock PR for success case mockPR := &github.PullRequest{ - Number: github.Ptr(42), - Title: github.Ptr("Test PR"), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"), + Number: new(42), + Title: new("Test PR"), + State: new("open"), + HTMLURL: new("https://github.com/owner/repo/pull/42"), Head: &github.PullRequestBranch{ - SHA: github.Ptr("abcd1234"), - Ref: github.Ptr("feature-branch"), + SHA: new("abcd1234"), + Ref: new("feature-branch"), }, Base: &github.PullRequestBranch{ - SHA: github.Ptr("efgh5678"), - Ref: github.Ptr("main"), + SHA: new("efgh5678"), + Ref: new("main"), }, - Body: github.Ptr("This is a test PR"), - Draft: github.Ptr(false), - MaintainerCanModify: github.Ptr(true), + Body: new("This is a test PR"), + Draft: new(false), + MaintainerCanModify: new(true), User: &github.User{ - Login: github.Ptr("testuser"), + Login: new("testuser"), }, } @@ -2949,12 +2949,12 @@ func Test_CreatePullRequest_MCPAppsFeature_UIGate(t *testing.T) { t.Parallel() mockPR := &github.PullRequest{ - Number: github.Ptr(42), - Title: github.Ptr("Test PR"), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"), - Head: &github.PullRequestBranch{SHA: github.Ptr("abc"), Ref: github.Ptr("feature")}, - Base: &github.PullRequestBranch{SHA: github.Ptr("def"), Ref: github.Ptr("main")}, - User: &github.User{Login: github.Ptr("testuser")}, + Number: new(42), + Title: new("Test PR"), + HTMLURL: new("https://github.com/owner/repo/pull/42"), + Head: &github.PullRequestBranch{SHA: new("abc"), Ref: new("feature")}, + Base: &github.PullRequestBranch{SHA: new("def"), Ref: new("main")}, + User: &github.User{Login: new("testuser")}, } serverTool := CreatePullRequest(translations.NullTranslationHelper) @@ -2966,7 +2966,7 @@ func Test_CreatePullRequest_MCPAppsFeature_UIGate(t *testing.T) { deps := BaseDeps{ Client: client, GQLClient: githubv4.NewClient(nil), - featureChecker: featureCheckerFor(MCPAppsFeatureFlag), + featureChecker: featureCheckerFor(), } handler := serverTool.Handler(deps) @@ -3050,12 +3050,12 @@ func Test_UpdatePullRequest_MCPAppsFeature_UIGate(t *testing.T) { t.Parallel() mockPR := &github.PullRequest{ - Number: github.Ptr(42), - Title: github.Ptr("Updated"), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42"), - Head: &github.PullRequestBranch{SHA: github.Ptr("abc"), Ref: github.Ptr("feature")}, - Base: &github.PullRequestBranch{SHA: github.Ptr("def"), Ref: github.Ptr("main")}, - User: &github.User{Login: github.Ptr("testuser")}, + Number: new(42), + Title: new("Updated"), + HTMLURL: new("https://github.com/owner/repo/pull/42"), + Head: &github.PullRequestBranch{SHA: new("abc"), Ref: new("feature")}, + Base: &github.PullRequestBranch{SHA: new("def"), Ref: new("main")}, + User: &github.User{Login: new("testuser")}, } serverTool := UpdatePullRequest(translations.NullTranslationHelper) @@ -3068,7 +3068,7 @@ func Test_UpdatePullRequest_MCPAppsFeature_UIGate(t *testing.T) { deps := BaseDeps{ Client: client, GQLClient: githubv4.NewClient(nil), - featureChecker: featureCheckerFor(MCPAppsFeatureFlag), + featureChecker: featureCheckerFor(), } handler := serverTool.Handler(deps) @@ -3667,7 +3667,7 @@ func TestAddPullRequestReviewCommentToPendingReview(t *testing.T) { expectedToolErrMsg string }{ { - name: "successful line comment addition", + name: "selects viewer pending review by ID", requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -3681,18 +3681,21 @@ func TestAddPullRequestReviewCommentToPendingReview(t *testing.T) { "startSide": "RIGHT", }, mockedClient: githubv4mock.NewMockedHTTPClient( - viewerQuery("williammartin"), - getLatestPendingReviewQuery(getLatestPendingReviewQueryParams{ - author: "williammartin", - owner: "owner", - repo: "repo", - prNum: 42, - - reviews: []getLatestPendingReviewQueryReview{ + viewerIDQuery("U_viewer"), + getPendingReviewsQuery(getPendingReviewsQueryParams{ + owner: "owner", + repo: "repo", + prNum: 42, + + reviews: []pendingReviewQueryReview{ + { + id: "PR_other", + authorID: "U_other", + }, { - id: "PR_kwDODKw3uc6WYN1T", - state: "PENDING", - url: "https://github.com/owner/repo/pull/42", + id: "PR_kwDODKw3uc6WYN1T", + authorID: "U_viewer", + authorField: "botId", }, }, }), @@ -3705,7 +3708,7 @@ func TestAddPullRequestReviewCommentToPendingReview(t *testing.T) { } `graphql:"addPullRequestReviewThread(input: $input)"` }{}, githubv4.AddPullRequestReviewThreadInput{ - Path: githubv4.String("file.go"), + Path: githubv4.NewString("file.go"), Body: githubv4.String("This is a test comment"), SubjectType: githubv4mock.Ptr(githubv4.PullRequestReviewThreadSubjectTypeLine), Line: githubv4.NewInt(10), @@ -3740,18 +3743,16 @@ func TestAddPullRequestReviewCommentToPendingReview(t *testing.T) { "startSide": "RIGHT", }, mockedClient: githubv4mock.NewMockedHTTPClient( - viewerQuery("williammartin"), - getLatestPendingReviewQuery(getLatestPendingReviewQueryParams{ - author: "williammartin", - owner: "owner", - repo: "repo", - prNum: 42, - - reviews: []getLatestPendingReviewQueryReview{ + viewerIDQuery("U_viewer"), + getPendingReviewsQuery(getPendingReviewsQueryParams{ + owner: "owner", + repo: "repo", + prNum: 42, + + reviews: []pendingReviewQueryReview{ { - id: "PR_kwDODKw3uc6WYN1T", - state: "PENDING", - url: "https://github.com/owner/repo/pull/42", + id: "PR_kwDODKw3uc6WYN1T", + authorID: "U_viewer", }, }, }), @@ -3764,7 +3765,7 @@ func TestAddPullRequestReviewCommentToPendingReview(t *testing.T) { } `graphql:"addPullRequestReviewThread(input: $input)"` }{}, githubv4.AddPullRequestReviewThreadInput{ - Path: githubv4.String("file.go"), + Path: githubv4.NewString("file.go"), Body: githubv4.String("This is a test comment"), SubjectType: githubv4mock.Ptr(githubv4.PullRequestReviewThreadSubjectTypeLine), Line: githubv4.NewInt(10), @@ -3821,18 +3822,16 @@ func TestAddPullRequestReviewCommentToPendingReview(t *testing.T) { "side": "RIGHT", }, mockedClient: githubv4mock.NewMockedHTTPClient( - viewerQuery("williammartin"), - getLatestPendingReviewQuery(getLatestPendingReviewQueryParams{ - author: "williammartin", - owner: "owner", - repo: "repo", - prNum: 42, - - reviews: []getLatestPendingReviewQueryReview{ + viewerIDQuery("U_viewer"), + getPendingReviewsQuery(getPendingReviewsQueryParams{ + owner: "owner", + repo: "repo", + prNum: 42, + + reviews: []pendingReviewQueryReview{ { - id: "PR_kwDODKw3uc6WYN1T", - state: "PENDING", - url: "https://github.com/owner/repo/pull/42", + id: "PR_kwDODKw3uc6WYN1T", + authorID: "U_viewer", }, }, }), @@ -3845,7 +3844,7 @@ func TestAddPullRequestReviewCommentToPendingReview(t *testing.T) { } `graphql:"addPullRequestReviewThread(input: $input)"` }{}, githubv4.AddPullRequestReviewThreadInput{ - Path: githubv4.String("file.go"), + Path: githubv4.NewString("file.go"), Body: githubv4.String("Comment on non-existent line"), SubjectType: githubv4mock.Ptr(githubv4.PullRequestReviewThreadSubjectTypeLine), Line: githubv4.NewInt(999), @@ -3939,18 +3938,16 @@ func TestSubmitPendingPullRequestReview(t *testing.T) { "body": "This is a test review", }, mockedClient: githubv4mock.NewMockedHTTPClient( - viewerQuery("williammartin"), - getLatestPendingReviewQuery(getLatestPendingReviewQueryParams{ - author: "williammartin", - owner: "owner", - repo: "repo", - prNum: 42, - - reviews: []getLatestPendingReviewQueryReview{ + viewerIDQuery("U_viewer"), + getPendingReviewsQuery(getPendingReviewsQueryParams{ + owner: "owner", + repo: "repo", + prNum: 42, + + reviews: []pendingReviewQueryReview{ { - id: "PR_kwDODKw3uc6WYN1T", - state: "PENDING", - url: "https://github.com/owner/repo/pull/42", + id: "PR_kwDODKw3uc6WYN1T", + authorID: "U_viewer", }, }, }), @@ -4040,18 +4037,16 @@ func TestDeletePendingPullRequestReview(t *testing.T) { "pullNumber": float64(42), }, mockedClient: githubv4mock.NewMockedHTTPClient( - viewerQuery("williammartin"), - getLatestPendingReviewQuery(getLatestPendingReviewQueryParams{ - author: "williammartin", - owner: "owner", - repo: "repo", - prNum: 42, - - reviews: []getLatestPendingReviewQueryReview{ + viewerIDQuery("U_viewer"), + getPendingReviewsQuery(getPendingReviewsQueryParams{ + owner: "owner", + repo: "repo", + prNum: 42, + + reviews: []pendingReviewQueryReview{ { - id: "PR_kwDODKw3uc6WYN1T", - state: "PENDING", - url: "https://github.com/owner/repo/pull/42", + id: "PR_kwDODKw3uc6WYN1T", + authorID: "U_viewer", }, }, }), @@ -4106,6 +4101,57 @@ func TestDeletePendingPullRequestReview(t *testing.T) { } } +func TestGetPendingPullRequestReviewForViewerPaginates(t *testing.T) { + t.Parallel() + + var reviewQueries atomic.Int32 + transport := NewMockRoundTripper().OnRequest(http.MethodPost, "/graphql", func(w http.ResponseWriter, r *http.Request) { + var request struct { + Query string `json:"query"` + Variables map[string]any `json:"variables"` + } + require.NoError(t, json.NewDecoder(r.Body).Decode(&request)) + + w.Header().Set("Content-Type", "application/json") + switch { + case strings.Contains(request.Query, "viewer"): + require.NoError(t, json.NewEncoder(w).Encode(map[string]any{ + "data": map[string]any{ + "viewer": map[string]any{"id": "U_viewer"}, + }, + })) + case strings.Contains(request.Query, "reviews"): + queryNumber := reviewQueries.Add(1) + if queryNumber == 1 { + assert.Nil(t, request.Variables["after"]) + require.NoError(t, json.NewEncoder(w).Encode(pendingReviewsResponse( + []pendingReviewQueryReview{{id: "PR_other", authorID: "U_other"}}, + true, + "cursor-1", + ))) + return + } + + assert.Equal(t, "cursor-1", request.Variables["after"]) + require.NoError(t, json.NewEncoder(w).Encode(pendingReviewsResponse( + []pendingReviewQueryReview{{id: "PR_viewer", authorID: "U_viewer"}}, + false, + "", + ))) + default: + t.Fatalf("unexpected GraphQL query: %s", request.Query) + } + }) + + client := githubv4.NewClient(&http.Client{Transport: transport}) + reviewID, result := getPendingPullRequestReviewForViewer(context.Background(), client, "owner", "repo", 42) + + require.Nil(t, result) + require.NotNil(t, reviewID) + assert.Equal(t, githubv4.ID("PR_viewer"), *reviewID) + assert.Equal(t, int32(2), reviewQueries.Load()) +} + func TestGetPullRequestDiff(t *testing.T) { t.Parallel() @@ -4140,8 +4186,8 @@ index 5d6e7b2..8a4f5c3 100644 // author, then the raw diff; branch on the Accept header to serve both. prOrDiffHandler := func(authorLogin string) http.HandlerFunc { mockPR := &github.PullRequest{ - Number: github.Ptr(42), - User: &github.User{Login: github.Ptr(authorLogin)}, + Number: new(42), + User: &github.User{Login: new(authorLogin)}, } return func(w http.ResponseWriter, r *http.Request) { if strings.Contains(r.Header.Get("Accept"), "diff") { @@ -4252,76 +4298,103 @@ index 5d6e7b2..8a4f5c3 100644 } } -func viewerQuery(login string) githubv4mock.Matcher { +func viewerIDQuery(id string) githubv4mock.Matcher { return githubv4mock.NewQueryMatcher( struct { Viewer struct { - Login githubv4.String + ID githubv4.ID } `graphql:"viewer"` }{}, map[string]any{}, githubv4mock.DataResponse(map[string]any{ "viewer": map[string]any{ - "login": login, + "id": id, }, }), ) } -type getLatestPendingReviewQueryReview struct { - id string - state string - url string +type pendingReviewQueryReview struct { + id string + authorID string + authorField string } -type getLatestPendingReviewQueryParams struct { - author string - owner string - repo string - prNum int32 +type getPendingReviewsQueryParams struct { + owner string + repo string + prNum int32 - reviews []getLatestPendingReviewQueryReview + reviews []pendingReviewQueryReview } -func getLatestPendingReviewQuery(p getLatestPendingReviewQueryParams) githubv4mock.Matcher { - return githubv4mock.NewQueryMatcher( +func getPendingReviewsQuery(p getPendingReviewsQueryParams) githubv4mock.Matcher { + matcher := githubv4mock.NewQueryMatcher( struct { Repository struct { PullRequest struct { Reviews struct { Nodes []struct { - ID githubv4.ID - State githubv4.PullRequestReviewState - URL githubv4.URI + ID githubv4.ID + Author pendingReviewAuthor } - } `graphql:"reviews(first: 1, author: $author)"` + PageInfo struct { + HasNextPage githubv4.Boolean + EndCursor githubv4.String + } + } `graphql:"reviews(first: 100, after: $after, states: $states)"` } `graphql:"pullRequest(number: $prNum)"` } `graphql:"repository(owner: $owner, name: $name)"` }{}, map[string]any{ - "author": githubv4.String(p.author), + "after": (*githubv4.String)(nil), "owner": githubv4.String(p.owner), "name": githubv4.String(p.repo), "prNum": githubv4.Int(p.prNum), + "states": []githubv4.PullRequestReviewState{githubv4.PullRequestReviewStatePending}, }, - githubv4mock.DataResponse( - map[string]any{ - "repository": map[string]any{ - "pullRequest": map[string]any{ - "reviews": map[string]any{ - "nodes": []any{ - map[string]any{ - "id": p.reviews[0].id, - "state": p.reviews[0].state, - "url": p.reviews[0].url, - }, - }, - }, + githubv4mock.DataResponse(map[string]any{ + "repository": pendingReviewsData(p.reviews, false, "")["repository"], + }), + ) + matcher.Variables["states"] = []any{"PENDING"} + return matcher +} + +func pendingReviewsResponse(reviews []pendingReviewQueryReview, hasNextPage bool, endCursor string) map[string]any { + return map[string]any{ + "data": pendingReviewsData(reviews, hasNextPage, endCursor), + } +} + +func pendingReviewsData(reviews []pendingReviewQueryReview, hasNextPage bool, endCursor string) map[string]any { + nodes := make([]any, 0, len(reviews)) + for _, review := range reviews { + authorField := review.authorField + if authorField == "" { + authorField = "userId" + } + nodes = append(nodes, map[string]any{ + "id": review.id, + "author": map[string]any{ + authorField: review.authorID, + }, + }) + } + + return map[string]any{ + "repository": map[string]any{ + "pullRequest": map[string]any{ + "reviews": map[string]any{ + "nodes": nodes, + "pageInfo": map[string]any{ + "hasNextPage": hasNextPage, + "endCursor": endCursor, }, }, }, - ), - ) + }, + } } func TestAddReplyToPullRequestComment(t *testing.T) { @@ -4345,19 +4418,19 @@ func TestAddReplyToPullRequestComment(t *testing.T) { // Setup mock reply comment for success case mockReplyComment := &github.PullRequestComment{ - ID: github.Ptr(int64(456)), - Body: github.Ptr("This is a reply to the comment"), - InReplyTo: github.Ptr(int64(123)), - HTMLURL: github.Ptr("https://github.com/owner/repo/pull/42#discussion_r456"), + ID: new(int64(456)), + Body: new("This is a reply to the comment"), + InReplyTo: new(int64(123)), + HTMLURL: new("https://github.com/owner/repo/pull/42#discussion_r456"), User: &github.User{ - Login: github.Ptr("responder"), + Login: new("responder"), }, CreatedAt: &github.Timestamp{Time: time.Now()}, UpdatedAt: &github.Timestamp{Time: time.Now()}, } mockReaction := &github.Reaction{ - ID: github.Ptr(int64(789)), - Content: github.Ptr("rocket"), + ID: new(int64(789)), + Content: new("rocket"), } replyCreatedAfterReactionFailure := &atomic.Bool{} diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index 8e2dd25172..4b7e84cb9a 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -20,7 +20,7 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" @@ -496,9 +496,9 @@ SHA MUST be provided for existing file updates. // Create the file options opts := &github.RepositoryContentFileOptions{ - Message: github.Ptr(message), + Message: new(message), Content: contentBytes, - Branch: github.Ptr(branch), + Branch: new(branch), } // If SHA is provided, set it (for updates) @@ -507,7 +507,7 @@ SHA MUST be provided for existing file updates. return utils.NewToolResultError(err.Error()), nil, nil } if sha != "" { - opts.SHA = github.Ptr(sha) + opts.SHA = new(sha) } allowSymlinkWrite, err := OptionalParam[bool](args, "allow_symlink_write") @@ -699,10 +699,10 @@ func CreateRepository(t translations.TranslationHelperFunc) inventory.ServerTool } repo := &github.Repository{ - Name: github.Ptr(name), - Description: github.Ptr(description), - Private: github.Ptr(private), - AutoInit: github.Ptr(autoInit), + Name: new(name), + Description: new(description), + Private: new(private), + AutoInit: new(autoInit), } client, err := deps.GetClient(ctx) @@ -768,7 +768,7 @@ func DeleteRepository(t translations.TranslationHelperFunc) inventory.ServerTool Annotations: &mcp.ToolAnnotations{ Title: t("TOOL_DELETE_REPOSITORY_USER_TITLE", "Delete repository"), ReadOnlyHint: false, - DestructiveHint: github.Ptr(true), + DestructiveHint: new(true), }, InputSchema: &jsonschema.Schema{ Type: "object", @@ -1310,7 +1310,7 @@ func DeleteFile(t translations.TranslationHelperFunc) inventory.ServerTool { Annotations: &mcp.ToolAnnotations{ Title: t("TOOL_DELETE_FILE_USER_TITLE", "Delete file"), ReadOnlyHint: false, - DestructiveHint: github.Ptr(true), + DestructiveHint: new(true), }, InputSchema: &jsonschema.Schema{ Type: "object", @@ -1400,9 +1400,9 @@ func DeleteFile(t translations.TranslationHelperFunc) inventory.ServerTool { // Create a tree entry for the file deletion by setting SHA to nil treeEntries := []*github.TreeEntry{ { - Path: github.Ptr(path), - Mode: github.Ptr("100644"), // Regular file mode - Type: github.Ptr("blob"), + Path: new(path), + Mode: new("100644"), // Regular file mode + Type: new("blob"), SHA: nil, // Setting SHA to nil deletes the file }, } @@ -1428,7 +1428,7 @@ func DeleteFile(t translations.TranslationHelperFunc) inventory.ServerTool { // Create a new commit with the new tree commit := github.Commit{ - Message: github.Ptr(message), + Message: new(message), Tree: newTree, Parents: []*github.Commit{{SHA: baseCommit.SHA}}, } @@ -1454,7 +1454,7 @@ func DeleteFile(t translations.TranslationHelperFunc) inventory.ServerTool { ref.Object.SHA = newCommit.SHA _, resp, err = client.Git.UpdateRef(ctx, owner, repo, *ref.Ref, github.UpdateRef{ SHA: *newCommit.SHA, - Force: github.Ptr(false), + Force: new(false), }) if err != nil { return ghErrors.NewGitHubAPIErrorResponse(ctx, @@ -1708,10 +1708,10 @@ func PushFiles(t translations.TranslationHelperFunc) inventory.ServerTool { } entries = append(entries, &github.TreeEntry{ - Path: github.Ptr(filePath), - Mode: github.Ptr("100644"), - Type: github.Ptr("blob"), - Content: github.Ptr(content), + Path: new(filePath), + Mode: new("100644"), + Type: new("blob"), + Content: new(content), }) } @@ -1803,7 +1803,7 @@ func PushFiles(t translations.TranslationHelperFunc) inventory.ServerTool { // Create a new commit (baseCommit always has a value now) commit := github.Commit{ - Message: github.Ptr(message), + Message: new(message), Tree: newTree, Parents: []*github.Commit{{SHA: baseCommit.SHA}}, } @@ -1823,7 +1823,7 @@ func PushFiles(t translations.TranslationHelperFunc) inventory.ServerTool { ref.Object.SHA = newCommit.SHA updatedRef, resp, err := client.Git.UpdateRef(ctx, owner, repo, *ref.Ref, github.UpdateRef{ SHA: *newCommit.SHA, - Force: github.Ptr(false), + Force: new(false), }) if err != nil { return ghErrors.NewGitHubAPIErrorResponse(ctx, @@ -2345,10 +2345,10 @@ func sanitizeReleaseNameAndBody(release *github.RepositoryRelease) { return } if release.Name != nil { - release.Name = github.Ptr(sanitize.PlainText(*release.Name)) + release.Name = new(sanitize.PlainText(*release.Name)) } if release.Body != nil { - release.Body = github.Ptr(sanitize.Content(*release.Body)) + release.Body = new(sanitize.Content(*release.Body)) } } diff --git a/pkg/github/repositories_helper.go b/pkg/github/repositories_helper.go index 8859b4a2ba..519385fc8c 100644 --- a/pkg/github/repositories_helper.go +++ b/pkg/github/repositories_helper.go @@ -15,7 +15,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/raw" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -34,9 +34,9 @@ func initializeRepository(ctx context.Context, client *github.Client, owner, rep defaultBranch := repository.GetDefaultBranch() fileOpts := &github.RepositoryContentFileOptions{ - Message: github.Ptr("Initial commit"), + Message: new("Initial commit"), Content: []byte(""), - Branch: github.Ptr(defaultBranch), + Branch: new(defaultBranch), } // Create an initial empty commit to create the default branch diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go index 8194895afa..0c930ae8c0 100644 --- a/pkg/github/repositories_test.go +++ b/pkg/github/repositories_test.go @@ -17,7 +17,7 @@ import ( "github.com/github/github-mcp-server/pkg/raw" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" @@ -113,19 +113,19 @@ func Test_GetFileContents(t *testing.T) { // Setup mock directory content for success case mockDirContent := []*github.RepositoryContent{ { - Type: github.Ptr("file"), - Name: github.Ptr("README.md"), - Path: github.Ptr("README.md"), - SHA: github.Ptr(gitBlobSHA(mockRawContent)), - Size: github.Ptr(42), - HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/README.md"), + Type: new("file"), + Name: new("README.md"), + Path: new("README.md"), + SHA: new(gitBlobSHA(mockRawContent)), + Size: new(42), + HTMLURL: new("https://github.com/owner/repo/blob/main/README.md"), }, { - Type: github.Ptr("dir"), - Name: github.Ptr("src"), - Path: github.Ptr("src"), - SHA: github.Ptr("def456"), - HTMLURL: github.Ptr("https://github.com/owner/repo/tree/main/src"), + Type: new("dir"), + Name: new("src"), + Path: new("src"), + SHA: new("def456"), + HTMLURL: new("https://github.com/owner/repo/tree/main/src"), }, } @@ -147,12 +147,12 @@ func Test_GetFileContents(t *testing.T) { GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) fileContent := &github.RepositoryContent{ - Name: github.Ptr("README.md"), - Path: github.Ptr("README.md"), - SHA: github.Ptr(gitBlobSHA(mockRawContent)), - Type: github.Ptr("file"), - Content: github.Ptr(string(mockRawContent)), - Size: github.Ptr(len(mockRawContent)), + Name: new("README.md"), + Path: new("README.md"), + SHA: new(gitBlobSHA(mockRawContent)), + Type: new("file"), + Content: new(string(mockRawContent)), + Size: new(len(mockRawContent)), } contentBytes, _ := json.Marshal(fileContent) _, _ = w.Write(contentBytes) @@ -183,13 +183,13 @@ func Test_GetFileContents(t *testing.T) { pngContent := []byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01") encodedContent := base64.StdEncoding.EncodeToString(pngContent) fileContent := &github.RepositoryContent{ - Name: github.Ptr("test.png"), - Path: github.Ptr("test.png"), - SHA: github.Ptr(gitBlobSHA(pngContent)), - Type: github.Ptr("file"), - Content: github.Ptr(encodedContent), - Size: github.Ptr(len(pngContent)), - Encoding: github.Ptr("base64"), + Name: new("test.png"), + Path: new("test.png"), + SHA: new(gitBlobSHA(pngContent)), + Type: new("file"), + Content: new(encodedContent), + Size: new(len(pngContent)), + Encoding: new("base64"), } contentBytes, _ := json.Marshal(fileContent) _, _ = w.Write(contentBytes) @@ -219,13 +219,13 @@ func Test_GetFileContents(t *testing.T) { pdfContent := []byte("%PDF-1.4 fake pdf content") encodedContent := base64.StdEncoding.EncodeToString(pdfContent) fileContent := &github.RepositoryContent{ - Name: github.Ptr("document.pdf"), - Path: github.Ptr("document.pdf"), - SHA: github.Ptr(gitBlobSHA(pdfContent)), - Type: github.Ptr("file"), - Content: github.Ptr(encodedContent), - Size: github.Ptr(len(pdfContent)), - Encoding: github.Ptr("base64"), + Name: new("document.pdf"), + Path: new("document.pdf"), + SHA: new(gitBlobSHA(pdfContent)), + Type: new("file"), + Content: new(encodedContent), + Size: new(len(pdfContent)), + Encoding: new("base64"), } contentBytes, _ := json.Marshal(fileContent) _, _ = w.Write(contentBytes) @@ -274,13 +274,13 @@ func Test_GetFileContents(t *testing.T) { // Base64 encode the content as GitHub API does encodedContent := base64.StdEncoding.EncodeToString(mockRawContent) fileContent := &github.RepositoryContent{ - Name: github.Ptr("README.md"), - Path: github.Ptr("README.md"), - SHA: github.Ptr(gitBlobSHA(mockRawContent)), - Type: github.Ptr("file"), - Content: github.Ptr(encodedContent), - Size: github.Ptr(len(mockRawContent)), - Encoding: github.Ptr("base64"), + Name: new("README.md"), + Path: new("README.md"), + SHA: new(gitBlobSHA(mockRawContent)), + Type: new("file"), + Content: new(encodedContent), + Size: new(len(mockRawContent)), + Encoding: new("base64"), } contentBytes, _ := json.Marshal(fileContent) _, _ = w.Write(contentBytes) @@ -358,13 +358,13 @@ func Test_GetFileContents(t *testing.T) { // Base64 encode the content as GitHub API does encodedContent := base64.StdEncoding.EncodeToString(mockRawContent) fileContent := &github.RepositoryContent{ - Name: github.Ptr("README.md"), - Path: github.Ptr("README.md"), - SHA: github.Ptr(gitBlobSHA(mockRawContent)), - Type: github.Ptr("file"), - Content: github.Ptr(encodedContent), - Size: github.Ptr(len(mockRawContent)), - Encoding: github.Ptr("base64"), + Name: new("README.md"), + Path: new("README.md"), + SHA: new(gitBlobSHA(mockRawContent)), + Type: new("file"), + Content: new(encodedContent), + Size: new(len(mockRawContent)), + Encoding: new("base64"), } contentBytes, _ := json.Marshal(fileContent) _, _ = w.Write(contentBytes) @@ -391,22 +391,22 @@ func Test_GetFileContents(t *testing.T) { GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"), "GET /repos/owner/repo/git/trees/refs/heads/main": mockResponse(t, http.StatusOK, &github.Tree{ Entries: []*github.TreeEntry{{ - Path: github.Ptr("large-file.bin"), - Mode: github.Ptr("100644"), - Type: github.Ptr("blob"), - SHA: github.Ptr(strings.Repeat("a", 40)), + Path: new("large-file.bin"), + Mode: new("100644"), + Type: new("blob"), + SHA: new(strings.Repeat("a", 40)), }}, }), GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) // File larger than 1MB - Contents API returns metadata but no content fileContent := &github.RepositoryContent{ - Name: github.Ptr("large-file.bin"), - Path: github.Ptr("large-file.bin"), - SHA: github.Ptr(strings.Repeat("a", 40)), - Type: github.Ptr("file"), - Size: github.Ptr(2 * 1024 * 1024), // 2MB - DownloadURL: github.Ptr("https://raw.githubusercontent.com/owner/repo/main/large-file.bin"), + Name: new("large-file.bin"), + Path: new("large-file.bin"), + SHA: new(strings.Repeat("a", 40)), + Type: new("file"), + Size: new(2 * 1024 * 1024), // 2MB + DownloadURL: new("https://raw.githubusercontent.com/owner/repo/main/large-file.bin"), } contentBytes, _ := json.Marshal(fileContent) _, _ = w.Write(contentBytes) @@ -433,13 +433,13 @@ func Test_GetFileContents(t *testing.T) { GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) fileContent := &github.RepositoryContent{ - Name: github.Ptr(".gitkeep"), - Path: github.Ptr(".gitkeep"), - SHA: github.Ptr(gitBlobSHA(nil)), - Type: github.Ptr("file"), + Name: new(".gitkeep"), + Path: new(".gitkeep"), + SHA: new(gitBlobSHA(nil)), + Type: new("file"), Content: nil, - Size: github.Ptr(0), - Encoding: github.Ptr("base64"), + Size: new(0), + Encoding: new("base64"), } contentBytes, _ := json.Marshal(fileContent) _, _ = w.Write(contentBytes) @@ -593,8 +593,8 @@ func Test_GetFileContents_SymlinkDisclosure(t *testing.T) { content := []byte("ordinary content") result, requests := runRepositoryReadFixture(t, repositoryReadFixture{ contents: &github.RepositoryContent{ - Type: github.Ptr("file"), Path: github.Ptr("README.md"), SHA: github.Ptr(gitBlobSHA(content)), - Content: github.Ptr(string(content)), Size: github.Ptr(len(content)), + Type: new("file"), Path: new("README.md"), SHA: new(gitBlobSHA(content)), + Content: new(string(content)), Size: new(len(content)), }, }, args("README.md")) require.False(t, result.IsError) @@ -615,9 +615,9 @@ func Test_GetFileContents_SymlinkDisclosure(t *testing.T) { linkSHA := gitBlobSHA([]byte(target)) result, requests := runRepositoryReadFixture(t, repositoryReadFixture{ contents: &github.RepositoryContent{ - Type: github.Ptr("file"), Path: github.Ptr("docs/link"), SHA: github.Ptr(linkSHA), - Content: github.Ptr(base64.StdEncoding.EncodeToString(tc.content)), - Encoding: github.Ptr("base64"), Size: github.Ptr(len(tc.content)), + Type: new("file"), Path: new("docs/link"), SHA: new(linkSHA), + Content: new(base64.StdEncoding.EncodeToString(tc.content)), + Encoding: new("base64"), Size: new(len(tc.content)), }, blobs: map[string][]byte{linkSHA: []byte(target)}, }, args("docs/link")) @@ -644,8 +644,8 @@ func Test_GetFileContents_SymlinkDisclosure(t *testing.T) { target := "../../outside" result, requests := runRepositoryReadFixture(t, repositoryReadFixture{ contents: &github.RepositoryContent{ - Type: github.Ptr("symlink"), Path: github.Ptr("docs/link"), - SHA: github.Ptr(gitBlobSHA([]byte(target))), Target: github.Ptr(target), + Type: new("symlink"), Path: new("docs/link"), + SHA: new(gitBlobSHA([]byte(target))), Target: new(target), }, }, args("docs/link")) require.False(t, result.IsError) @@ -662,9 +662,9 @@ func Test_GetFileContents_SymlinkDisclosure(t *testing.T) { target, content := "target.txt", []byte("resolved") result, requests := runRepositoryReadFixture(t, repositoryReadFixture{ contents: &github.RepositoryContent{ - Type: github.Ptr("symlink"), Path: github.Ptr("docs/link"), - SHA: github.Ptr(gitBlobSHA([]byte(target))), Target: github.Ptr(target), - Content: github.Ptr(string(content)), Size: github.Ptr(len(content)), + Type: new("symlink"), Path: new("docs/link"), + SHA: new(gitBlobSHA([]byte(target))), Target: new(target), + Content: new(string(content)), Size: new(len(content)), }, }, args("docs/link")) require.False(t, result.IsError) @@ -676,8 +676,8 @@ func Test_GetFileContents_SymlinkDisclosure(t *testing.T) { t.Run("submodule is explicit", func(t *testing.T) { result, requests := runRepositoryReadFixture(t, repositoryReadFixture{ contents: &github.RepositoryContent{ - Type: github.Ptr("submodule"), Path: github.Ptr("vendor/module"), - SHA: github.Ptr(strings.Repeat("d", 40)), SubmoduleGitURL: github.Ptr("https://example.com/module.git"), + Type: new("submodule"), Path: new("vendor/module"), + SHA: new(strings.Repeat("d", 40)), SubmoduleGitURL: new("https://example.com/module.git"), }, }, args("vendor/module")) require.False(t, result.IsError) @@ -691,8 +691,8 @@ func Test_GetFileContents_SymlinkDisclosure(t *testing.T) { content := []byte("resolved") result, requests := runRepositoryReadFixture(t, repositoryReadFixture{ contents: &github.RepositoryContent{ - Type: github.Ptr("file"), Path: github.Ptr("bad"), SHA: github.Ptr("bad-sha"), - Content: github.Ptr(string(content)), Size: github.Ptr(len(content)), + Type: new("file"), Path: new("bad"), SHA: new("bad-sha"), + Content: new(string(content)), Size: new(len(content)), }, }, args("bad")) assert.Equal(t, 1, requests) @@ -702,8 +702,8 @@ func Test_GetFileContents_SymlinkDisclosure(t *testing.T) { pathSHA := gitBlobSHA(pathBlob) result, requests = runRepositoryReadFixture(t, repositoryReadFixture{ contents: &github.RepositoryContent{ - Type: github.Ptr("file"), Path: github.Ptr("bad"), SHA: github.Ptr(pathSHA), - Content: github.Ptr(string(content)), Size: github.Ptr(len(content)), + Type: new("file"), Path: new("bad"), SHA: new(pathSHA), + Content: new(string(content)), Size: new(len(content)), }, blobs: map[string][]byte{pathSHA: pathBlob}, }, args("bad")) @@ -723,10 +723,10 @@ func Test_GetFileContents_ContentlessRequestCounts(t *testing.T) { t.Run("normal large file", func(t *testing.T) { result, requests := runRepositoryReadFixture(t, repositoryReadFixture{ contents: &github.RepositoryContent{ - Type: github.Ptr("file"), Path: github.Ptr("large.bin"), SHA: github.Ptr(fileSHA), Size: github.Ptr(largeSize), + Type: new("file"), Path: new("large.bin"), SHA: new(fileSHA), Size: new(largeSize), }, trees: map[string]*github.Tree{commitSHA: {Entries: []*github.TreeEntry{ - {Path: github.Ptr("large.bin"), Mode: github.Ptr("100644"), Type: github.Ptr("blob"), SHA: github.Ptr(fileSHA)}, + {Path: new("large.bin"), Mode: new("100644"), Type: new("blob"), SHA: new(fileSHA)}, }}}, }, args("large.bin")) require.False(t, result.IsError) @@ -740,15 +740,15 @@ func Test_GetFileContents_ContentlessRequestCounts(t *testing.T) { linkSHA := gitBlobSHA([]byte(target)) result, requests := runRepositoryReadFixture(t, repositoryReadFixture{ contents: &github.RepositoryContent{ - Type: github.Ptr("file"), Path: github.Ptr("docs/link"), SHA: github.Ptr(linkSHA), Size: github.Ptr(largeSize), + Type: new("file"), Path: new("docs/link"), SHA: new(linkSHA), Size: new(largeSize), }, blobs: map[string][]byte{linkSHA: []byte(target)}, trees: map[string]*github.Tree{ commitSHA: {Entries: []*github.TreeEntry{ - {Path: github.Ptr("docs"), Mode: github.Ptr("040000"), Type: github.Ptr("tree"), SHA: github.Ptr("docs-tree")}, + {Path: new("docs"), Mode: new("040000"), Type: new("tree"), SHA: new("docs-tree")}, }}, "docs-tree": {Entries: []*github.TreeEntry{ - {Path: github.Ptr("link"), Mode: github.Ptr(gitSymlinkMode), Type: github.Ptr("blob"), SHA: github.Ptr(linkSHA)}, + {Path: new("link"), Mode: new(gitSymlinkMode), Type: new("blob"), SHA: new(linkSHA)}, }}, }, inspect: func(r *http.Request) { @@ -767,9 +767,9 @@ func Test_GetFileContents_ContentlessRequestCounts(t *testing.T) { t.Run("truncated tree fails closed", func(t *testing.T) { result, requests := runRepositoryReadFixture(t, repositoryReadFixture{ contents: &github.RepositoryContent{ - Type: github.Ptr("file"), Path: github.Ptr("large.bin"), SHA: github.Ptr(fileSHA), Size: github.Ptr(largeSize), + Type: new("file"), Path: new("large.bin"), SHA: new(fileSHA), Size: new(largeSize), }, - trees: map[string]*github.Tree{commitSHA: {Truncated: github.Ptr(true)}}, + trees: map[string]*github.Tree{commitSHA: {Truncated: new(true)}}, }, args("large.bin")) assert.Equal(t, 2, requests) assert.Contains(t, getErrorResult(t, result).Text, "truncated") @@ -778,7 +778,7 @@ func Test_GetFileContents_ContentlessRequestCounts(t *testing.T) { t.Run("directory remains one request", func(t *testing.T) { result, requests := runRepositoryReadFixture(t, repositoryReadFixture{ contents: []*github.RepositoryContent{{ - Type: github.Ptr("file"), Path: github.Ptr("docs/readme"), SHA: github.Ptr(fileSHA), + Type: new("file"), Path: new("docs/readme"), SHA: new(fileSHA), }}, }, args("docs")) require.False(t, result.IsError) @@ -789,21 +789,21 @@ func Test_GetFileContents_ContentlessRequestCounts(t *testing.T) { func Test_GetFileContents_DirectoryFieldFiltering(t *testing.T) { mockDirContent := []*github.RepositoryContent{ { - Type: github.Ptr("file"), - Name: github.Ptr("README.md"), - Path: github.Ptr("README.md"), - SHA: github.Ptr("abc123"), - Size: github.Ptr(42), - URL: github.Ptr("https://api.github.com/repos/owner/repo/contents/README.md"), - HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/README.md"), - DownloadURL: github.Ptr("https://raw.githubusercontent.com/owner/repo/main/README.md"), + Type: new("file"), + Name: new("README.md"), + Path: new("README.md"), + SHA: new("abc123"), + Size: new(42), + URL: new("https://api.github.com/repos/owner/repo/contents/README.md"), + HTMLURL: new("https://github.com/owner/repo/blob/main/README.md"), + DownloadURL: new("https://raw.githubusercontent.com/owner/repo/main/README.md"), }, { - Type: github.Ptr("dir"), - Name: github.Ptr("src"), - Path: github.Ptr("src"), - SHA: github.Ptr("def456"), - HTMLURL: github.Ptr("https://github.com/owner/repo/tree/main/src"), + Type: new("dir"), + Name: new("src"), + Path: new("src"), + SHA: new("def456"), + HTMLURL: new("https://github.com/owner/repo/tree/main/src"), }, } @@ -852,14 +852,14 @@ func Test_GetFileContents_DirectoryFieldFiltering(t *testing.T) { func Test_GetFileContents_DirectoryFieldsTelemetry(t *testing.T) { mockDirContent := []*github.RepositoryContent{ { - Type: github.Ptr("file"), - Name: github.Ptr("README.md"), - Path: github.Ptr("README.md"), - SHA: github.Ptr("abc123"), - Size: github.Ptr(42), - URL: github.Ptr("https://api.github.com/repos/owner/repo/contents/README.md"), - HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/README.md"), - DownloadURL: github.Ptr("https://raw.githubusercontent.com/owner/repo/main/README.md"), + Type: new("file"), + Name: new("README.md"), + Path: new("README.md"), + SHA: new("abc123"), + Size: new(42), + URL: new("https://api.github.com/repos/owner/repo/contents/README.md"), + HTMLURL: new("https://github.com/owner/repo/blob/main/README.md"), + DownloadURL: new("https://raw.githubusercontent.com/owner/repo/main/README.md"), }, } @@ -919,13 +919,13 @@ func Test_GetFileContents_IFC_InsidersMode(t *testing.T) { w.WriteHeader(http.StatusOK) encodedContent := base64.StdEncoding.EncodeToString(mockRawContent) fileContent := &github.RepositoryContent{ - Name: github.Ptr("README.md"), - Path: github.Ptr("README.md"), - SHA: github.Ptr(gitBlobSHA(mockRawContent)), - Type: github.Ptr("file"), - Content: github.Ptr(encodedContent), - Size: github.Ptr(len(mockRawContent)), - Encoding: github.Ptr("base64"), + Name: new("README.md"), + Path: new("README.md"), + SHA: new(gitBlobSHA(mockRawContent)), + Type: new("file"), + Content: new(encodedContent), + Size: new(len(mockRawContent)), + Encoding: new("base64"), } contentBytes, _ := json.Marshal(fileContent) _, _ = w.Write(contentBytes) @@ -1012,13 +1012,13 @@ func Test_GetFileContents_IFC_InsidersMode(t *testing.T) { w.WriteHeader(http.StatusOK) encodedContent := base64.StdEncoding.EncodeToString(mockRawContent) fileContent := &github.RepositoryContent{ - Name: github.Ptr("README.md"), - Path: github.Ptr("README.md"), - SHA: github.Ptr(gitBlobSHA(mockRawContent)), - Type: github.Ptr("file"), - Content: github.Ptr(encodedContent), - Size: github.Ptr(len(mockRawContent)), - Encoding: github.Ptr("base64"), + Name: new("README.md"), + Path: new("README.md"), + SHA: new(gitBlobSHA(mockRawContent)), + Type: new("file"), + Content: new(encodedContent), + Size: new(len(mockRawContent)), + Encoding: new("base64"), } contentBytes, _ := json.Marshal(fileContent) _, _ = w.Write(contentBytes) @@ -1048,8 +1048,8 @@ func Test_GetFileContents_IFC_InsidersMode(t *testing.T) { GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"), GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, map[string]any{"private": false}), GetReposContentsByOwnerByRepoByPath: mockResponse(t, http.StatusOK, &github.RepositoryContent{ - Type: github.Ptr("file"), Path: github.Ptr("link"), SHA: github.Ptr(linkSHA), - Content: github.Ptr(string(content)), Size: github.Ptr(len(content)), + Type: new("file"), Path: new("link"), SHA: new(linkSHA), + Content: new(string(content)), Size: new(len(content)), }), GetReposGitBlobsByOwnerByRepoByFileSHA: func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write(target) @@ -1081,9 +1081,9 @@ func Test_GetCommit_IFC_FeatureFlag(t *testing.T) { serverTool := GetCommit(translations.NullTranslationHelper) mockCommit := &github.RepositoryCommit{ - SHA: github.Ptr("abc123def456"), - Commit: &github.Commit{Message: github.Ptr("First commit")}, - HTMLURL: github.Ptr("https://github.com/owner/repo/commit/abc123def456"), + SHA: new("abc123def456"), + Commit: &github.Commit{Message: new("First commit")}, + HTMLURL: new("https://github.com/owner/repo/commit/abc123def456"), } makeMockClient := func(isPrivate bool) *http.Client { @@ -1207,16 +1207,16 @@ func Test_ForkRepository(t *testing.T) { // Setup mock forked repo for success case mockForkedRepo := &github.Repository{ - ID: github.Ptr(int64(123456)), - Name: github.Ptr("repo"), - FullName: github.Ptr("new-owner/repo"), + ID: new(int64(123456)), + Name: new("repo"), + FullName: new("new-owner/repo"), Owner: &github.User{ - Login: github.Ptr("new-owner"), + Login: new("new-owner"), }, - HTMLURL: github.Ptr("https://github.com/new-owner/repo"), - DefaultBranch: github.Ptr("main"), - Fork: github.Ptr(true), - ForksCount: github.Ptr(0), + HTMLURL: new("https://github.com/new-owner/repo"), + DefaultBranch: new("main"), + Fork: new(true), + ForksCount: new(0), } tests := []struct { @@ -1310,22 +1310,22 @@ func Test_CreateBranch(t *testing.T) { // Setup mock repository for default branch test mockRepo := &github.Repository{ - DefaultBranch: github.Ptr("main"), + DefaultBranch: new("main"), } // Setup mock reference for from_branch tests mockSourceRef := &github.Reference{ - Ref: github.Ptr("refs/heads/main"), + Ref: new("refs/heads/main"), Object: &github.GitObject{ - SHA: github.Ptr("abc123def456"), + SHA: new("abc123def456"), }, } // Setup mock created reference mockCreatedRef := &github.Reference{ - Ref: github.Ptr("refs/heads/new-feature"), + Ref: new("refs/heads/new-feature"), Object: &github.GitObject{ - SHA: github.Ptr("abc123def456"), + SHA: new("abc123def456"), }, } @@ -1521,32 +1521,32 @@ func Test_GetCommit(t *testing.T) { assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "sha"}) mockCommit := &github.RepositoryCommit{ - SHA: github.Ptr("abc123def456"), + SHA: new("abc123def456"), Commit: &github.Commit{ - Message: github.Ptr("First commit"), + Message: new("First commit"), Author: &github.CommitAuthor{ - Name: github.Ptr("Test User"), - Email: github.Ptr("test@example.com"), + Name: new("Test User"), + Email: new("test@example.com"), Date: &github.Timestamp{Time: time.Now().Add(-48 * time.Hour)}, }, }, Author: &github.User{ - Login: github.Ptr("testuser"), + Login: new("testuser"), }, - HTMLURL: github.Ptr("https://github.com/owner/repo/commit/abc123def456"), + HTMLURL: new("https://github.com/owner/repo/commit/abc123def456"), Stats: &github.CommitStats{ - Additions: github.Ptr(10), - Deletions: github.Ptr(2), - Total: github.Ptr(12), + Additions: new(10), + Deletions: new(2), + Total: new(12), }, Files: []*github.CommitFile{ { - Filename: github.Ptr("file1.go"), - Status: github.Ptr("modified"), - Additions: github.Ptr(10), - Deletions: github.Ptr(2), - Changes: github.Ptr(12), - Patch: github.Ptr("@@ -1,2 +1,10 @@"), + Filename: new("file1.go"), + Status: new("modified"), + Additions: new(10), + Deletions: new(2), + Changes: new(12), + Patch: new("@@ -1,2 +1,10 @@"), }, }, } @@ -1635,24 +1635,24 @@ func Test_GetCommit(t *testing.T) { func Test_GetCommit_Detail(t *testing.T) { mockCommit := &github.RepositoryCommit{ - SHA: github.Ptr("abc123def456"), - HTMLURL: github.Ptr("https://github.com/owner/repo/commit/abc123def456"), + SHA: new("abc123def456"), + HTMLURL: new("https://github.com/owner/repo/commit/abc123def456"), Commit: &github.Commit{ - Message: github.Ptr("First commit"), + Message: new("First commit"), }, Stats: &github.CommitStats{ - Additions: github.Ptr(10), - Deletions: github.Ptr(2), - Total: github.Ptr(12), + Additions: new(10), + Deletions: new(2), + Total: new(12), }, Files: []*github.CommitFile{ { - Filename: github.Ptr("file1.go"), - Status: github.Ptr("modified"), - Additions: github.Ptr(10), - Deletions: github.Ptr(2), - Changes: github.Ptr(12), - Patch: github.Ptr("@@ -1,2 +1,10 @@\n+new line"), + Filename: new("file1.go"), + Status: new("modified"), + Additions: new(10), + Deletions: new(2), + Changes: new(12), + Patch: new("@@ -1,2 +1,10 @@\n+new line"), }, }, } @@ -1773,73 +1773,73 @@ func Test_ListCommits(t *testing.T) { // Setup mock commits for success case mockCommits := []*github.RepositoryCommit{ { - SHA: github.Ptr("abc123def456"), + SHA: new("abc123def456"), Commit: &github.Commit{ - Message: github.Ptr("First commit"), + Message: new("First commit"), Author: &github.CommitAuthor{ - Name: github.Ptr("Test User"), - Email: github.Ptr("test@example.com"), + Name: new("Test User"), + Email: new("test@example.com"), Date: &github.Timestamp{Time: time.Now().Add(-48 * time.Hour)}, }, }, Author: &github.User{ - Login: github.Ptr("testuser"), - ID: github.Ptr(int64(12345)), - HTMLURL: github.Ptr("https://github.com/testuser"), - AvatarURL: github.Ptr("https://github.com/testuser.png"), + Login: new("testuser"), + ID: new(int64(12345)), + HTMLURL: new("https://github.com/testuser"), + AvatarURL: new("https://github.com/testuser.png"), }, - HTMLURL: github.Ptr("https://github.com/owner/repo/commit/abc123def456"), + HTMLURL: new("https://github.com/owner/repo/commit/abc123def456"), Stats: &github.CommitStats{ - Additions: github.Ptr(10), - Deletions: github.Ptr(5), - Total: github.Ptr(15), + Additions: new(10), + Deletions: new(5), + Total: new(15), }, Files: []*github.CommitFile{ { - Filename: github.Ptr("src/main.go"), - Status: github.Ptr("modified"), - Additions: github.Ptr(8), - Deletions: github.Ptr(3), - Changes: github.Ptr(11), + Filename: new("src/main.go"), + Status: new("modified"), + Additions: new(8), + Deletions: new(3), + Changes: new(11), }, { - Filename: github.Ptr("README.md"), - Status: github.Ptr("added"), - Additions: github.Ptr(2), - Deletions: github.Ptr(2), - Changes: github.Ptr(4), + Filename: new("README.md"), + Status: new("added"), + Additions: new(2), + Deletions: new(2), + Changes: new(4), }, }, }, { - SHA: github.Ptr("def456abc789"), + SHA: new("def456abc789"), Commit: &github.Commit{ - Message: github.Ptr("Second commit"), + Message: new("Second commit"), Author: &github.CommitAuthor{ - Name: github.Ptr("Another User"), - Email: github.Ptr("another@example.com"), + Name: new("Another User"), + Email: new("another@example.com"), Date: &github.Timestamp{Time: time.Now().Add(-24 * time.Hour)}, }, }, Author: &github.User{ - Login: github.Ptr("anotheruser"), - ID: github.Ptr(int64(67890)), - HTMLURL: github.Ptr("https://github.com/anotheruser"), - AvatarURL: github.Ptr("https://github.com/anotheruser.png"), + Login: new("anotheruser"), + ID: new(int64(67890)), + HTMLURL: new("https://github.com/anotheruser"), + AvatarURL: new("https://github.com/anotheruser.png"), }, - HTMLURL: github.Ptr("https://github.com/owner/repo/commit/def456abc789"), + HTMLURL: new("https://github.com/owner/repo/commit/def456abc789"), Stats: &github.CommitStats{ - Additions: github.Ptr(20), - Deletions: github.Ptr(10), - Total: github.Ptr(30), + Additions: new(20), + Deletions: new(10), + Total: new(30), }, Files: []*github.CommitFile{ { - Filename: github.Ptr("src/utils.go"), - Status: github.Ptr("added"), - Additions: github.Ptr(20), - Deletions: github.Ptr(10), - Changes: github.Ptr(30), + Filename: new("src/utils.go"), + Status: new("added"), + Additions: new(20), + Deletions: new(10), + Changes: new(30), }, }, }, @@ -2077,22 +2077,22 @@ func Test_CreateOrUpdateFile(t *testing.T) { // Setup mock file content response mockFileResponse := &github.RepositoryContentResponse{ Content: &github.RepositoryContent{ - Name: github.Ptr("example.md"), - Path: github.Ptr("docs/example.md"), - SHA: github.Ptr("abc123def456"), - Size: github.Ptr(42), - HTMLURL: github.Ptr("https://github.com/owner/repo/blob/main/docs/example.md"), - DownloadURL: github.Ptr("https://raw.githubusercontent.com/owner/repo/main/docs/example.md"), + Name: new("example.md"), + Path: new("docs/example.md"), + SHA: new("abc123def456"), + Size: new(42), + HTMLURL: new("https://github.com/owner/repo/blob/main/docs/example.md"), + DownloadURL: new("https://raw.githubusercontent.com/owner/repo/main/docs/example.md"), }, Commit: github.Commit{ - SHA: github.Ptr("def456abc789"), - Message: github.Ptr("Add example file"), + SHA: new("def456abc789"), + Message: new("Add example file"), Author: &github.CommitAuthor{ - Name: github.Ptr("Test User"), - Email: github.Ptr("test@example.com"), + Name: new("Test User"), + Email: new("test@example.com"), Date: &github.Timestamp{Time: time.Now()}, }, - HTMLURL: github.Ptr("https://github.com/owner/repo/commit/def456abc789"), + HTMLURL: new("https://github.com/owner/repo/commit/def456abc789"), }, } symlinkTarget := []byte("other.md") @@ -2105,10 +2105,10 @@ func Test_CreateOrUpdateFile(t *testing.T) { tree = &github.Tree{ Entries: []*github.TreeEntry{ { - Path: github.Ptr("docs"), - Mode: github.Ptr("040000"), - Type: github.Ptr("tree"), - SHA: github.Ptr("docs-tree"), + Path: new("docs"), + Mode: new("040000"), + Type: new("tree"), + SHA: new("docs-tree"), }, }, } @@ -2120,10 +2120,10 @@ func Test_CreateOrUpdateFile(t *testing.T) { tree = &github.Tree{ Entries: []*github.TreeEntry{ { - Path: github.Ptr("example.md"), - Mode: github.Ptr(mode), - Type: github.Ptr("blob"), - SHA: github.Ptr(entrySHA), + Path: new("example.md"), + Mode: new(mode), + Type: new("blob"), + SHA: new(entrySHA), }, }, } @@ -2180,12 +2180,12 @@ func Test_CreateOrUpdateFile(t *testing.T) { mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposGitTreesByOwnerByRepoByTree: mockPathTree("100644"), "GET /repos/owner/repo/contents/docs/example.md": mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr("abc123def456"), - Type: github.Ptr("file"), + SHA: new("abc123def456"), + Type: new("file"), }), "GET /repos/{owner}/{repo}/contents/{path:.*}": mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr("abc123def456"), - Type: github.Ptr("file"), + SHA: new("abc123def456"), + Type: new("file"), }), PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]any{ "message": "Update example file", @@ -2246,12 +2246,12 @@ func Test_CreateOrUpdateFile(t *testing.T) { mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposGitTreesByOwnerByRepoByTree: mockPathTree("100644"), "GET /repos/owner/repo/contents/docs/example.md": mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr("abc123def456"), - Type: github.Ptr("file"), + SHA: new("abc123def456"), + Type: new("file"), }), "GET /repos/{owner}/{repo}/contents/{path:.*}": mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr("abc123def456"), - Type: github.Ptr("file"), + SHA: new("abc123def456"), + Type: new("file"), }), PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]any{ "message": "Update example file", @@ -2291,12 +2291,12 @@ func Test_CreateOrUpdateFile(t *testing.T) { _, _ = w.Write(symlinkTarget) }, "GET /repos/owner/repo/contents/docs/example.md": mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr("abc123def456"), - Type: github.Ptr("file"), + SHA: new("abc123def456"), + Type: new("file"), }), "GET /repos/{owner}/{repo}/contents/{path:.*}": mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr("abc123def456"), - Type: github.Ptr("file"), + SHA: new("abc123def456"), + Type: new("file"), }), }), requestArgs: map[string]any{ @@ -2331,12 +2331,12 @@ func Test_CreateOrUpdateFile(t *testing.T) { _, _ = w.Write(symlinkTarget) }, "GET /repos/owner/repo/contents/docs/example.md": mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr("abc123def456"), - Type: github.Ptr("file"), + SHA: new("abc123def456"), + Type: new("file"), }), "GET /repos/{owner}/{repo}/contents/{path:.*}": mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr("abc123def456"), - Type: github.Ptr("file"), + SHA: new("abc123def456"), + Type: new("file"), }), }), requestArgs: map[string]any{ @@ -2356,12 +2356,12 @@ func Test_CreateOrUpdateFile(t *testing.T) { name: "allows intentional symbolic link update with explicit opt-in", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ "GET /repos/owner/repo/contents/docs/example.md": mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr("abc123def456"), - Type: github.Ptr("file"), + SHA: new("abc123def456"), + Type: new("file"), }), "GET /repos/{owner}/{repo}/contents/{path:.*}": mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr("abc123def456"), - Type: github.Ptr("file"), + SHA: new("abc123def456"), + Type: new("file"), }), PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]any{ "message": "Change link target", @@ -2398,14 +2398,14 @@ func Test_CreateOrUpdateFile(t *testing.T) { name: "rejects explicit symbolic link response without tree inspection", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ "GET /repos/owner/repo/contents/docs/example.md": mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr("abc123def456"), - Type: github.Ptr("symlink"), - Target: github.Ptr("../../outside"), + SHA: new("abc123def456"), + Type: new("symlink"), + Target: new("../../outside"), }), "GET /repos/{owner}/{repo}/contents/{path:.*}": mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr("abc123def456"), - Type: github.Ptr("symlink"), - Target: github.Ptr("../../outside"), + SHA: new("abc123def456"), + Type: new("symlink"), + Target: new("../../outside"), }), }), requestArgs: map[string]any{ @@ -2430,15 +2430,15 @@ func Test_CreateOrUpdateFile(t *testing.T) { name: "fails closed when git tree is truncated", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposGitTreesByOwnerByRepoByTree: mockResponse(t, http.StatusOK, &github.Tree{ - Truncated: github.Ptr(true), + Truncated: new(true), }), "GET /repos/owner/repo/contents/docs/example.md": mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr("abc123def456"), - Type: github.Ptr("file"), + SHA: new("abc123def456"), + Type: new("file"), }), "GET /repos/{owner}/{repo}/contents/{path:.*}": mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr("abc123def456"), - Type: github.Ptr("file"), + SHA: new("abc123def456"), + Type: new("file"), }), }), requestArgs: map[string]any{ @@ -2460,16 +2460,16 @@ func Test_CreateOrUpdateFile(t *testing.T) { "GET /repos/owner/repo/contents/docs/example.md": expectQueryParams(t, map[string]string{ "ref": "main", }).andThen(mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr(symlinkSHA), - Type: github.Ptr("symlink"), - Target: github.Ptr(string(symlinkTarget)), + SHA: new(symlinkSHA), + Type: new("symlink"), + Target: new(string(symlinkTarget)), })), "GET /repos/{owner}/{repo}/contents/{path:.*}": expectQueryParams(t, map[string]string{ "ref": "main", }).andThen(mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr(symlinkSHA), - Type: github.Ptr("symlink"), - Target: github.Ptr(string(symlinkTarget)), + SHA: new(symlinkSHA), + Type: new("symlink"), + Target: new(string(symlinkTarget)), })), }), requestArgs: map[string]any{ @@ -2558,14 +2558,14 @@ func Test_CreateOrUpdateFile(t *testing.T) { "GET /repos/owner/repo/contents/docs/example.md": expectQueryParams(t, map[string]string{ "ref": "release/#candidate", }).andThen(mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr("existing123"), - Type: github.Ptr("file"), + SHA: new("existing123"), + Type: new("file"), })), "GET /repos/{owner}/{repo}/contents/{path:.*}": expectQueryParams(t, map[string]string{ "ref": "release/#candidate", }).andThen(mockResponse(t, http.StatusOK, &github.RepositoryContent{ - SHA: github.Ptr("existing123"), - Type: github.Ptr("file"), + SHA: new("existing123"), + Type: new("file"), })), }), requestArgs: map[string]any{ @@ -2749,13 +2749,13 @@ func Test_CreateRepository(t *testing.T) { // Setup mock repository response mockRepo := &github.Repository{ - Name: github.Ptr("test-repo"), - Description: github.Ptr("Test repository"), - Private: github.Ptr(true), - HTMLURL: github.Ptr("https://github.com/testuser/test-repo"), + Name: new("test-repo"), + Description: new("Test repository"), + Private: new(true), + HTMLURL: new("https://github.com/testuser/test-repo"), CreatedAt: &github.Timestamp{Time: time.Now()}, Owner: &github.User{ - Login: github.Ptr("testuser"), + Login: new("testuser"), }, } @@ -2939,35 +2939,35 @@ func Test_PushFiles(t *testing.T) { // Setup mock objects mockRef := &github.Reference{ - Ref: github.Ptr("refs/heads/main"), + Ref: new("refs/heads/main"), Object: &github.GitObject{ - SHA: github.Ptr("abc123"), - URL: github.Ptr("https://api.github.com/repos/owner/repo/git/trees/abc123"), + SHA: new("abc123"), + URL: new("https://api.github.com/repos/owner/repo/git/trees/abc123"), }, } mockCommit := &github.Commit{ - SHA: github.Ptr("abc123"), + SHA: new("abc123"), Tree: &github.Tree{ - SHA: github.Ptr("def456"), + SHA: new("def456"), }, } mockTree := &github.Tree{ - SHA: github.Ptr("ghi789"), + SHA: new("ghi789"), } mockNewCommit := &github.Commit{ - SHA: github.Ptr("jkl012"), - Message: github.Ptr("Update multiple files"), - HTMLURL: github.Ptr("https://github.com/owner/repo/commit/jkl012"), + SHA: new("jkl012"), + Message: new("Update multiple files"), + HTMLURL: new("https://github.com/owner/repo/commit/jkl012"), } mockUpdatedRef := &github.Reference{ - Ref: github.Ptr("refs/heads/main"), + Ref: new("refs/heads/main"), Object: &github.GitObject{ - SHA: github.Ptr("jkl012"), - URL: github.Ptr("https://api.github.com/repos/owner/repo/git/trees/jkl012"), + SHA: new("jkl012"), + URL: new("https://api.github.com/repos/owner/repo/git/trees/jkl012"), }, } @@ -3250,7 +3250,7 @@ func Test_PushFiles(t *testing.T) { WithRequestMatch( GetReposByOwnerByRepo, &github.Repository{ - DefaultBranch: github.Ptr("main"), + DefaultBranch: new("main"), }, ), // Create initial file using Contents API @@ -3264,7 +3264,7 @@ func Test_PushFiles(t *testing.T) { require.Equal(t, "main", body["branch"]) w.WriteHeader(http.StatusCreated) response := &github.RepositoryContentResponse{ - Commit: github.Commit{SHA: github.Ptr("abc123")}, + Commit: github.Commit{SHA: new("abc123")}, } b, _ := json.Marshal(response) _, _ = w.Write(b) @@ -3328,8 +3328,8 @@ func Test_PushFiles(t *testing.T) { // Second call: returns the updated reference after first file creation w.WriteHeader(http.StatusOK) b, _ := json.Marshal(&github.Reference{ - Ref: github.Ptr("refs/heads/main"), - Object: &github.GitObject{SHA: github.Ptr("init456")}, + Ref: new("refs/heads/main"), + Object: &github.GitObject{SHA: new("init456")}, }) _, _ = w.Write(b) } @@ -3340,7 +3340,7 @@ func Test_PushFiles(t *testing.T) { WithRequestMatch( GetReposByOwnerByRepo, &github.Repository{ - DefaultBranch: github.Ptr("main"), + DefaultBranch: new("main"), }, ), // Create initial empty README.md file using Contents API to initialize repo @@ -3358,12 +3358,12 @@ func Test_PushFiles(t *testing.T) { w.WriteHeader(http.StatusCreated) response := &github.RepositoryContentResponse{ Content: &github.RepositoryContent{ - SHA: github.Ptr("readme123"), + SHA: new("readme123"), }, Commit: github.Commit{ - SHA: github.Ptr("init456"), + SHA: new("init456"), Tree: &github.Tree{ - SHA: github.Ptr("tree456"), + SHA: new("tree456"), }, }, } @@ -3377,9 +3377,9 @@ func Test_PushFiles(t *testing.T) { http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) response := &github.Commit{ - SHA: github.Ptr("init456"), + SHA: new("init456"), Tree: &github.Tree{ - SHA: github.Ptr("tree456"), + SHA: new("tree456"), }, } b, _ := json.Marshal(response) @@ -3479,7 +3479,7 @@ func Test_PushFiles(t *testing.T) { WithRequestMatch( GetReposByOwnerByRepo, &github.Repository{ - DefaultBranch: github.Ptr("main"), + DefaultBranch: new("main"), }, ), // Fail to create initial file using Contents API @@ -3532,15 +3532,15 @@ func Test_PushFiles(t *testing.T) { WithRequestMatch( GetReposByOwnerByRepo, &github.Repository{ - DefaultBranch: github.Ptr("main"), + DefaultBranch: new("main"), }, ), // Create initial file using Contents API WithRequestMatch( PutReposContentsByOwnerByRepoByPath, &github.RepositoryContentResponse{ - Content: &github.RepositoryContent{SHA: github.Ptr("readme123")}, - Commit: github.Commit{SHA: github.Ptr("init456")}, + Content: &github.RepositoryContent{SHA: new("readme123")}, + Commit: github.Commit{SHA: new("init456")}, }, ), ), @@ -3578,15 +3578,15 @@ func Test_PushFiles(t *testing.T) { WithRequestMatch( GetReposByOwnerByRepo, &github.Repository{ - DefaultBranch: github.Ptr("main"), + DefaultBranch: new("main"), }, ), // Create initial file using Contents API WithRequestMatch( PutReposContentsByOwnerByRepoByPath, &github.RepositoryContentResponse{ - Content: &github.RepositoryContent{SHA: github.Ptr("readme123")}, - Commit: github.Commit{SHA: github.Ptr("init456")}, + Content: &github.RepositoryContent{SHA: new("readme123")}, + Commit: github.Commit{SHA: new("init456")}, }, ), // Fail to get commit @@ -4122,12 +4122,12 @@ func Test_ListBranches(t *testing.T) { // Setup mock branches for success case mockBranches := []*github.Branch{ { - Name: github.Ptr("main"), - Commit: &github.RepositoryCommit{SHA: github.Ptr("abc123")}, + Name: new("main"), + Commit: &github.RepositoryCommit{SHA: new("abc123")}, }, { - Name: github.Ptr("develop"), - Commit: &github.RepositoryCommit{SHA: github.Ptr("def456")}, + Name: new("develop"), + Commit: &github.RepositoryCommit{SHA: new("def456")}, }, } @@ -4241,27 +4241,27 @@ func Test_DeleteFile(t *testing.T) { // Setup mock objects for Git Data API mockRef := &github.Reference{ - Ref: github.Ptr("refs/heads/main"), + Ref: new("refs/heads/main"), Object: &github.GitObject{ - SHA: github.Ptr("abc123"), + SHA: new("abc123"), }, } mockCommit := &github.Commit{ - SHA: github.Ptr("abc123"), + SHA: new("abc123"), Tree: &github.Tree{ - SHA: github.Ptr("def456"), + SHA: new("def456"), }, } mockTree := &github.Tree{ - SHA: github.Ptr("ghi789"), + SHA: new("ghi789"), } mockNewCommit := &github.Commit{ - SHA: github.Ptr("jkl012"), - Message: github.Ptr("Delete example file"), - HTMLURL: github.Ptr("https://github.com/owner/repo/commit/jkl012"), + SHA: new("jkl012"), + Message: new("Delete example file"), + HTMLURL: new("https://github.com/owner/repo/commit/jkl012"), } tests := []struct { @@ -4321,9 +4321,9 @@ func Test_DeleteFile(t *testing.T) { "force": false, }).andThen( mockResponse(t, http.StatusOK, &github.Reference{ - Ref: github.Ptr("refs/heads/main"), + Ref: new("refs/heads/main"), Object: &github.GitObject{ - SHA: github.Ptr("jkl012"), + SHA: new("jkl012"), }, }), ), @@ -4422,22 +4422,22 @@ func Test_ListTags(t *testing.T) { // Setup mock tags for success case mockTags := []*github.RepositoryTag{ { - Name: github.Ptr("v1.0.0"), + Name: new("v1.0.0"), Commit: &github.Commit{ - SHA: github.Ptr("v1.0.0-tag-sha"), - URL: github.Ptr("https://api.github.com/repos/owner/repo/commits/abc123"), + SHA: new("v1.0.0-tag-sha"), + URL: new("https://api.github.com/repos/owner/repo/commits/abc123"), }, - ZipballURL: github.Ptr("https://github.com/owner/repo/zipball/v1.0.0"), - TarballURL: github.Ptr("https://github.com/owner/repo/tarball/v1.0.0"), + ZipballURL: new("https://github.com/owner/repo/zipball/v1.0.0"), + TarballURL: new("https://github.com/owner/repo/tarball/v1.0.0"), }, { - Name: github.Ptr("v0.9.0"), + Name: new("v0.9.0"), Commit: &github.Commit{ - SHA: github.Ptr("v0.9.0-tag-sha"), - URL: github.Ptr("https://api.github.com/repos/owner/repo/commits/def456"), + SHA: new("v0.9.0-tag-sha"), + URL: new("https://api.github.com/repos/owner/repo/commits/def456"), }, - ZipballURL: github.Ptr("https://github.com/owner/repo/zipball/v0.9.0"), - TarballURL: github.Ptr("https://github.com/owner/repo/tarball/v0.9.0"), + ZipballURL: new("https://github.com/owner/repo/zipball/v0.9.0"), + TarballURL: new("https://github.com/owner/repo/tarball/v0.9.0"), }, } @@ -4551,28 +4551,28 @@ func Test_GetTag(t *testing.T) { assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "tag"}) mockAnnotatedTagRef := &github.Reference{ - Ref: github.Ptr("refs/tags/v1.0.0"), + Ref: new("refs/tags/v1.0.0"), Object: &github.GitObject{ - Type: github.Ptr("tag"), - SHA: github.Ptr("v1.0.0-tag-sha"), + Type: new("tag"), + SHA: new("v1.0.0-tag-sha"), }, } mockLightweightTagRef := &github.Reference{ - Ref: github.Ptr("refs/tags/v1.0.1"), + Ref: new("refs/tags/v1.0.1"), Object: &github.GitObject{ - Type: github.Ptr("commit"), - SHA: github.Ptr("abc123"), + Type: new("commit"), + SHA: new("abc123"), }, } mockTagObj := &github.Tag{ - SHA: github.Ptr("v1.0.0-tag-sha"), - Tag: github.Ptr("v1.0.0"), - Message: github.Ptr("Release v1.0.0"), + SHA: new("v1.0.0-tag-sha"), + Tag: new("v1.0.0"), + Message: new("Release v1.0.0"), Object: &github.GitObject{ - Type: github.Ptr("commit"), - SHA: github.Ptr("abc123"), + Type: new("commit"), + SHA: new("abc123"), }, } @@ -4756,12 +4756,12 @@ func Test_ListReleases(t *testing.T) { { ID: 1, TagName: "v1.0.0", - Name: github.Ptr("First Release"), + Name: new("First Release"), }, { ID: 2, TagName: "v0.9.0", - Name: github.Ptr("Beta Release"), + Name: new("Beta Release"), }, } @@ -4854,8 +4854,8 @@ func Test_GetLatestRelease(t *testing.T) { mockRelease := &github.RepositoryRelease{ ID: 1, TagName: "v1.0.0", - Name: github.Ptr("can't \"quote\" AT&T\u200B"), - Body: github.Ptr("
Notes
\u200B"), + Name: new("can't \"quote\" AT&T\u200B"), + Body: new("
Notes
\u200B"), } tests := []struct { @@ -4947,12 +4947,12 @@ func Test_GetReleaseByTag(t *testing.T) { mockRelease := &github.RepositoryRelease{ ID: 1, TagName: "v1.0.0", - Name: github.Ptr("can't \"quote\" AT&T\u200B"), - Body: github.Ptr("
Notes
\u200B"), + Name: new("can't \"quote\" AT&T\u200B"), + Body: new("
Notes
\u200B"), Assets: []*github.ReleaseAsset{ { - ID: github.Ptr(int64(1)), - Name: github.Ptr("release-v1.0.0.tar.gz"), + ID: new(int64(1)), + Name: new("release-v1.0.0.tar.gz"), }, }, } @@ -5115,7 +5115,7 @@ func Test_GetReleaseByTag_IFC_FeatureFlag(t *testing.T) { return &github.RepositoryRelease{ ID: 1, TagName: "v1.0.0", - Name: github.Ptr("v1.0.0"), + Name: new("v1.0.0"), Draft: draft, } } @@ -5278,10 +5278,10 @@ func Test_filterPaths(t *testing.T) { { name: "file name", tree: []*github.TreeEntry{ - {Path: github.Ptr("folder/foo.txt"), Type: github.Ptr("blob")}, - {Path: github.Ptr("bar.txt"), Type: github.Ptr("blob")}, - {Path: github.Ptr("nested/folder/foo.txt"), Type: github.Ptr("blob")}, - {Path: github.Ptr("nested/folder/baz.txt"), Type: github.Ptr("blob")}, + {Path: new("folder/foo.txt"), Type: new("blob")}, + {Path: new("bar.txt"), Type: new("blob")}, + {Path: new("nested/folder/foo.txt"), Type: new("blob")}, + {Path: new("nested/folder/baz.txt"), Type: new("blob")}, }, path: "foo.txt", maxResults: -1, @@ -5290,10 +5290,10 @@ func Test_filterPaths(t *testing.T) { { name: "dir name", tree: []*github.TreeEntry{ - {Path: github.Ptr("folder"), Type: github.Ptr("tree")}, - {Path: github.Ptr("bar.txt"), Type: github.Ptr("blob")}, - {Path: github.Ptr("nested/folder"), Type: github.Ptr("tree")}, - {Path: github.Ptr("nested/folder/baz.txt"), Type: github.Ptr("blob")}, + {Path: new("folder"), Type: new("tree")}, + {Path: new("bar.txt"), Type: new("blob")}, + {Path: new("nested/folder"), Type: new("tree")}, + {Path: new("nested/folder/baz.txt"), Type: new("blob")}, }, path: "folder/", maxResults: -1, @@ -5302,8 +5302,8 @@ func Test_filterPaths(t *testing.T) { { name: "dir and file match", tree: []*github.TreeEntry{ - {Path: github.Ptr("name"), Type: github.Ptr("tree")}, - {Path: github.Ptr("name"), Type: github.Ptr("blob")}, + {Path: new("name"), Type: new("tree")}, + {Path: new("name"), Type: new("blob")}, }, path: "name", // No trailing slash can match both files and directories maxResults: -1, @@ -5312,8 +5312,8 @@ func Test_filterPaths(t *testing.T) { { name: "dir only match", tree: []*github.TreeEntry{ - {Path: github.Ptr("name"), Type: github.Ptr("tree")}, - {Path: github.Ptr("name"), Type: github.Ptr("blob")}, + {Path: new("name"), Type: new("tree")}, + {Path: new("name"), Type: new("blob")}, }, path: "name/", // Trialing slash ensures only directories are matched maxResults: -1, @@ -5322,9 +5322,9 @@ func Test_filterPaths(t *testing.T) { { name: "max results limit 2", tree: []*github.TreeEntry{ - {Path: github.Ptr("folder"), Type: github.Ptr("tree")}, - {Path: github.Ptr("nested/folder"), Type: github.Ptr("tree")}, - {Path: github.Ptr("nested/nested/folder"), Type: github.Ptr("tree")}, + {Path: new("folder"), Type: new("tree")}, + {Path: new("nested/folder"), Type: new("tree")}, + {Path: new("nested/nested/folder"), Type: new("tree")}, }, path: "folder/", maxResults: 2, @@ -5333,9 +5333,9 @@ func Test_filterPaths(t *testing.T) { { name: "max results limit 1", tree: []*github.TreeEntry{ - {Path: github.Ptr("folder"), Type: github.Ptr("tree")}, - {Path: github.Ptr("nested/folder"), Type: github.Ptr("tree")}, - {Path: github.Ptr("nested/nested/folder"), Type: github.Ptr("tree")}, + {Path: new("folder"), Type: new("tree")}, + {Path: new("nested/folder"), Type: new("tree")}, + {Path: new("nested/nested/folder"), Type: new("tree")}, }, path: "folder/", maxResults: 1, @@ -5344,9 +5344,9 @@ func Test_filterPaths(t *testing.T) { { name: "max results limit 0", tree: []*github.TreeEntry{ - {Path: github.Ptr("folder"), Type: github.Ptr("tree")}, - {Path: github.Ptr("nested/folder"), Type: github.Ptr("tree")}, - {Path: github.Ptr("nested/nested/folder"), Type: github.Ptr("tree")}, + {Path: new("folder"), Type: new("tree")}, + {Path: new("nested/folder"), Type: new("tree")}, + {Path: new("nested/nested/folder"), Type: new("tree")}, }, path: "folder/", maxResults: 0, @@ -5648,39 +5648,39 @@ func Test_ListStarredRepositories(t *testing.T) { { StarredAt: &github.Timestamp{Time: starredAt}, Repository: &github.Repository{ - ID: github.Ptr(int64(12345)), - Name: github.Ptr("awesome-repo"), - FullName: github.Ptr("owner/awesome-repo"), - Description: github.Ptr("An awesome repository"), - HTMLURL: github.Ptr("https://github.com/owner/awesome-repo"), - Language: github.Ptr("Go"), - StargazersCount: github.Ptr(100), - ForksCount: github.Ptr(25), - OpenIssuesCount: github.Ptr(5), + ID: new(int64(12345)), + Name: new("awesome-repo"), + FullName: new("owner/awesome-repo"), + Description: new("An awesome repository"), + HTMLURL: new("https://github.com/owner/awesome-repo"), + Language: new("Go"), + StargazersCount: new(100), + ForksCount: new(25), + OpenIssuesCount: new(5), UpdatedAt: &github.Timestamp{Time: updatedAt}, - Private: github.Ptr(false), - Fork: github.Ptr(false), - Archived: github.Ptr(false), - DefaultBranch: github.Ptr("main"), + Private: new(false), + Fork: new(false), + Archived: new(false), + DefaultBranch: new("main"), }, }, { StarredAt: &github.Timestamp{Time: starredAt.Add(-12 * time.Hour)}, Repository: &github.Repository{ - ID: github.Ptr(int64(67890)), - Name: github.Ptr("cool-project"), - FullName: github.Ptr("user/cool-project"), - Description: github.Ptr("A very cool project"), - HTMLURL: github.Ptr("https://github.com/user/cool-project"), - Language: github.Ptr("Python"), - StargazersCount: github.Ptr(500), - ForksCount: github.Ptr(75), - OpenIssuesCount: github.Ptr(10), + ID: new(int64(67890)), + Name: new("cool-project"), + FullName: new("user/cool-project"), + Description: new("A very cool project"), + HTMLURL: new("https://github.com/user/cool-project"), + Language: new("Python"), + StargazersCount: new(500), + ForksCount: new(75), + OpenIssuesCount: new(10), UpdatedAt: &github.Timestamp{Time: updatedAt.Add(-1 * time.Hour)}, - Private: github.Ptr(false), - Fork: github.Ptr(true), - Archived: github.Ptr(false), - DefaultBranch: github.Ptr("master"), + Private: new(false), + Fork: new(true), + Archived: new(false), + DefaultBranch: new("master"), }, }, } @@ -6700,14 +6700,14 @@ func Test_ListRepositoryCollaborators(t *testing.T) { mockCollaborators := []*github.User{ { - Login: github.Ptr("user1"), - ID: github.Ptr(int64(101)), - RoleName: github.Ptr("admin"), + Login: new("user1"), + ID: new(int64(101)), + RoleName: new("admin"), }, { - Login: github.Ptr("user2"), - ID: github.Ptr(int64(102)), - RoleName: github.Ptr("write"), + Login: new("user2"), + ID: new(int64(102)), + RoleName: new("write"), }, } diff --git a/pkg/github/repository_resource.go b/pkg/github/repository_resource.go index 323cdf45d2..d6f82294f8 100644 --- a/pkg/github/repository_resource.go +++ b/pkg/github/repository_resource.go @@ -15,7 +15,7 @@ import ( "github.com/github/github-mcp-server/pkg/octicons" "github.com/github/github-mcp-server/pkg/raw" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/yosida95/uritemplate/v3" ) diff --git a/pkg/github/repository_resource_completions.go b/pkg/github/repository_resource_completions.go index 2c05d0531e..deb4b738dc 100644 --- a/pkg/github/repository_resource_completions.go +++ b/pkg/github/repository_resource_completions.go @@ -6,7 +6,7 @@ import ( "fmt" "strings" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/github/repository_resource_completions_test.go b/pkg/github/repository_resource_completions_test.go index 22c083b466..7d20d1e1db 100644 --- a/pkg/github/repository_resource_completions_test.go +++ b/pkg/github/repository_resource_completions_test.go @@ -6,7 +6,7 @@ import ( "fmt" "testing" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/github/rulesets.go b/pkg/github/rulesets.go index ac05282aaa..2e856b5dd7 100644 --- a/pkg/github/rulesets.go +++ b/pkg/github/rulesets.go @@ -14,7 +14,7 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -403,7 +403,7 @@ func GetRepositoryRulesForBranch(ctx context.Context, client *github.Client, own PerPage: pagination.PerPage, } - branchRules, resp, err := client.Repositories.ListRulesForBranch(ctx, owner, repo, url.PathEscape(branch), opts) + branchRules, resp, err := client.Repositories.ListRulesForBranch(ctx, owner, repo, branch, opts) if resp != nil { defer func() { _ = resp.Body.Close() }() } diff --git a/pkg/github/rulesets_test.go b/pkg/github/rulesets_test.go index 37de10f112..659efc671f 100644 --- a/pkg/github/rulesets_test.go +++ b/pkg/github/rulesets_test.go @@ -9,7 +9,7 @@ import ( "net/url" "testing" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/github/sanitize_coverage_test.go b/pkg/github/sanitize_coverage_test.go index 20a175b07a..f8f612cfe2 100644 --- a/pkg/github/sanitize_coverage_test.go +++ b/pkg/github/sanitize_coverage_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -30,7 +30,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { name: "issue title (REST)", got: func() string { return convertToMinimalIssue(&github.Issue{ - Title: github.Ptr(maliciousText), + Title: new(maliciousText), }).Title }, }, @@ -39,7 +39,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { content: true, got: func() string { return convertToMinimalIssue(&github.Issue{ - Body: github.Ptr(maliciousText), + Body: new(maliciousText), }).Body }, }, @@ -48,7 +48,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { content: true, got: func() string { return convertToMinimalIssueComment(&github.IssueComment{ - Body: github.Ptr(maliciousText), + Body: new(maliciousText), }).Body }, }, @@ -56,7 +56,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { name: "pull request title", got: func() string { return convertToMinimalPullRequest(&github.PullRequest{ - Title: github.Ptr(maliciousText), + Title: new(maliciousText), }).Title }, }, @@ -65,7 +65,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { content: true, got: func() string { return convertToMinimalPullRequest(&github.PullRequest{ - Body: github.Ptr(maliciousText), + Body: new(maliciousText), }).Body }, }, @@ -74,7 +74,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { content: true, got: func() string { return convertToMinimalPullRequestReview(&github.PullRequestReview{ - Body: github.Ptr(maliciousText), + Body: new(maliciousText), }).Body }, }, @@ -92,7 +92,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { name: "release name", got: func() string { return convertToMinimalRelease(&github.RepositoryRelease{ - Name: github.Ptr(maliciousText), + Name: new(maliciousText), }).Name }, }, @@ -101,7 +101,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { content: true, got: func() string { return convertToMinimalRelease(&github.RepositoryRelease{ - Body: github.Ptr(maliciousText), + Body: new(maliciousText), }).Body }, }, @@ -110,7 +110,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { content: true, got: func() string { commit := convertToMinimalCommit(&github.RepositoryCommit{ - Commit: &github.Commit{Message: github.Ptr(maliciousText)}, + Commit: &github.Commit{Message: new(maliciousText)}, }, commitDetailNone) require.NotNil(t, commit.Commit) return commit.Commit.Message @@ -121,7 +121,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { content: true, got: func() string { item := convertCommitResultToMinimalCommit(&github.CommitResult{ - Commit: &github.Commit{Message: github.Ptr(maliciousText)}, + Commit: &github.Commit{Message: new(maliciousText)}, }) require.NotNil(t, item.Commit) return item.Commit.Message @@ -132,7 +132,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { content: true, got: func() string { commits := convertToMinimalPullRequestCommits([]*github.RepositoryCommit{ - {Commit: &github.Commit{Message: github.Ptr(maliciousText)}}, + {Commit: &github.Commit{Message: new(maliciousText)}}, }) require.Len(t, commits, 1) return commits[0].Message @@ -143,7 +143,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { content: true, got: func() string { resp := convertToMinimalFileContentResponse(&github.RepositoryContentResponse{ - Commit: github.Commit{Message: github.Ptr(maliciousText)}, + Commit: github.Commit{Message: new(maliciousText)}, }) require.NotNil(t, resp.Commit) return resp.Commit.Message @@ -154,7 +154,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { content: true, got: func() string { run := convertToMinimalWorkflowRun(&github.WorkflowRun{ - HeadCommit: &github.HeadCommit{Message: github.Ptr(maliciousText)}, + HeadCommit: &github.HeadCommit{Message: new(maliciousText)}, }) require.NotNil(t, run.HeadCommit) return run.HeadCommit.Message @@ -164,7 +164,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { name: "project item content title (issue)", got: func() string { return convertIssueToMinimalProjectItemContent(&github.Issue{ - Title: github.Ptr(maliciousText), + Title: new(maliciousText), }).Title }, }, @@ -172,7 +172,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { name: "project item content title (pull request)", got: func() string { return convertPullRequestToMinimalProjectItemContent(&github.PullRequest{ - Title: github.Ptr(maliciousText), + Title: new(maliciousText), }).Title }, }, @@ -180,7 +180,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { name: "project item content title (draft issue)", got: func() string { return convertDraftIssueToMinimalProjectItemContent(&github.ProjectV2DraftIssue{ - Title: github.Ptr(maliciousText), + Title: new(maliciousText), }).Title }, }, @@ -188,7 +188,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { name: "project pull request ref title (from *github.PullRequest)", got: func() string { return minimalProjectPullRequestRefFromPullRequest(&github.PullRequest{ - Title: github.Ptr(maliciousText), + Title: new(maliciousText), }).Title }, }, @@ -226,7 +226,7 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { name: "issue dependency ref title (issue_dependency_read / issue_dependency_write)", got: func() string { return issueToDependencyRef(&github.Issue{ - Title: github.Ptr(maliciousText), + Title: new(maliciousText), }).Title }, }, @@ -249,8 +249,8 @@ func Test_MinimalConverters_SanitizeUserAuthoredText(t *testing.T) { func Test_SearchIssueResult_SanitizesTitleAndBody(t *testing.T) { result := SearchIssueResult{ Issue: &github.Issue{ - Title: github.Ptr(maliciousText), - Body: github.Ptr(maliciousText), + Title: new(maliciousText), + Body: new(maliciousText), }, } @@ -283,8 +283,8 @@ func Test_SanitizeIssueTitleAndBody(t *testing.T) { t.Run("sanitizes in place", func(t *testing.T) { issue := &github.Issue{ - Title: github.Ptr(maliciousText), - Body: github.Ptr(maliciousText), + Title: new(maliciousText), + Body: new(maliciousText), } sanitizeIssueTitleAndBody(issue) require.NotNil(t, issue.Title) @@ -302,7 +302,7 @@ func Test_MinimalConverters_PreserveCodeFidelity(t *testing.T) { t.Run("commit file patch (get_commit)", func(t *testing.T) { commit := convertToMinimalCommit(&github.RepositoryCommit{ - Files: []*github.CommitFile{{Filename: github.Ptr("a.go"), Patch: github.Ptr(patch)}}, + Files: []*github.CommitFile{{Filename: new("a.go"), Patch: new(patch)}}, }, commitDetailFullPatch) require.Len(t, commit.Files, 1) assert.Equal(t, patch, commit.Files[0].Patch) @@ -310,7 +310,7 @@ func Test_MinimalConverters_PreserveCodeFidelity(t *testing.T) { t.Run("pull request file patch (get_pull_request_files)", func(t *testing.T) { files := convertToMinimalPRFiles([]*github.CommitFile{ - {Filename: github.Ptr("a.go"), Patch: github.Ptr(patch)}, + {Filename: new("a.go"), Patch: new(patch)}, }) require.Len(t, files, 1) assert.Equal(t, patch, files[0].Patch) diff --git a/pkg/github/search.go b/pkg/github/search.go index 715ca83aed..18c4d466db 100644 --- a/pkg/github/search.go +++ b/pkg/github/search.go @@ -13,7 +13,7 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/github/search_test.go b/pkg/github/search_test.go index 52e70b639c..f372de1bd7 100644 --- a/pkg/github/search_test.go +++ b/pkg/github/search_test.go @@ -9,7 +9,7 @@ import ( "github.com/github/github-mcp-server/internal/toolsnaps" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -35,24 +35,24 @@ func Test_SearchRepositories(t *testing.T) { // Setup mock search results mockSearchResult := &github.RepositoriesSearchResult{ - Total: github.Ptr(2), - IncompleteResults: github.Ptr(false), + Total: new(2), + IncompleteResults: new(false), Repositories: []*github.Repository{ { - ID: github.Ptr(int64(12345)), - Name: github.Ptr("repo-1"), - FullName: github.Ptr("owner/repo-1"), - HTMLURL: github.Ptr("https://github.com/owner/repo-1"), - Description: github.Ptr("Test repository 1"), - StargazersCount: github.Ptr(100), + ID: new(int64(12345)), + Name: new("repo-1"), + FullName: new("owner/repo-1"), + HTMLURL: new("https://github.com/owner/repo-1"), + Description: new("Test repository 1"), + StargazersCount: new(100), }, { - ID: github.Ptr(int64(67890)), - Name: github.Ptr("repo-2"), - FullName: github.Ptr("owner/repo-2"), - HTMLURL: github.Ptr("https://github.com/owner/repo-2"), - Description: github.Ptr("Test repository 2"), - StargazersCount: github.Ptr(50), + ID: new(int64(67890)), + Name: new("repo-2"), + FullName: new("owner/repo-2"), + HTMLURL: new("https://github.com/owner/repo-2"), + Description: new("Test repository 2"), + StargazersCount: new(50), }, }, } @@ -181,18 +181,18 @@ func Test_SearchRepositories_IFC_InsidersMode(t *testing.T) { makeRepo := func(r repoFixture) *github.Repository { return &github.Repository{ - ID: github.Ptr(int64(1)), - Name: github.Ptr(r.name), - FullName: github.Ptr(r.owner + "/" + r.name), - Private: github.Ptr(r.isPrivate), - Owner: &github.User{Login: github.Ptr(r.owner)}, + ID: new(int64(1)), + Name: new(r.name), + FullName: new(r.owner + "/" + r.name), + Private: new(r.isPrivate), + Owner: &github.User{Login: new(r.owner)}, } } makeMockClient := func(repos []repoFixture) *http.Client { searchResult := &github.RepositoriesSearchResult{ - Total: github.Ptr(len(repos)), - IncompleteResults: github.Ptr(false), + Total: new(len(repos)), + IncompleteResults: new(false), } for _, r := range repos { searchResult.Repositories = append(searchResult.Repositories, makeRepo(r)) @@ -280,16 +280,16 @@ func Test_SearchRepositories_IFC_InsidersMode(t *testing.T) { func Test_SearchRepositories_FullOutput(t *testing.T) { mockSearchResult := &github.RepositoriesSearchResult{ - Total: github.Ptr(1), - IncompleteResults: github.Ptr(false), + Total: new(1), + IncompleteResults: new(false), Repositories: []*github.Repository{ { - ID: github.Ptr(int64(12345)), - Name: github.Ptr("test-repo"), - FullName: github.Ptr("owner/test-repo"), - HTMLURL: github.Ptr("https://github.com/owner/test-repo"), - Description: github.Ptr("Test repository"), - StargazersCount: github.Ptr(100), + ID: new(int64(12345)), + Name: new("test-repo"), + FullName: new("owner/test-repo"), + HTMLURL: new("https://github.com/owner/test-repo"), + Description: new("Test repository"), + StargazersCount: new(100), }, }, } @@ -359,30 +359,30 @@ func Test_SearchCode(t *testing.T) { // Setup mock search results mockSearchResult := &github.CodeSearchResult{ - Total: github.Ptr(2), - IncompleteResults: github.Ptr(false), + Total: new(2), + IncompleteResults: new(false), CodeResults: []*github.CodeResult{ { - Name: github.Ptr("file1.go"), - Path: github.Ptr("path/to/file1.go"), - SHA: github.Ptr("abc123def456"), + Name: new("file1.go"), + Path: new("path/to/file1.go"), + SHA: new("abc123def456"), Repository: &github.Repository{ - Name: github.Ptr("repo"), - FullName: github.Ptr("owner/repo"), + Name: new("repo"), + FullName: new("owner/repo"), }, TextMatches: []*github.TextMatch{ { - Fragment: github.Ptr("func main() { fmt.Println(\"hello\") }"), + Fragment: new("func main() { fmt.Println(\"hello\") }"), }, }, }, { - Name: github.Ptr("file2.go"), - Path: github.Ptr("path/to/file2.go"), - SHA: github.Ptr("def456abc123"), + Name: new("file2.go"), + Path: new("path/to/file2.go"), + SHA: new("def456abc123"), Repository: &github.Repository{ - Name: github.Ptr("repo"), - FullName: github.Ptr("owner/repo"), + Name: new("repo"), + FullName: new("owner/repo"), }, }, }, @@ -512,19 +512,19 @@ func Test_SearchCode(t *testing.T) { func Test_SearchCode_FieldFiltering(t *testing.T) { mockSearchResult := &github.CodeSearchResult{ - Total: github.Ptr(1), - IncompleteResults: github.Ptr(false), + Total: new(1), + IncompleteResults: new(false), CodeResults: []*github.CodeResult{ { - Name: github.Ptr("file1.go"), - Path: github.Ptr("path/to/file1.go"), - SHA: github.Ptr("abc123def456"), + Name: new("file1.go"), + Path: new("path/to/file1.go"), + SHA: new("abc123def456"), Repository: &github.Repository{ - Name: github.Ptr("repo"), - FullName: github.Ptr("owner/repo"), + Name: new("repo"), + FullName: new("owner/repo"), }, TextMatches: []*github.TextMatch{ - {Fragment: github.Ptr("func main() {}")}, + {Fragment: new("func main() {}")}, }, }, }, @@ -569,19 +569,19 @@ func Test_SearchCode_FieldFiltering(t *testing.T) { func Test_SearchCode_FieldsTelemetry(t *testing.T) { mockSearchResult := &github.CodeSearchResult{ - Total: github.Ptr(1), - IncompleteResults: github.Ptr(false), + Total: new(1), + IncompleteResults: new(false), CodeResults: []*github.CodeResult{ { - Name: github.Ptr("file1.go"), - Path: github.Ptr("path/to/file1.go"), - SHA: github.Ptr("abc123def456"), + Name: new("file1.go"), + Path: new("path/to/file1.go"), + SHA: new("abc123def456"), Repository: &github.Repository{ - Name: github.Ptr("repo"), - FullName: github.Ptr("owner/repo"), + Name: new("repo"), + FullName: new("owner/repo"), }, TextMatches: []*github.TextMatch{ - {Fragment: github.Ptr("func main() {}")}, + {Fragment: new("func main() {}")}, }, }, }, @@ -656,21 +656,21 @@ func Test_SearchUsers(t *testing.T) { // Setup mock search results mockSearchResult := &github.UsersSearchResult{ - Total: github.Ptr(2), - IncompleteResults: github.Ptr(false), + Total: new(2), + IncompleteResults: new(false), Users: []*github.User{ { - Login: github.Ptr("user1"), - ID: github.Ptr(int64(1001)), - HTMLURL: github.Ptr("https://github.com/user1"), - AvatarURL: github.Ptr("https://avatars.githubusercontent.com/u/1001"), + Login: new("user1"), + ID: new(int64(1001)), + HTMLURL: new("https://github.com/user1"), + AvatarURL: new("https://avatars.githubusercontent.com/u/1001"), }, { - Login: github.Ptr("user2"), - ID: github.Ptr(int64(1002)), - HTMLURL: github.Ptr("https://github.com/user2"), - AvatarURL: github.Ptr("https://avatars.githubusercontent.com/u/1002"), - Type: github.Ptr("User"), + Login: new("user2"), + ID: new(int64(1002)), + HTMLURL: new("https://github.com/user2"), + AvatarURL: new("https://avatars.githubusercontent.com/u/1002"), + Type: new("User"), }, }, } @@ -843,20 +843,20 @@ func Test_SearchOrgs(t *testing.T) { // Setup mock search results mockSearchResult := &github.UsersSearchResult{ - Total: github.Ptr(int(2)), - IncompleteResults: github.Ptr(false), + Total: new(int(2)), + IncompleteResults: new(false), Users: []*github.User{ { - Login: github.Ptr("org-1"), - ID: github.Ptr(int64(111)), - HTMLURL: github.Ptr("https://github.com/org-1"), - AvatarURL: github.Ptr("https://avatars.githubusercontent.com/u/111?v=4"), + Login: new("org-1"), + ID: new(int64(111)), + HTMLURL: new("https://github.com/org-1"), + AvatarURL: new("https://avatars.githubusercontent.com/u/111?v=4"), }, { - Login: github.Ptr("org-2"), - ID: github.Ptr(int64(222)), - HTMLURL: github.Ptr("https://github.com/org-2"), - AvatarURL: github.Ptr("https://avatars.githubusercontent.com/u/222?v=4"), + Login: new("org-2"), + ID: new(int64(222)), + HTMLURL: new("https://github.com/org-2"), + AvatarURL: new("https://avatars.githubusercontent.com/u/222?v=4"), }, }, } @@ -1001,41 +1001,41 @@ func Test_SearchCommits(t *testing.T) { now := time.Now().Truncate(time.Second) mockSearchResult := &github.CommitsSearchResult{ - Total: github.Ptr(2), - IncompleteResults: github.Ptr(false), + Total: new(2), + IncompleteResults: new(false), Commits: []*github.CommitResult{ { - SHA: github.Ptr("abc123commit"), - HTMLURL: github.Ptr("https://github.com/owner/repo/commit/abc123commit"), + SHA: new("abc123commit"), + HTMLURL: new("https://github.com/owner/repo/commit/abc123commit"), Commit: &github.Commit{ - Message: github.Ptr("Initial commit"), + Message: new("Initial commit"), Author: &github.CommitAuthor{ - Name: github.Ptr("Author Name"), - Email: github.Ptr("author@example.com"), + Name: new("Author Name"), + Email: new("author@example.com"), Date: &github.Timestamp{Time: now}, }, }, Author: &github.User{ - Login: github.Ptr("author"), - ID: github.Ptr(int64(1)), - HTMLURL: github.Ptr("https://github.com/author"), + Login: new("author"), + ID: new(int64(1)), + HTMLURL: new("https://github.com/author"), }, Repository: &github.Repository{ - FullName: github.Ptr("owner/repo"), - HTMLURL: github.Ptr("https://github.com/owner/repo"), - Private: github.Ptr(false), + FullName: new("owner/repo"), + HTMLURL: new("https://github.com/owner/repo"), + Private: new(false), }, }, { // Commit with no resolved GitHub user for author or committer // (common when the commit email isn't linked to an account). - SHA: github.Ptr("def456commit"), - HTMLURL: github.Ptr("https://github.com/owner/repo/commit/def456commit"), + SHA: new("def456commit"), + HTMLURL: new("https://github.com/owner/repo/commit/def456commit"), Commit: &github.Commit{ - Message: github.Ptr("Unlinked author"), + Message: new("Unlinked author"), }, Repository: &github.Repository{ - FullName: github.Ptr("owner/repo"), + FullName: new("owner/repo"), }, }, }, diff --git a/pkg/github/search_utils.go b/pkg/github/search_utils.go index 52d735ca0b..47b8aa26bd 100644 --- a/pkg/github/search_utils.go +++ b/pkg/github/search_utils.go @@ -11,7 +11,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -135,7 +135,7 @@ func prepareSearchArgs(args map[string]any, targetType string, mode searchMode) // field.: qualifiers require the advanced search API. if strings.Contains(query, "field.") { - opts.AdvancedSearch = github.Ptr(true) + opts.AdvancedSearch = new(true) } // Lexical is the API default, so it leaves search_type unset. diff --git a/pkg/github/secret_scanning.go b/pkg/github/secret_scanning.go index 1991d5d2be..8f6659f047 100644 --- a/pkg/github/secret_scanning.go +++ b/pkg/github/secret_scanning.go @@ -13,7 +13,7 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -85,7 +85,7 @@ func GetSecretScanningAlert(t translations.TranslationHelperFunc) inventory.Serv return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get alert", resp, body), nil, nil } - r, err := json.Marshal(alert) + r, err := json.Marshal(alert) //nolint:gosec // G117: This security_events-scoped tool intentionally returns the alert's secret; the result is labeled private-untrusted. if err != nil { return nil, nil, fmt.Errorf("failed to marshal alert: %w", err) } @@ -200,7 +200,7 @@ func ListSecretScanningAlerts(t translations.TranslationHelperFunc) inventory.Se return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to list alerts", resp, body), nil, nil } - r, err := json.Marshal(alerts) + r, err := json.Marshal(alerts) //nolint:gosec // G117: This security_events-scoped tool intentionally returns alert secrets; the result is labeled private-untrusted. if err != nil { return nil, nil, fmt.Errorf("failed to marshal alerts: %w", err) } diff --git a/pkg/github/secret_scanning_test.go b/pkg/github/secret_scanning_test.go index 603f1e9eb2..6018962d6e 100644 --- a/pkg/github/secret_scanning_test.go +++ b/pkg/github/secret_scanning_test.go @@ -8,7 +8,7 @@ import ( "github.com/github/github-mcp-server/internal/toolsnaps" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -32,9 +32,9 @@ func Test_GetSecretScanningAlert(t *testing.T) { // Setup mock alert for success case mockAlert := &github.SecretScanningAlert{ - Number: github.Ptr(42), - State: github.Ptr("open"), - HTMLURL: github.Ptr("https://github.com/owner/private-repo/security/secret-scanning/42"), + Number: new(42), + State: new("open"), + HTMLURL: new("https://github.com/owner/private-repo/security/secret-scanning/42"), } tests := []struct { @@ -139,18 +139,18 @@ func Test_ListSecretScanningAlerts(t *testing.T) { // Setup mock alerts for success case resolvedAlert := github.SecretScanningAlert{ - Number: github.Ptr(2), - HTMLURL: github.Ptr("https://github.com/owner/private-repo/security/secret-scanning/2"), - State: github.Ptr("resolved"), - Resolution: github.Ptr("false_positive"), - SecretType: github.Ptr("adafruit_io_key"), + Number: new(2), + HTMLURL: new("https://github.com/owner/private-repo/security/secret-scanning/2"), + State: new("resolved"), + Resolution: new("false_positive"), + SecretType: new("adafruit_io_key"), } openAlert := github.SecretScanningAlert{ - Number: github.Ptr(2), - HTMLURL: github.Ptr("https://github.com/owner/private-repo/security/secret-scanning/3"), - State: github.Ptr("open"), - Resolution: github.Ptr("false_positive"), - SecretType: github.Ptr("adafruit_io_key"), + Number: new(2), + HTMLURL: new("https://github.com/owner/private-repo/security/secret-scanning/3"), + State: new("open"), + Resolution: new("false_positive"), + SecretType: new("adafruit_io_key"), } tests := []struct { diff --git a/pkg/github/security_advisories.go b/pkg/github/security_advisories.go index 63a9a43b0e..633cd2a8a4 100644 --- a/pkg/github/security_advisories.go +++ b/pkg/github/security_advisories.go @@ -13,7 +13,7 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/github/security_advisories_test.go b/pkg/github/security_advisories_test.go index 8c212d5843..27dcf7ec0a 100644 --- a/pkg/github/security_advisories_test.go +++ b/pkg/github/security_advisories_test.go @@ -8,7 +8,7 @@ import ( "github.com/github/github-mcp-server/internal/toolsnaps" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/assert" @@ -33,10 +33,10 @@ func Test_ListGlobalSecurityAdvisories(t *testing.T) { // Setup mock advisory for success case mockAdvisory := &github.GlobalSecurityAdvisory{ SecurityAdvisory: github.SecurityAdvisory{ - GHSAID: github.Ptr("GHSA-xxxx-xxxx-xxxx"), - Summary: github.Ptr("Test advisory"), - Description: github.Ptr("This is a test advisory."), - Severity: github.Ptr("high"), + GHSAID: new("GHSA-xxxx-xxxx-xxxx"), + Summary: new("Test advisory"), + Description: new("This is a test advisory."), + Severity: new("high"), }, } @@ -146,10 +146,10 @@ func Test_GetGlobalSecurityAdvisory(t *testing.T) { // Setup mock advisory for success case mockAdvisory := &github.GlobalSecurityAdvisory{ SecurityAdvisory: github.SecurityAdvisory{ - GHSAID: github.Ptr("GHSA-xxxx-xxxx-xxxx"), - Summary: github.Ptr("Test advisory"), - Description: github.Ptr("This is a test advisory."), - Severity: github.Ptr("high"), + GHSAID: new("GHSA-xxxx-xxxx-xxxx"), + Summary: new("Test advisory"), + Description: new("This is a test advisory."), + Severity: new("high"), }, } @@ -256,16 +256,16 @@ func Test_ListRepositorySecurityAdvisories(t *testing.T) { // Setup mock advisories for success cases adv1 := &github.SecurityAdvisory{ - GHSAID: github.Ptr("GHSA-1111-1111-1111"), - Summary: github.Ptr("Repo advisory one"), - Description: github.Ptr("First repo advisory."), - Severity: github.Ptr("high"), + GHSAID: new("GHSA-1111-1111-1111"), + Summary: new("Repo advisory one"), + Description: new("First repo advisory."), + Severity: new("high"), } adv2 := &github.SecurityAdvisory{ - GHSAID: github.Ptr("GHSA-2222-2222-2222"), - Summary: github.Ptr("Repo advisory two"), - Description: github.Ptr("Second repo advisory."), - Severity: github.Ptr("medium"), + GHSAID: new("GHSA-2222-2222-2222"), + Summary: new("Repo advisory two"), + Description: new("Second repo advisory."), + Severity: new("medium"), } tests := []struct { @@ -384,14 +384,14 @@ func Test_ListRepositorySecurityAdvisories_IFC_FeatureFlag(t *testing.T) { toolDef := ListRepositorySecurityAdvisories(translations.NullTranslationHelper) publishedAdvisory := &github.SecurityAdvisory{ - GHSAID: github.Ptr("GHSA-1111-1111-1111"), - Summary: github.Ptr("Published advisory"), - State: github.Ptr("published"), + GHSAID: new("GHSA-1111-1111-1111"), + Summary: new("Published advisory"), + State: new("published"), } draftAdvisory := &github.SecurityAdvisory{ - GHSAID: github.Ptr("GHSA-2222-2222-2222"), - Summary: github.Ptr("Draft advisory"), - State: github.Ptr("draft"), + GHSAID: new("GHSA-2222-2222-2222"), + Summary: new("Draft advisory"), + State: new("draft"), } makeMockClient := func(isPrivate bool, advisories []*github.SecurityAdvisory) *http.Client { @@ -515,16 +515,16 @@ func Test_ListOrgRepositorySecurityAdvisories(t *testing.T) { assert.ElementsMatch(t, schema.Required, []string{"org"}) adv1 := &github.SecurityAdvisory{ - GHSAID: github.Ptr("GHSA-aaaa-bbbb-cccc"), - Summary: github.Ptr("Org repo advisory 1"), - Description: github.Ptr("First advisory"), - Severity: github.Ptr("low"), + GHSAID: new("GHSA-aaaa-bbbb-cccc"), + Summary: new("Org repo advisory 1"), + Description: new("First advisory"), + Severity: new("low"), } adv2 := &github.SecurityAdvisory{ - GHSAID: github.Ptr("GHSA-dddd-eeee-ffff"), - Summary: github.Ptr("Org repo advisory 2"), - Description: github.Ptr("Second advisory"), - Severity: github.Ptr("critical"), + GHSAID: new("GHSA-dddd-eeee-ffff"), + Summary: new("Org repo advisory 2"), + Description: new("Second advisory"), + Severity: new("critical"), } tests := []struct { diff --git a/pkg/github/server.go b/pkg/github/server.go index 6e9b5b7566..4ce1f965b1 100644 --- a/pkg/github/server.go +++ b/pkg/github/server.go @@ -125,10 +125,9 @@ func NewMCPServer(ctx context.Context, cfg *MCPServerConfig, deps ToolDependenci inv.RegisterAll(ctx, ghServer, deps, cfg.ToolHandlerMiddleware...) // Register MCP App UI resources whenever the embedded UI assets are - // available. The resources are static HTML and are only referenced by - // tools when the remote_mcp_ui_apps feature flag is enabled for the - // request (the inventory strips the _meta.ui block otherwise via - // stripMCPAppsMetadata), so registering them unconditionally is safe. + // available. The resources are static HTML referenced by tools' _meta.ui + // block (the inventory strips that block for clients that do not support + // MCP Apps), so registering them unconditionally is safe. // Registering here — rather than in the stdio bootstrap — ensures the // remote/HTTP server also serves them, fixing the "-32002 Resource not // found" error clients hit after the tool returns a ui:// URI. diff --git a/pkg/github/server_test.go b/pkg/github/server_test.go index bd8b9abd6b..f629ace35d 100644 --- a/pkg/github/server_test.go +++ b/pkg/github/server_test.go @@ -17,7 +17,7 @@ import ( "github.com/github/github-mcp-server/pkg/observability/metrics" "github.com/github/github-mcp-server/pkg/raw" "github.com/github/github-mcp-server/pkg/translations" - gogithub "github.com/google/go-github/v89/github" + gogithub "github.com/google/go-github/v92/github" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" @@ -123,7 +123,7 @@ func mockRESTPermissionServer(t *testing.T, defaultPerm string, overrides map[st } } resp := gogithub.RepositoryPermissionLevel{ - Permission: gogithub.Ptr(perm), + Permission: new(perm), } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(resp) diff --git a/pkg/github/tools.go b/pkg/github/tools.go index b91664a67f..e3177f668f 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -5,7 +5,7 @@ import ( "slices" "strings" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/shurcooL/githubv4" "github.com/github/github-mcp-server/pkg/inventory" @@ -376,6 +376,8 @@ func AllTools(t translations.TranslationHelperFunc, opts ...ToolOption) []invent GranularRemoveIssueReaction(t), GranularAddIssueCommentReaction(t), GranularRemoveIssueCommentReaction(t), + GranularHideIssueComment(t), + GranularUnhideIssueComment(t), // Granular pull request tools (feature-flagged, replace consolidated update_pull_request/pull_request_review_write) GranularUpdatePullRequestTitle(t), @@ -392,6 +394,10 @@ func AllTools(t translations.TranslationHelperFunc, opts ...ToolOption) []invent GranularUnresolveReviewThread(t), GranularAddPullRequestReviewCommentReaction(t), GranularRemovePullRequestReviewCommentReaction(t), + GranularHidePullRequestReviewComment(t), + GranularUnhidePullRequestReviewComment(t), + GranularHidePullRequestReview(t), + GranularUnhidePullRequestReview(t), }) } diff --git a/pkg/github/tools_validation_test.go b/pkg/github/tools_validation_test.go index 3eacc8e1c2..dc3e758dd3 100644 --- a/pkg/github/tools_validation_test.go +++ b/pkg/github/tools_validation_test.go @@ -234,12 +234,6 @@ func TestFeatureRulesOverlap(t *testing.T) { assert.False(t, featureDeclarationsOverlap([]inventory.ServerTool{enabled, disabled})) } -func TestMCPAppsFeatureFlagMatchesInventory(t *testing.T) { - inv, err := NewInventory(stubTranslation).Build() - require.NoError(t, err) - assert.Contains(t, inv.RequiredFeatures(), inventory.FeatureFlag(MCPAppsFeatureFlag)) -} - // TestNoDuplicateResourceNames ensures all resources have unique names func TestNoDuplicateResourceNames(t *testing.T) { resources := AllResources(stubTranslation) diff --git a/pkg/github/ui_capability.go b/pkg/github/ui_capability.go index 3de6a39b74..b4f5ddf281 100644 --- a/pkg/github/ui_capability.go +++ b/pkg/github/ui_capability.go @@ -62,16 +62,15 @@ func hasNonFormParams(args map[string]any, formParams map[string]struct{}) bool // shouldDeferToForm is the single source of truth for the show/defer decision // shared by the form-backed write tools (create_pull_request, // update_pull_request, issue_write). It reports whether a call should be handed -// off to its MCP App form instead of executing now: defer only when MCP Apps -// are enabled, form deferral has not been disabled, the client can render UI, +// off to its MCP App form instead of executing now: defer only when form +// deferral has not been disabled, the client can render UI, // the call is not itself a form submission, and every supplied parameter can // be represented by the form (formParams is the tool's form-parameter // allowlist). When it returns false the handler executes directly; the host may // still render the tool's view, which renders the result rather than an input // form. func shouldDeferToForm(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest, args map[string]any, formParams map[string]struct{}) bool { - return deps.IsFeatureEnabled(ctx, MCPAppsFeatureFlag) && - !deps.IsFeatureEnabled(ctx, MCPAppsDisableFormDeferralFeatureFlag) && + return !deps.IsFeatureEnabled(ctx, MCPAppsDisableFormDeferralFeatureFlag) && clientSupportsUI(ctx, req) && !uiSubmitted(args) && !hasNonFormParams(args, formParams) diff --git a/pkg/github/ui_capability_test.go b/pkg/github/ui_capability_test.go index 812d6fde89..7d3ee34c4a 100644 --- a/pkg/github/ui_capability_test.go +++ b/pkg/github/ui_capability_test.go @@ -100,27 +100,14 @@ func Test_shouldDeferToForm_featureFlags(t *testing.T) { want bool }{ { - name: "MCP Apps enabled defers to form", - enabledFlags: []inventory.FeatureFlag{MCPAppsFeatureFlag}, - want: true, + name: "defers to form by default", + want: true, }, { - name: "form deferral disabled executes directly", - enabledFlags: []inventory.FeatureFlag{ - MCPAppsFeatureFlag, - MCPAppsDisableFormDeferralFeatureFlag, - }, - want: false, - }, - { - name: "form deferral opt-out does not enable MCP Apps", + name: "form deferral disabled executes directly", enabledFlags: []inventory.FeatureFlag{MCPAppsDisableFormDeferralFeatureFlag}, want: false, }, - { - name: "MCP Apps disabled executes directly", - want: false, - }, } for _, tc := range tests { diff --git a/pkg/github/ui_tools.go b/pkg/github/ui_tools.go index b8d6cd31a5..a1a4179858 100644 --- a/pkg/github/ui_tools.go +++ b/pkg/github/ui_tools.go @@ -13,7 +13,7 @@ import ( "github.com/github/github-mcp-server/pkg/inventory" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" @@ -98,7 +98,6 @@ func UIGet(t translations.TranslationHelperFunc) inventory.ServerTool { return utils.NewToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil } }) - st.FeatureRule = featureEnabledRule(MCPAppsFeatureFlag) return st } diff --git a/pkg/github/ui_tools_test.go b/pkg/github/ui_tools_test.go index d400752bdf..4e6aba185f 100644 --- a/pkg/github/ui_tools_test.go +++ b/pkg/github/ui_tools_test.go @@ -12,9 +12,9 @@ import ( "github.com/github/github-mcp-server/internal/githubv4mock" "github.com/github/github-mcp-server/internal/toolsnaps" - "github.com/github/github-mcp-server/pkg/inventory" + ghcontext "github.com/github/github-mcp-server/pkg/context" "github.com/github/github-mcp-server/pkg/translations" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/google/jsonschema-go/jsonschema" "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" @@ -106,7 +106,7 @@ func Test_UIGet(t *testing.T) { assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "repo") assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"method", "owner"}) assert.True(t, tool.Annotations.ReadOnlyHint, "ui_get should be read-only") - assert.Equal(t, []inventory.FeatureFlag{MCPAppsFeatureFlag}, serverTool.FeatureRule.Features()) + assert.Empty(t, serverTool.FeatureRule.Features(), "ui_get should not be feature-gated") // ui_get must be app-only so the host hides it from the agent's tool list // while keeping it callable by the views (MCP Apps 2026-01-26 spec). @@ -116,34 +116,34 @@ func Test_UIGet(t *testing.T) { // Setup mock data mockAssignees := []*github.User{ - {Login: github.Ptr("user1"), AvatarURL: github.Ptr("https://avatars.githubusercontent.com/u/1")}, - {Login: github.Ptr("user2"), AvatarURL: github.Ptr("https://avatars.githubusercontent.com/u/2")}, + {Login: new("user1"), AvatarURL: new("https://avatars.githubusercontent.com/u/1")}, + {Login: new("user2"), AvatarURL: new("https://avatars.githubusercontent.com/u/2")}, } mockBranches := []*github.Branch{ - {Name: github.Ptr("main"), Protected: github.Ptr(true)}, - {Name: github.Ptr("feature"), Protected: github.Ptr(false)}, + {Name: new("main"), Protected: new(true)}, + {Name: new("feature"), Protected: new(false)}, } dueDate := time.Date(2026, 1, 31, 0, 0, 0, 0, time.UTC) mockMilestones := []*github.Milestone{ - {Number: github.Ptr(1), Title: github.Ptr("with due date"), DueOn: &github.Timestamp{Time: dueDate}}, - {Number: github.Ptr(2), Title: github.Ptr("no due date")}, + {Number: new(1), Title: new("with due date"), DueOn: &github.Timestamp{Time: dueDate}}, + {Number: new(2), Title: new("no due date")}, } mockIssueTypes := []*github.IssueType{ - {Name: github.Ptr("Bug")}, - {Name: github.Ptr("Feature")}, + {Name: new("Bug")}, + {Name: new("Feature")}, } mockReviewers := []*github.User{ - {Login: github.Ptr("octocat"), AvatarURL: github.Ptr("https://avatars.githubusercontent.com/u/583231")}, - {Login: github.Ptr("dependabot[bot]"), AvatarURL: github.Ptr("https://avatars.githubusercontent.com/in/29110")}, - {Login: github.Ptr("github-actions"), Type: github.Ptr("Bot")}, + {Login: new("octocat"), AvatarURL: new("https://avatars.githubusercontent.com/u/583231")}, + {Login: new("dependabot[bot]"), AvatarURL: new("https://avatars.githubusercontent.com/in/29110")}, + {Login: new("github-actions"), Type: new("Bot")}, } mockReviewerTeams := []*github.Team{ - {Slug: github.Ptr("docs"), Name: github.Ptr("Docs")}, + {Slug: new("docs"), Name: new("Docs")}, } tests := []struct { @@ -385,7 +385,7 @@ func Test_UIGet(t *testing.T) { { name: "branches pagination stops at the page cap", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ - "GET /repos/owner/repo/branches": alwaysNextPageHandler(t, []*github.Branch{{Name: github.Ptr("feature")}}), + "GET /repos/owner/repo/branches": alwaysNextPageHandler(t, []*github.Branch{{Name: new("feature")}}), }), requestArgs: map[string]any{ "method": "branches", @@ -427,7 +427,7 @@ func Test_UIGet(t *testing.T) { { name: "reviewers pagination stops at the page cap", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ - "GET /repos/owner/repo/collaborators": alwaysNextPageHandler(t, []*github.User{{Login: github.Ptr("octocat")}}), + "GET /repos/owner/repo/collaborators": alwaysNextPageHandler(t, []*github.User{{Login: new("octocat")}}), "GET /repos/owner/repo/teams": mockResponse(t, http.StatusOK, mockReviewerTeams), }), requestArgs: map[string]any{ @@ -583,3 +583,22 @@ func Test_marshalUIGetIssueFields_TrimsForUI(t *testing.T) { textField := fields[1].(map[string]any) assert.NotContains(t, textField, "options") } + +func Test_UIGet_OmittedForClientsWithoutUISupport(t *testing.T) { + inv, err := NewInventory(translations.NullTranslationHelper).WithToolsets([]string{"all"}).Build() + require.NoError(t, err) + + hasUIGet := func(ctx context.Context) bool { + for _, tool := range inv.ToolsForRegistration(ctx) { + if tool.Tool.Name == "ui_get" { + return true + } + } + return false + } + + assert.False(t, hasUIGet(ghcontext.WithUISupport(context.Background(), false)), + "app-only ui_get must not be exposed as a model-visible tool to non-UI clients") + assert.True(t, hasUIGet(ghcontext.WithUISupport(context.Background(), true))) + assert.True(t, hasUIGet(context.Background()), "ui_get is kept when UI support is unknown") +} diff --git a/pkg/http/handler_test.go b/pkg/http/handler_test.go index ea37ec2a6a..c9fa1de095 100644 --- a/pkg/http/handler_test.go +++ b/pkg/http/handler_test.go @@ -1299,62 +1299,36 @@ func TestSubscriptionsListenIsRejected(t *testing.T) { } } -// TestInsidersRoutePreservesUIMeta is a regression test for the bug where -// _meta.ui was stripped from tools/list responses on the HTTP /insiders route. -// -// Before the fix: -// - buildStaticInventory called Build() on a builder configured with the -// HTTP feature checker (which reads insiders mode from the request ctx). -// - Build() invoked checkFeatureFlag(context.Background()) — bg ctx has no -// insiders mode, so the FF reported MCP Apps off, and stripMCPAppsMetadata -// ran eagerly against the static tool slice at server startup. -// - Per-request inventory factories then served pre-stripped tools regardless -// of whether the request actually came in via /insiders. -// -// After the fix: -// - Build() no longer touches MCP Apps metadata. -// - RegisterTools applies the strip per-request, using the request context -// where the HTTP feature checker correctly observes insiders mode. -func TestInsidersRoutePreservesUIMeta(t *testing.T) { +// TestUIMetaPreservedByDefault verifies that _meta.ui is preserved in the +// registered tool surface on both the /insiders route and the default route: +// MCP Apps UI metadata is no longer gated behind a feature flag. +func TestUIMetaPreservedByDefault(t *testing.T) { const uiURI = "ui://test/widget" uiTool := mockTool("with_ui", "repos", true) uiTool.Tool.Meta = mcp.Meta{"ui": map[string]any{"resourceUri": uiURI}} - checker := createHTTPFeatureChecker(nil, false) - build := func() *inventory.Inventory { - inv, err := inventory.NewBuilder(). - SetTools([]inventory.ServerTool{uiTool}). - WithFeatureChecker(checker). - WithToolsets([]string{"all"}). - Build() - require.NoError(t, err) - return inv - } - - // Simulate a /insiders request: ctx has insiders mode set. - insidersCtx := ghcontext.WithInsidersMode(context.Background(), true) + inv, err := inventory.NewBuilder(). + SetTools([]inventory.ServerTool{uiTool}). + WithFeatureChecker(createHTTPFeatureChecker(nil, false)). + WithToolsets([]string{"all"}). + Build() + require.NoError(t, err) - // AvailableTools no longer strips _meta.ui (post-fix), regardless of ctx. - // The strip lives in RegisterTools, gated on the per-request FF check. - insidersTools := build().AvailableTools(insidersCtx) - plainTools := build().AvailableTools(context.Background()) - - // On the /insiders path, the FF check returns true → no strip → _meta preserved. - enabled, _ := checker(insidersCtx, "remote_mcp_ui_apps") - require.True(t, enabled, "FF should be on for /insiders ctx") - require.Len(t, insidersTools, 1) - require.NotNil(t, insidersTools[0].Tool.Meta, "_meta should be present on /insiders") - require.Equal(t, uiURI, insidersTools[0].Tool.Meta["ui"].(map[string]any)["resourceUri"]) - - // On the non-insiders path, RegisterTools strips _meta.ui. - plainEnabled, _ := checker(context.Background(), "remote_mcp_ui_apps") - require.False(t, plainEnabled, "FF should be off for non-insiders ctx") - require.Len(t, plainTools, 1) + for name, ctx := range map[string]context.Context{ + "insiders": ghcontext.WithInsidersMode(context.Background(), true), + "default": context.Background(), + } { + t.Run(name, func(t *testing.T) { + tools := inv.ToolsForRegistration(ctx) + require.Len(t, tools, 1) + require.NotNil(t, tools[0].Tool.Meta, "_meta should be present") + require.Equal(t, uiURI, tools[0].Tool.Meta["ui"].(map[string]any)["resourceUri"]) + }) + } } -// TestUIMetaStrippedWhenClientLacksCapability verifies that even on the -// /insiders path (where the feature flag is on), UI metadata is stripped from -// tools/list responses when the client did NOT advertise the +// TestUIMetaStrippedWhenClientLacksCapability verifies that UI metadata is +// stripped from tools/list responses when the client did NOT advertise the // io.modelcontextprotocol/ui extension capability. Per the 2026-01-26 MCP // Apps spec, servers SHOULD check client capabilities before exposing // UI-enabled tools. @@ -1387,10 +1361,9 @@ func TestUIMetaStrippedWhenClientLacksCapability(t *testing.T) { require.NotNil(t, preserved[0].Tool.Meta["ui"], "_meta.ui should be preserved when client advertises UI capability") require.Equal(t, uiURI, preserved[0].Tool.Meta["ui"].(map[string]any)["resourceUri"]) - // Unknown capability falls through to the FF gate (insiders ctx → kept). unknown := build().ToolsForRegistration(insidersCtx) require.Len(t, unknown, 1) - require.NotNil(t, unknown[0].Tool.Meta["ui"], "_meta.ui should be preserved when capability is unknown and FF is on") + require.NotNil(t, unknown[0].Tool.Meta["ui"], "_meta.ui should be preserved when capability is unknown") } // TestMaxRequestBodyBytes checks the effective limit and, critically, that it diff --git a/pkg/http/middleware/pat_scope_test.go b/pkg/http/middleware/pat_scope_test.go index 0607b8cf2b..5182a636fe 100644 --- a/pkg/http/middleware/pat_scope_test.go +++ b/pkg/http/middleware/pat_scope_test.go @@ -55,7 +55,7 @@ func TestWithPATScopes(t *testing.T) { }, { name: "fine-grained PAT skips scope fetching", - tokenInfo: &ghcontext.TokenInfo{ + tokenInfo: &ghcontext.TokenInfo{ //nolint:gosec // G101: Synthetic fine-grained PAT fixture verifies scope fetching is skipped; it cannot authenticate. Token: "github_pat_xxxxxxxxxxxxxxxxxxxxxxx", TokenType: utils.TokenTypeFineGrainedPersonalAccessToken, }, diff --git a/pkg/http/middleware/scope_challenge_test.go b/pkg/http/middleware/scope_challenge_test.go index 1c83ba600c..2beeecf49d 100644 --- a/pkg/http/middleware/scope_challenge_test.go +++ b/pkg/http/middleware/scope_challenge_test.go @@ -359,7 +359,7 @@ func scopeChallengeContext(ctx context.Context) context.Context { } func scopeChallengeContextWithScopes(ctx context.Context, activeScopes []string) context.Context { - ctx = ghcontext.WithTokenInfo(ctx, &ghcontext.TokenInfo{ + ctx = ghcontext.WithTokenInfo(ctx, &ghcontext.TokenInfo{ //nolint:gosec // G101: "oauth-token" is a synthetic context fixture, not an authentication credential. Token: "oauth-token", TokenType: utils.TokenTypeOAuthAccessToken, }) diff --git a/pkg/http/middleware/token_test.go b/pkg/http/middleware/token_test.go index fa8f0ee98e..a8373a1565 100644 --- a/pkg/http/middleware/token_test.go +++ b/pkg/http/middleware/token_test.go @@ -37,7 +37,7 @@ func TestExtractUserToken(t *testing.T) { expectWWWAuth: true, }, // Personal Access Token (classic) - ghp_ prefix - { + { //nolint:gosec // G101: Synthetic token fixture exercises classic PAT prefix detection; it cannot authenticate. name: "personal access token (classic) with Bearer prefix", authHeader: "Bearer ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", expectedStatusCode: http.StatusOK, @@ -45,7 +45,7 @@ func TestExtractUserToken(t *testing.T) { expectedToken: "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", expectTokenInfo: true, }, - { + { //nolint:gosec // G101: Synthetic token fixture exercises lowercase bearer parsing; it cannot authenticate. name: "personal access token (classic) with bearer lowercase", authHeader: "bearer ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", expectedStatusCode: http.StatusOK, @@ -62,7 +62,7 @@ func TestExtractUserToken(t *testing.T) { expectTokenInfo: true, }, // Fine-grained Personal Access Token - github_pat_ prefix - { + { //nolint:gosec // G101: Synthetic token fixture exercises fine-grained PAT prefix detection; it cannot authenticate. name: "fine-grained personal access token with Bearer prefix", authHeader: "Bearer github_pat_xxxxxxxxxxxxxxxxxxxxxxx", expectedStatusCode: http.StatusOK, @@ -70,7 +70,7 @@ func TestExtractUserToken(t *testing.T) { expectedToken: "github_pat_xxxxxxxxxxxxxxxxxxxxxxx", expectTokenInfo: true, }, - { + { //nolint:gosec // G101: Synthetic token fixture exercises fine-grained PAT parsing without a bearer prefix; it cannot authenticate. name: "fine-grained personal access token without Bearer prefix", authHeader: "github_pat_xxxxxxxxxxxxxxxxxxxxxxx", expectedStatusCode: http.StatusOK, @@ -113,7 +113,7 @@ func TestExtractUserToken(t *testing.T) { expectTokenInfo: true, }, // Server-to-Server GitHub App Token (installation token) - ghs_ prefix - { + { //nolint:gosec // G101: Synthetic token fixture exercises installation token prefix detection; it cannot authenticate. name: "server-to-server GitHub App token with Bearer prefix", authHeader: "Bearer ghs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", expectedStatusCode: http.StatusOK, diff --git a/pkg/http/oauth/oauth_test.go b/pkg/http/oauth/oauth_test.go index c2ef660104..515a0a471f 100644 --- a/pkg/http/oauth/oauth_test.go +++ b/pkg/http/oauth/oauth_test.go @@ -436,7 +436,7 @@ func TestHandleProtectedResource(t *testing.T) { host: "api.example.com", method: http.MethodGet, expectedStatusCode: http.StatusOK, -expectedScopes: []string{ + expectedScopes: []string{ "repo", "read:org", "read:user", diff --git a/pkg/http/server_test.go b/pkg/http/server_test.go index 500bb40611..903194082e 100644 --- a/pkg/http/server_test.go +++ b/pkg/http/server_test.go @@ -374,12 +374,6 @@ func TestCreateHTTPFeatureChecker(t *testing.T) { headerFeatures: []string{github.FeatureFlagPullRequestsGranular}, wantEnabled: true, }, - { - name: "MCP Apps flag accepted from header", - flagName: github.MCPAppsFeatureFlag, - headerFeatures: []string{github.MCPAppsFeatureFlag}, - wantEnabled: true, - }, { name: "MCP Apps form deferral opt-out accepted from header", flagName: github.MCPAppsDisableFormDeferralFeatureFlag, @@ -417,8 +411,8 @@ func TestCreateHTTPFeatureChecker(t *testing.T) { wantEnabled: false, }, { - name: "insiders mode enables MCP Apps without header", - flagName: github.MCPAppsFeatureFlag, + name: "insiders mode enables CSV output without header", + flagName: github.FeatureFlagCSVOutput, insidersMode: true, wantEnabled: true, }, diff --git a/pkg/http/transport/etag_test.go b/pkg/http/transport/etag_test.go index a3905d01ee..dcac5566eb 100644 --- a/pkg/http/transport/etag_test.go +++ b/pkg/http/transport/etag_test.go @@ -46,10 +46,10 @@ func TestETagTransport_ServesCachedBodyOn304(t *testing.T) { const etag = `"abc123"` const body = `{"number":1}` - var requests int32 + var requests atomic.Int32 var lastIfNoneMatch string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - n := atomic.AddInt32(&requests, 1) + n := requests.Add(1) lastIfNoneMatch = r.Header.Get(headers.IfNoneMatchHeader) w.Header().Set(headers.ETagHeader, etag) if n == 1 { @@ -84,7 +84,7 @@ func TestETagTransport_ServesCachedBodyOn304(t *testing.T) { assert.Equal(t, http.StatusOK, status2, "304 is translated to the cached 200") assert.Equal(t, body, body2, "cached body is served on 304") assert.Equal(t, etag, lastIfNoneMatch, "second request sends the cached ETag") - assert.Equal(t, int32(2), atomic.LoadInt32(&requests), "every request still reaches the server") + assert.Equal(t, int32(2), requests.Load(), "every request still reaches the server") } // TestETagTransport_UpdatesRateLimitHeadersFrom304 verifies that a cache-served @@ -95,9 +95,9 @@ func TestETagTransport_UpdatesRateLimitHeadersFrom304(t *testing.T) { const etag = `"v1"` - var requests int32 + var requests atomic.Int32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - n := atomic.AddInt32(&requests, 1) + n := requests.Add(1) w.Header().Set(headers.ETagHeader, etag) if n == 1 { w.Header().Set("X-RateLimit-Remaining", "100") @@ -143,7 +143,7 @@ func TestETagTransport_ScopesCacheByAuthorization(t *testing.T) { return } w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, r.Header.Get(headers.AuthorizationHeader)) + _, _ = io.WriteString(w, r.Header.Get(headers.AuthorizationHeader)) //nolint:gosec // G705: Test server echoes synthetic tokens to verify cache isolation; no browser consumes the response. })) defer server.Close() diff --git a/pkg/http/transport/user_agent.go b/pkg/http/transport/user_agent.go index a489941cce..a40b4cc45b 100644 --- a/pkg/http/transport/user_agent.go +++ b/pkg/http/transport/user_agent.go @@ -1,11 +1,19 @@ package transport import ( + "context" "net/http" "github.com/github/github-mcp-server/pkg/http/headers" ) +type userAgentKey struct{} + +// WithUserAgent supplies a request-scoped identity without mutating a shared transport. +func WithUserAgent(ctx context.Context, agent string) context.Context { + return context.WithValue(ctx, userAgentKey{}, agent) +} + type UserAgentTransport struct { Transport http.RoundTripper Agent string @@ -13,6 +21,10 @@ type UserAgentTransport struct { func (t *UserAgentTransport) RoundTrip(req *http.Request) (*http.Response, error) { req = req.Clone(req.Context()) - req.Header.Set(headers.UserAgentHeader, t.Agent) + agent := t.Agent + if scoped, ok := req.Context().Value(userAgentKey{}).(string); ok { + agent = scoped + } + req.Header.Set(headers.UserAgentHeader, agent) return t.Transport.RoundTrip(req) } diff --git a/pkg/http/transport/user_agent_test.go b/pkg/http/transport/user_agent_test.go new file mode 100644 index 0000000000..9757b4937f --- /dev/null +++ b/pkg/http/transport/user_agent_test.go @@ -0,0 +1,48 @@ +package transport + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUserAgentTransportRequestIsolation(t *testing.T) { + t.Parallel() + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + _, _ = io.WriteString(w, req.UserAgent()) //nolint:gosec // G705: Test server echoes the test client's user agent to verify transport isolation; no browser consumes the response. + })) + t.Cleanup(server.Close) + transport := &UserAgentTransport{ + Transport: http.DefaultTransport, + Agent: "github-mcp-server/remote-abcdef", + } + client := &http.Client{Transport: transport} + + for _, agent := range []string{"", "github-mcp-server/v1.2.3 (first/1)", "github-mcp-server/v1.2.3 (second/2)"} { + t.Run(agent, func(t *testing.T) { + t.Parallel() + ctx := context.Background() + want := transport.Agent + if agent != "" { + ctx = WithUserAgent(ctx, agent) + want = agent + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, server.URL, nil) + require.NoError(t, err) + req.Header.Set("User-Agent", "original") + resp, err := client.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + assert.Equal(t, want, string(body)) + assert.Equal(t, "original", req.UserAgent()) + assert.Equal(t, "github-mcp-server/remote-abcdef", transport.Agent) + }) + } +} diff --git a/pkg/inventory/builder.go b/pkg/inventory/builder.go index 60bda764b6..e70df0eed3 100644 --- a/pkg/inventory/builder.go +++ b/pkg/inventory/builder.go @@ -14,11 +14,6 @@ var ( ErrUnknownTools = errors.New("unknown tools specified in WithTools") ) -// mcpAppsFeatureFlag is the feature flag name that controls MCP Apps UI metadata. -// This is defined here to avoid importing pkg/github (which imports pkg/inventory). -// The value must match github.MCPAppsFeatureFlag. -const mcpAppsFeatureFlag FeatureFlag = "remote_mcp_ui_apps" - // ToolFilter is a function that determines if a tool should be included. // Returns true if the tool should be included, false to exclude it. type ToolFilter func(ctx context.Context, tool *ServerTool) (bool, error) @@ -374,16 +369,21 @@ func (b *Builder) processToolsets() (map[ToolsetID]bool, []string, []ToolsetID, return enabledToolsets, unrecognized, allToolsetIDs, validIDs, defaultToolsetIDList, descriptions } -// mcpAppsMetaKeys lists the Meta keys controlled by the remote_mcp_ui_apps feature flag. +// mcpAppsMetaKeys lists the Meta keys that carry MCP Apps UI metadata. var mcpAppsMetaKeys = []string{ "ui", // MCP Apps UI metadata } // stripMCPAppsMetadata removes MCP Apps UI metadata from tools when the -// remote_mcp_ui_apps feature flag is not enabled. +// client does not support MCP Apps UI. App-only tools (ui.visibility without +// "model") are omitted entirely rather than being exposed as ordinary +// model-visible tools. func stripMCPAppsMetadata(tools []ServerTool) []ServerTool { result := make([]ServerTool, 0, len(tools)) for _, tool := range tools { + if isAppOnlyTool(&tool) { + continue + } if stripped := stripMetaKeys(tool, mcpAppsMetaKeys); stripped != nil { result = append(result, *stripped) } else { @@ -393,6 +393,30 @@ func stripMCPAppsMetadata(tools []ServerTool) []ServerTool { return result } +// isAppOnlyTool reports whether a tool declares MCP Apps visibility that +// excludes the model (e.g. `_meta.ui.visibility: ["app"]`). Such tools are +// only meant to be called by MCP App views, never by the model directly. +func isAppOnlyTool(tool *ServerTool) bool { + ui, ok := tool.Tool.Meta["ui"].(map[string]any) + if !ok { + return false + } + var visibility []string + switch v := ui["visibility"].(type) { + case []string: + visibility = v + case []any: + for _, item := range v { + if s, ok := item.(string); ok { + visibility = append(visibility, s) + } + } + default: + return false + } + return len(visibility) > 0 && !slices.Contains(visibility, "model") +} + // stripMetaKeys removes the specified Meta keys from a single tool. // Returns a modified copy if changes were made, nil otherwise. func stripMetaKeys(tool ServerTool, keys []string) *ServerTool { diff --git a/pkg/inventory/filters.go b/pkg/inventory/filters.go index b3dfbeb912..bcc0d58c5f 100644 --- a/pkg/inventory/filters.go +++ b/pkg/inventory/filters.go @@ -16,12 +16,6 @@ func (r *Inventory) isToolsetEnabled(toolsetID ToolsetID) bool { return true } -// checkFeatureFlag checks a feature flag using the feature checker. -// Returns false if checker is nil or returns an error (errors are logged). -func (r *Inventory) checkFeatureFlag(ctx context.Context, flagName FeatureFlag) bool { - return ResolveFeature(ctx, r.featureChecker, flagName) -} - // isToolEnabled checks if a specific tool is enabled based on current filters. // Filter evaluation order: // 1. Tool.Enabled (tool self-filtering) diff --git a/pkg/inventory/registry.go b/pkg/inventory/registry.go index 9439e6f201..02dc5b0fd7 100644 --- a/pkg/inventory/registry.go +++ b/pkg/inventory/registry.go @@ -179,25 +179,18 @@ func (r *Inventory) ToolsetDescriptions() map[ToolsetID]string { // diagnostics that need the same view of the tool surface the server would // register. // -// The strip applies when EITHER of the following is true: -// -// - The remote_mcp_ui_apps feature flag is not enabled in ctx (server-side gate). -// - The client explicitly did not advertise the io.modelcontextprotocol/ui -// extension capability (per the 2026-01-26 MCP Apps spec, servers SHOULD -// check client capabilities before exposing UI-enabled tools). When the -// capability is unknown (e.g. stdio paths that do not populate the -// context flag) the feature-flag gate is the sole source of truth. +// MCP Apps UI metadata is stripped only when the client explicitly did not +// advertise the io.modelcontextprotocol/ui extension capability (per the +// 2026-01-26 MCP Apps spec, servers SHOULD check client capabilities before +// exposing UI-enabled tools). In that case app-only tools (whose +// _meta.ui.visibility excludes "model") are omitted entirely. When the +// capability is unknown (e.g. stdio paths that do not populate the context +// flag) the metadata is kept. func (r *Inventory) ToolsForRegistration(ctx context.Context) []ServerTool { ctx = WithFeatureState(ctx, r.featureChecker) tools := r.availableTools(ctx) - if present, checkFeature := mcpAppsMetadataStatus(ctx, tools); present { - featureEnabled := false - if checkFeature { - featureEnabled = r.checkFeatureFlag(ctx, mcpAppsFeatureFlag) - } - if shouldStripMCPAppsMetadata(ctx, featureEnabled) { - tools = stripMCPAppsMetadata(tools) - } + if shouldStripMCPAppsMetadata(ctx) { + tools = stripMCPAppsMetadata(tools) } return tools } @@ -215,9 +208,6 @@ func (r *Inventory) RequiredFeatures() []FeatureFlag { for i := range r.prompts { features = appendFeatureDeclaration(features, r.prompts[i].FeatureRule) } - if r.usesMCPAppsMetadata() { - features = appendUniqueFeature(features, mcpAppsFeatureFlag) - } slices.Sort(features) return features } @@ -228,37 +218,6 @@ func (r *Inventory) WithFeatureState(ctx context.Context) context.Context { return WithFeatureState(ctx, r.featureChecker) } -func (r *Inventory) usesMCPAppsMetadata() bool { - for i := range r.tools { - if toolUsesMCPAppsMetadata(&r.tools[i]) { - return true - } - } - return false -} - -func mcpAppsMetadataStatus(ctx context.Context, tools []ServerTool) (present, checkFeature bool) { - for i := range tools { - if !toolUsesMCPAppsMetadata(&tools[i]) { - continue - } - present = true - if featureDecisionForToolAvailability(ctx, tools[i].availability()) != includeToolWithoutFeatureRule { - return true, true - } - } - return present, false -} - -func toolUsesMCPAppsMetadata(tool *ServerTool) bool { - for _, key := range mcpAppsMetaKeys { - if _, ok := tool.Tool.Meta[key]; ok { - return true - } - } - return false -} - func appendFeatureDeclaration(features []FeatureFlag, rule FeatureRule) []FeatureFlag { for _, feature := range rule.features { features = appendUniqueFeature(features, feature) @@ -267,29 +226,20 @@ func appendFeatureDeclaration(features []FeatureFlag, rule FeatureRule) []Featur } // shouldStripMCPAppsMetadata centralises the strip decision so the same logic -// is exercised by tests and by RegisterTools. -func shouldStripMCPAppsMetadata(ctx context.Context, featureFlagEnabled bool) bool { - if !featureFlagEnabled { - return true - } - // Feature flag is on. Respect the client capability if it is known. - if supported, ok := ghcontext.HasUISupport(ctx); ok && !supported { - return true - } - return false +// is exercised by tests and by RegisterTools. Metadata is stripped only when +// the client is known not to support MCP Apps UI. +func shouldStripMCPAppsMetadata(ctx context.Context) bool { + supported, ok := ghcontext.HasUISupport(ctx) + return ok && !supported } // RegisterTools registers all available tools with the server using the provided dependencies. // The context is used for feature flag evaluation and client capability checks. // // MCP Apps UI metadata (`_meta.ui`) is stripped from the registered tools when -// either the MCP Apps feature flag is not enabled for this request, or the -// client did not advertise the io.modelcontextprotocol/ui extension. The +// the client did not advertise the io.modelcontextprotocol/ui extension. The // strip happens here (rather than at Build() time) so the per-request -// context is in scope — HTTP feature checkers that read insiders mode or -// user identity from ctx would otherwise see context.Background() and -// falsely report the flag off, even when the actual request arrived on the -// /insiders route. +// context, which carries the client capability, is in scope. func (r *Inventory) RegisterTools(ctx context.Context, s *mcp.Server, deps any, middleware ...ToolHandlerMiddleware) { tools := r.ToolsForRegistration(ctx) addToolAvailabilityMiddleware(s, tools) diff --git a/pkg/inventory/registry_test.go b/pkg/inventory/registry_test.go index 2e21fc632c..4e827936c6 100644 --- a/pkg/inventory/registry_test.go +++ b/pkg/inventory/registry_test.go @@ -1334,7 +1334,7 @@ func TestMetadataBehaviorRemainsLive(t *testing.T) { meta["typed_map"].(map[string]string)["value"] = "changed" meta["bytes"].([]byte)[0] = 'X' - require.Contains(t, inv.RequiredFeatures(), mcpAppsFeatureFlag) + require.Empty(t, inv.RequiredFeatures()) available := inv.AllTools() require.Equal(t, "changed", available[0].Tool.Meta["typed_map"].(map[string]string)["value"]) require.Equal(t, byte('X'), available[0].Tool.Meta["bytes"].([]byte)[0]) @@ -2088,15 +2088,14 @@ func mockToolWithMeta(name string, toolsetID string, meta map[string]any) Server ) } -func TestWithMCPApps_DisabledStripsUIMetadata(t *testing.T) { +func TestWithMCPApps_UnsupportedClientStripsUIMetadata(t *testing.T) { toolWithUI := mockToolWithMeta("tool_with_ui", "toolset1", map[string]any{ "ui": map[string]any{"html": "
hello
"}, "description": "kept", }) - // Default: MCP Apps is disabled - UI meta should be stripped on registration. reg := mustBuild(t, NewBuilder().SetTools([]ServerTool{toolWithUI}).WithToolsets([]string{"all"})) - registered := captureRegisteredTools(context.Background(), t, reg) + registered := captureRegisteredTools(ghcontext.WithUISupport(context.Background(), false), t, reg) require.Len(t, registered, 1) if registered[0].Meta["ui"] != nil { @@ -2107,27 +2106,55 @@ func TestWithMCPApps_DisabledStripsUIMetadata(t *testing.T) { } } -func TestWithMCPApps_EnabledPreservesUIMetadata(t *testing.T) { +func TestWithMCPApps_UnsupportedClientOmitsAppOnlyTools(t *testing.T) { + appOnly := mockToolWithMeta("app_only", "toolset1", map[string]any{ + "ui": map[string]any{"visibility": []string{"app"}}, + }) + appOnlyAny := mockToolWithMeta("app_only_any", "toolset1", map[string]any{ + "ui": map[string]any{"visibility": []any{"app"}}, + }) + modelAndApp := mockToolWithMeta("model_and_app", "toolset1", map[string]any{ + "ui": map[string]any{"resourceUri": "ui://x", "visibility": []string{"model", "app"}}, + }) + noVisibility := mockToolWithMeta("no_visibility", "toolset1", map[string]any{ + "ui": map[string]any{"resourceUri": "ui://y"}, + }) + reg := mustBuild(t, NewBuilder(). + SetTools([]ServerTool{appOnly, appOnlyAny, modelAndApp, noVisibility}). + WithToolsets([]string{"all"})) + + stripped := reg.ToolsForRegistration(ghcontext.WithUISupport(context.Background(), false)) + names := make([]string, 0, len(stripped)) + for _, tool := range stripped { + names = append(names, tool.Tool.Name) + require.Nil(t, tool.Tool.Meta["ui"], "ui meta should be stripped from %s", tool.Tool.Name) + } + require.ElementsMatch(t, []string{"model_and_app", "no_visibility"}, names) + + for _, ctx := range []context.Context{ + context.Background(), + ghcontext.WithUISupport(context.Background(), true), + } { + require.Len(t, reg.ToolsForRegistration(ctx), 4, "app-only tools should be kept when UI is supported or unknown") + } +} + +func TestWithMCPApps_PreservesUIMetadataByDefault(t *testing.T) { uiData := map[string]any{"html": "
hello
"} toolWithUI := mockToolWithMeta("tool_with_ui", "toolset1", map[string]any{ "ui": uiData, "description": "kept", }) - // Feature checker enables MCP Apps - UI meta should be preserved - mcpAppsChecker := func(_ context.Context, flag string) (bool, error) { - return flag == string(mcpAppsFeatureFlag), nil - } reg := mustBuild(t, NewBuilder(). SetTools([]ServerTool{toolWithUI}). - WithToolsets([]string{"all"}). - WithFeatureChecker(mcpAppsChecker)) - available := reg.AvailableTools(context.Background()) + WithToolsets([]string{"all"})) + available := reg.ToolsForRegistration(context.Background()) require.Len(t, available, 1) // UI metadata should be preserved if available[0].Tool.Meta["ui"] == nil { - t.Errorf("Expected 'ui' meta to be preserved with MCP Apps enabled") + t.Errorf("Expected 'ui' meta to be preserved") } // Other metadata should also be preserved if available[0].Tool.Meta["description"] != "kept" { @@ -2142,7 +2169,6 @@ func TestWithMCPApps_ToolsWithoutUIMetaUnaffected(t *testing.T) { }) toolNilMeta := mockTool("tool_nil_meta", "toolset1", true) - // Test with MCP Apps disabled (default) - non-UI meta should be unaffected reg := mustBuild(t, NewBuilder(). SetTools([]ServerTool{toolNoUI, toolNilMeta}). WithToolsets([]string{"all"})) @@ -2183,7 +2209,7 @@ func TestWithMCPApps_UIOnlyMetaBecomesNil(t *testing.T) { reg := mustBuild(t, NewBuilder(). SetTools([]ServerTool{toolUIOnly}). WithToolsets([]string{"all"})) - registered := captureRegisteredTools(context.Background(), t, reg) + registered := captureRegisteredTools(ghcontext.WithUISupport(context.Background(), false), t, reg) require.Len(t, registered, 1) if registered[0].Meta != nil { @@ -2311,8 +2337,8 @@ func TestWithMCPApps_DoesNotMutateOriginalTools(t *testing.T) { tool := mockToolWithMeta("test", "toolset1", originalMeta) tools := []ServerTool{tool} - // Build with MCP Apps disabled (default) - should strip ui - _ = mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"})) + reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"})) + _ = reg.ToolsForRegistration(ghcontext.WithUISupport(context.Background(), false)) // Original tool should be unchanged require.Equal(t, "data", tools[0].Tool.Meta["ui"], "original tool should not be mutated") @@ -2484,52 +2510,36 @@ func captureRegisteredTools(ctx context.Context, t *testing.T, reg *Inventory) [ } // TestShouldStripMCPAppsMetadata verifies the spec-conformant strip decision: -// strip when the feature flag is off, OR when the client explicitly does not -// advertise the io.modelcontextprotocol/ui extension. +// strip only when the client explicitly does not advertise the +// io.modelcontextprotocol/ui extension. func TestShouldStripMCPAppsMetadata(t *testing.T) { t.Parallel() tests := []struct { name string setupCtx func() context.Context - ffOn bool want bool }{ { - name: "FF off, capability unknown -> strip", - setupCtx: context.Background, - ffOn: false, - want: true, - }, - { - name: "FF off, capability present -> strip (FF wins)", - setupCtx: func() context.Context { return ghcontext.WithUISupport(context.Background(), true) }, - ffOn: false, - want: true, - }, - { - name: "FF on, capability unknown -> keep", + name: "capability unknown -> keep", setupCtx: context.Background, - ffOn: true, want: false, }, { - name: "FF on, capability present -> keep", + name: "capability present -> keep", setupCtx: func() context.Context { return ghcontext.WithUISupport(context.Background(), true) }, - ffOn: true, want: false, }, { - name: "FF on, capability explicitly absent -> strip", + name: "capability explicitly absent -> strip", setupCtx: func() context.Context { return ghcontext.WithUISupport(context.Background(), false) }, - ffOn: true, want: true, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - got := shouldStripMCPAppsMetadata(tc.setupCtx(), tc.ffOn) + got := shouldStripMCPAppsMetadata(tc.setupCtx()) require.Equal(t, tc.want, got) }) } diff --git a/pkg/inventory/server_tool.go b/pkg/inventory/server_tool.go index 2bc2769593..9dd708685f 100644 --- a/pkg/inventory/server_tool.go +++ b/pkg/inventory/server_tool.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "maps" + "slices" "github.com/github/github-mcp-server/pkg/octicons" "github.com/google/jsonschema-go/jsonschema" @@ -142,8 +143,8 @@ func (st *ServerTool) Handler(deps any) mcp.ToolHandler { // Panics if the tool has no handler - all tools should have handlers. func (st *ServerTool) RegisterFunc(s *mcp.Server, deps any, middleware ...ToolHandlerMiddleware) { handler := st.Handler(deps) // This will panic if HandlerFunc is nil - for i := len(middleware) - 1; i >= 0; i-- { - handler = middleware[i](handler) + for _, m := range slices.Backward(middleware) { + handler = m(handler) } handler = st.wrapAvailabilityCheck(handler) // Make a shallow copy of the tool to avoid mutating the original diff --git a/pkg/lockdown/lockdown.go b/pkg/lockdown/lockdown.go index 8625073116..1e47890852 100644 --- a/pkg/lockdown/lockdown.go +++ b/pkg/lockdown/lockdown.go @@ -11,7 +11,7 @@ import ( "sync" "time" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/muesli/cache2go" "github.com/shurcooL/githubv4" ) diff --git a/pkg/lockdown/lockdown_test.go b/pkg/lockdown/lockdown_test.go index 6254533682..d724a46c4b 100644 --- a/pkg/lockdown/lockdown_test.go +++ b/pkg/lockdown/lockdown_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/github/github-mcp-server/internal/githubv4mock" - gogithub "github.com/google/go-github/v89/github" + gogithub "github.com/google/go-github/v92/github" "github.com/muesli/cache2go" "github.com/shurcooL/githubv4" "github.com/stretchr/testify/require" @@ -89,7 +89,7 @@ func newMockGQLClient(viewerLogin string, isPrivate bool) (*githubv4.Client, *co func newMockRESTServer(t *testing.T, permission string) *gogithub.Client { t.Helper() restServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - resp := gogithub.RepositoryPermissionLevel{Permission: gogithub.Ptr(permission)} + resp := gogithub.RepositoryPermissionLevel{Permission: new(permission)} w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(resp) })) diff --git a/pkg/raw/raw.go b/pkg/raw/raw.go index fcaa5c6af2..3bf77b29e7 100644 --- a/pkg/raw/raw.go +++ b/pkg/raw/raw.go @@ -8,7 +8,7 @@ import ( "net/url" "strings" - gogithub "github.com/google/go-github/v89/github" + gogithub "github.com/google/go-github/v92/github" ) // errPathTraversal is returned when an owner, repo, ref/sha, or path diff --git a/pkg/raw/raw_test.go b/pkg/raw/raw_test.go index 8f6f54f4c7..4269ade1ea 100644 --- a/pkg/raw/raw_test.go +++ b/pkg/raw/raw_test.go @@ -9,7 +9,7 @@ import ( "strings" "testing" - "github.com/google/go-github/v89/github" + "github.com/google/go-github/v92/github" "github.com/stretchr/testify/require" ) diff --git a/pkg/toolvalidation/readonlyhint.go b/pkg/toolvalidation/readonlyhint.go index bcde92a5ec..b9877bf847 100644 --- a/pkg/toolvalidation/readonlyhint.go +++ b/pkg/toolvalidation/readonlyhint.go @@ -58,7 +58,7 @@ func (v ReadOnlyHintViolation) String() string { // violations, err := toolvalidation.ScanReadOnlyHint(dir) func ScanReadOnlyHint(dir string) ([]ReadOnlyHintViolation, error) { fset := token.NewFileSet() - pkgs, err := parser.ParseDir(fset, dir, func(info os.FileInfo) bool { + pkgs, err := parser.ParseDir(fset, dir, func(info os.FileInfo) bool { //nolint:staticcheck // SA1019: This source-level validator intentionally scans all files regardless of build tags, without loading packages. // Skip test files: they are allowed to construct mcp.Tool literals // for fixtures or mocks where ReadOnlyHint is not meaningful. return !strings.HasSuffix(info.Name(), "_test.go") diff --git a/script/lint b/script/lint index 5b69cbe2ff..00cfeb1aa0 100755 --- a/script/lint +++ b/script/lint @@ -6,9 +6,9 @@ gofmt -s -w . BINDIR="$(git rev-parse --show-toplevel)"/bin BINARY=$BINDIR/golangci-lint # sync with .github/workflows/lint.yml -GOLANGCI_LINT_VERSION=v2.9.0 +GOLANGCI_LINT_VERSION=v2.14.0 -if [ ! -f "$BINARY" ]; then +if [ ! -f "$BINARY" ] || ! "$BINARY" version --short | grep -qx "${GOLANGCI_LINT_VERSION#v}"; then curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b "$BINDIR" "$GOLANGCI_LINT_VERSION" fi diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index 5bbc6700b0..d67b374b56 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -17,7 +17,7 @@ The following packages are included for the amd64, arm64 architectures. - [github.com/github/github-mcp-server](https://pkg.go.dev/github.com/github/github-mcp-server) ([MIT](https://github.com/github/github-mcp-server/blob/HEAD/LICENSE)) - [github.com/go-chi/chi/v5](https://pkg.go.dev/github.com/go-chi/chi/v5) ([MIT](https://github.com/go-chi/chi/blob/v5.3.2/LICENSE)) - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.5.0/LICENSE)) - - [github.com/google/go-github/v89/github](https://pkg.go.dev/github.com/google/go-github/v89/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/34349a88bac3/LICENSE)) + - [github.com/google/go-github/v92/github](https://pkg.go.dev/github.com/google/go-github/v92/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v92.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.2.0/LICENSE)) - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.4.3/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) @@ -31,8 +31,8 @@ The following packages are included for the amd64, arm64 architectures. - [github.com/sagikazarmark/locafero](https://pkg.go.dev/github.com/sagikazarmark/locafero) ([MIT](https://github.com/sagikazarmark/locafero/blob/v0.11.0/LICENSE)) - [github.com/segmentio/asm](https://pkg.go.dev/github.com/segmentio/asm) ([MIT](https://github.com/segmentio/asm/blob/v1.1.3/LICENSE)) - [github.com/segmentio/encoding](https://pkg.go.dev/github.com/segmentio/encoding) ([MIT](https://github.com/segmentio/encoding/blob/v0.5.4/LICENSE)) - - [github.com/shurcooL/githubv4](https://pkg.go.dev/github.com/shurcooL/githubv4) ([MIT](https://github.com/shurcooL/githubv4/blob/48295856cce7/LICENSE)) - - [github.com/shurcooL/graphql](https://pkg.go.dev/github.com/shurcooL/graphql) ([MIT](https://github.com/shurcooL/graphql/blob/ed46e5a46466/LICENSE)) + - [github.com/shurcooL/githubv4](https://pkg.go.dev/github.com/shurcooL/githubv4) ([MIT](https://github.com/shurcooL/githubv4/blob/2402fdf4a9ed/LICENSE)) + - [github.com/shurcooL/graphql](https://pkg.go.dev/github.com/shurcooL/graphql) ([MIT](https://github.com/shurcooL/graphql/blob/7ee5256398cf/LICENSE)) - [github.com/sourcegraph/conc](https://pkg.go.dev/github.com/sourcegraph/conc) ([MIT](https://github.com/sourcegraph/conc/blob/5f936abd7ae8/LICENSE)) - [github.com/spf13/afero](https://pkg.go.dev/github.com/spf13/afero) ([Apache-2.0](https://github.com/spf13/afero/blob/v1.15.0/LICENSE.txt)) - [github.com/spf13/cast](https://pkg.go.dev/github.com/spf13/cast) ([MIT](https://github.com/spf13/cast/blob/v1.10.0/LICENSE)) diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index ede346d1dc..a1abb6821b 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -17,7 +17,7 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/github/github-mcp-server](https://pkg.go.dev/github.com/github/github-mcp-server) ([MIT](https://github.com/github/github-mcp-server/blob/HEAD/LICENSE)) - [github.com/go-chi/chi/v5](https://pkg.go.dev/github.com/go-chi/chi/v5) ([MIT](https://github.com/go-chi/chi/blob/v5.3.2/LICENSE)) - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.5.0/LICENSE)) - - [github.com/google/go-github/v89/github](https://pkg.go.dev/github.com/google/go-github/v89/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/34349a88bac3/LICENSE)) + - [github.com/google/go-github/v92/github](https://pkg.go.dev/github.com/google/go-github/v92/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v92.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.2.0/LICENSE)) - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.4.3/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) @@ -31,8 +31,8 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/sagikazarmark/locafero](https://pkg.go.dev/github.com/sagikazarmark/locafero) ([MIT](https://github.com/sagikazarmark/locafero/blob/v0.11.0/LICENSE)) - [github.com/segmentio/asm](https://pkg.go.dev/github.com/segmentio/asm) ([MIT](https://github.com/segmentio/asm/blob/v1.1.3/LICENSE)) - [github.com/segmentio/encoding](https://pkg.go.dev/github.com/segmentio/encoding) ([MIT](https://github.com/segmentio/encoding/blob/v0.5.4/LICENSE)) - - [github.com/shurcooL/githubv4](https://pkg.go.dev/github.com/shurcooL/githubv4) ([MIT](https://github.com/shurcooL/githubv4/blob/48295856cce7/LICENSE)) - - [github.com/shurcooL/graphql](https://pkg.go.dev/github.com/shurcooL/graphql) ([MIT](https://github.com/shurcooL/graphql/blob/ed46e5a46466/LICENSE)) + - [github.com/shurcooL/githubv4](https://pkg.go.dev/github.com/shurcooL/githubv4) ([MIT](https://github.com/shurcooL/githubv4/blob/2402fdf4a9ed/LICENSE)) + - [github.com/shurcooL/graphql](https://pkg.go.dev/github.com/shurcooL/graphql) ([MIT](https://github.com/shurcooL/graphql/blob/7ee5256398cf/LICENSE)) - [github.com/sourcegraph/conc](https://pkg.go.dev/github.com/sourcegraph/conc) ([MIT](https://github.com/sourcegraph/conc/blob/5f936abd7ae8/LICENSE)) - [github.com/spf13/afero](https://pkg.go.dev/github.com/spf13/afero) ([Apache-2.0](https://github.com/spf13/afero/blob/v1.15.0/LICENSE.txt)) - [github.com/spf13/cast](https://pkg.go.dev/github.com/spf13/cast) ([MIT](https://github.com/spf13/cast/blob/v1.10.0/LICENSE)) diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index 1ef1a851d0..7e6f57aeb7 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -17,7 +17,7 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/github/github-mcp-server](https://pkg.go.dev/github.com/github/github-mcp-server) ([MIT](https://github.com/github/github-mcp-server/blob/HEAD/LICENSE)) - [github.com/go-chi/chi/v5](https://pkg.go.dev/github.com/go-chi/chi/v5) ([MIT](https://github.com/go-chi/chi/blob/v5.3.2/LICENSE)) - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.5.0/LICENSE)) - - [github.com/google/go-github/v89/github](https://pkg.go.dev/github.com/google/go-github/v89/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/34349a88bac3/LICENSE)) + - [github.com/google/go-github/v92/github](https://pkg.go.dev/github.com/google/go-github/v92/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v92.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.2.0/LICENSE)) - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.4.3/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) @@ -32,8 +32,8 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/sagikazarmark/locafero](https://pkg.go.dev/github.com/sagikazarmark/locafero) ([MIT](https://github.com/sagikazarmark/locafero/blob/v0.11.0/LICENSE)) - [github.com/segmentio/asm](https://pkg.go.dev/github.com/segmentio/asm) ([MIT](https://github.com/segmentio/asm/blob/v1.1.3/LICENSE)) - [github.com/segmentio/encoding](https://pkg.go.dev/github.com/segmentio/encoding) ([MIT](https://github.com/segmentio/encoding/blob/v0.5.4/LICENSE)) - - [github.com/shurcooL/githubv4](https://pkg.go.dev/github.com/shurcooL/githubv4) ([MIT](https://github.com/shurcooL/githubv4/blob/48295856cce7/LICENSE)) - - [github.com/shurcooL/graphql](https://pkg.go.dev/github.com/shurcooL/graphql) ([MIT](https://github.com/shurcooL/graphql/blob/ed46e5a46466/LICENSE)) + - [github.com/shurcooL/githubv4](https://pkg.go.dev/github.com/shurcooL/githubv4) ([MIT](https://github.com/shurcooL/githubv4/blob/2402fdf4a9ed/LICENSE)) + - [github.com/shurcooL/graphql](https://pkg.go.dev/github.com/shurcooL/graphql) ([MIT](https://github.com/shurcooL/graphql/blob/7ee5256398cf/LICENSE)) - [github.com/sourcegraph/conc](https://pkg.go.dev/github.com/sourcegraph/conc) ([MIT](https://github.com/sourcegraph/conc/blob/5f936abd7ae8/LICENSE)) - [github.com/spf13/afero](https://pkg.go.dev/github.com/spf13/afero) ([Apache-2.0](https://github.com/spf13/afero/blob/v1.15.0/LICENSE.txt)) - [github.com/spf13/cast](https://pkg.go.dev/github.com/spf13/cast) ([MIT](https://github.com/spf13/cast/blob/v1.10.0/LICENSE)) diff --git a/third-party/github.com/google/go-github/v92/github/LICENSE b/third-party/github.com/google/go-github/v92/github/LICENSE new file mode 100644 index 0000000000..28b6486f0b --- /dev/null +++ b/third-party/github.com/google/go-github/v92/github/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013 The go-github AUTHORS. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ui/package-lock.json b/ui/package-lock.json index 1e4e36cd7d..4211483e2b 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -9,489 +9,103 @@ "version": "1.0.0", "dependencies": { "@github/markdown-toolbar-element": "^2.2.3", - "@modelcontextprotocol/ext-apps": "^1.7.2", - "@primer/octicons-react": "^19.0.0", - "@primer/react": "^36.0.0", - "react": "^18.0.0", - "react-dom": "^18.0.0", + "@modelcontextprotocol/client": "^2.0.0", + "@modelcontextprotocol/ext-apps": "^2.0.3", + "@primer/octicons-react": "^19.38.0", + "@primer/primitives": "^11.10.0", + "@primer/react": "^38.40.1", + "react": "^19.3.0", + "react-dom": "^19.3.0", "react-markdown": "^10.1.0", - "remark-gfm": "^4.0.1" + "remark-gfm": "^4.0.1", + "zod": "^4.2.0" }, "devDependencies": { - "@types/node": "^25.2.0", - "@types/react": "^18.0.0", - "@types/react-dom": "^18.0.0", - "@vitejs/plugin-react": "^6.0.2", - "typescript": "^5.7.0", - "vite": "^8.0.16", + "@types/node": "^26.6.4", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "@vitejs/plugin-react": "^6.1.1", + "typescript": "^7.0.2", + "vite": "^8.3.2", "vite-plugin-singlefile": "^2.3.3" }, "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/types": "^7.29.7" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^26.0.0" } }, "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emotion/is-prop-valid": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", - "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@emotion/memoize": "^0.9.0" - } - }, - "node_modules/@emotion/memoize": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", - "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", - "license": "MIT", - "peer": true - }, - "node_modules/@emotion/stylis": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", - "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==", - "license": "MIT", - "peer": true - }, - "node_modules/@emotion/unitless": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", - "license": "MIT", - "peer": true - }, - "node_modules/@github/combobox-nav": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.3.1.tgz", - "integrity": "sha512-gwxPzLw8XKecy1nP63i9lOBritS3bWmxl02UX6G0TwMQZbMem1BCS1tEZgYd3mkrkiDrUMWaX+DbFCuDFo3K+A==", - "license": "MIT" - }, "node_modules/@github/markdown-toolbar-element": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@github/markdown-toolbar-element/-/markdown-toolbar-element-2.2.3.tgz", "integrity": "sha512-AlquKGee+IWiAMYVB0xyHFZRMnu4n3X4HTvJHu79GiVJ1ojTukCWyxMlF5NMsecoLcBKsuBhx3QPv2vkE/zQ0A==", "license": "MIT" }, - "node_modules/@github/paste-markdown": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@github/paste-markdown/-/paste-markdown-1.5.3.tgz", - "integrity": "sha512-PzZ1b3PaqBzYqbT4fwKEhiORf38h2OcGp2+JdXNNM7inZ7egaSmfmhyNkQILpqWfS0AYtRS3CDq6z03eZ8yOMQ==", + "node_modules/@github/mini-throttle": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@github/mini-throttle/-/mini-throttle-2.1.1.tgz", + "integrity": "sha512-KtOPaB+FiKJ6jcKm9UKyaM5fPURHGf+xcp+b4Mzoi81hOc6M1sIGpMZMAVbNzfa2lW5+RPGKq888Px0j76OZ/A==", "license": "MIT" }, "node_modules/@github/relative-time-element": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.5.1.tgz", - "integrity": "sha512-uxCxCwe9vdwUDmRmM84tN0UERlj8MosLV44+r/VDj7DZUVUSTP4vyWlE9mRK6vHelOmT8DS3RMlaMrLlg1h1PQ==", - "license": "MIT" - }, - "node_modules/@github/tab-container-element": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/@github/tab-container-element/-/tab-container-element-4.8.2.tgz", - "integrity": "sha512-WkaM4mfs8x7dXRWEaDb5deC0OhH6sGQ5cw8i/sVw25gikl4f8C7mHj0kihL5k3eKIIqmGT1Fdswdoi+9ZLDpRA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-5.3.1.tgz", + "integrity": "sha512-f++QCIXxGH7rMbpTASRfLyCyPydd4HqwOmATBb/sAUCq72HEZN0MX9znmJSp7NKYxuQxF8j0Mv3ez9P32c70vw==", "license": "MIT" }, - "node_modules/@hono/node-server": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.1.tgz", - "integrity": "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "hono": "^4" - } + "node_modules/@lit-labs/react": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lit-labs/react/-/react-1.2.1.tgz", + "integrity": "sha512-DiZdJYFU0tBbdQkfwwRSwYyI/mcWkg3sWesKRsHUd4G+NekTmmeq9fzsurvcKTNVa0comNljwtg4Hvi1ds3V+A==", + "license": "BSD-3-Clause" }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.6.0.tgz", + "integrity": "sha512-VHb0ALPMTlgKjM6yIxxoQNnpKyUKLD04VzeQdsiXkMqkvYlAHxq9glGLmgbb889/1GsohSOAjvQYoiBppXFqrQ==", + "license": "BSD-3-Clause" }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "node_modules/@modelcontextprotocol/client": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.2.0.tgz", + "integrity": "sha512-LxCou/CSYQ6dwEnjhLZY0KnEuc8V4UJ3IEQCl/uR2yHobSQIEdJKUlKLRYRF5i5FqRGHy1eK7une3aAWDHLtig==", "license": "MIT", - "peer": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "peer": true, + "@modelcontextprotocol/core": "2.2.0", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "jose": "^6.1.3", + "pkce-challenge": "^5.0.0", + "zod": "^4.2.0" + }, "engines": { - "node": ">=6.0.0" + "node": ">=20" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT", - "peer": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "node_modules/@modelcontextprotocol/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.2.0.tgz", + "integrity": "sha512-iLhmprRmWI8EcosOA3wVvww22z02NkgqhV4fBH6f/odQBsi7xnJc0HGmi21yWO+/iKw2yzqVE127Zhna5/+JXw==", "license": "MIT", - "peer": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" } }, - "node_modules/@lit-labs/react": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@lit-labs/react/-/react-1.2.1.tgz", - "integrity": "sha512-DiZdJYFU0tBbdQkfwwRSwYyI/mcWkg3sWesKRsHUd4G+NekTmmeq9fzsurvcKTNVa0comNljwtg4Hvi1ds3V+A==", - "license": "BSD-3-Clause" - }, - "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.5.1.tgz", - "integrity": "sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==", - "license": "BSD-3-Clause" - }, "node_modules/@modelcontextprotocol/ext-apps": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.7.2.tgz", - "integrity": "sha512-OOWKDxdAjYDcgHkmzVzccyyag3FK+jBWPaWu4WvTxFsU4R/cgOX4eep66zPRA5n4v6WfxUNibPyvX4iJ7egYTg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-2.0.3.tgz", + "integrity": "sha512-Tn+4+cyhO4f4cQSDxNyLiIfEE+qlTTuIbWBJB8JTinEXOhDYgCT4wDTQplxD9MBYZH5pCYGC59GaxiwIT4bu8w==", "license": "MIT", "workspaces": [ "examples/*" @@ -503,12 +117,17 @@ "node": ">=20" }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/client": "^2.0.0", + "@modelcontextprotocol/core": "^2.0.0", + "@modelcontextprotocol/server": "^2.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", - "zod": "^3.25.0 || ^4.0.0" + "zod": "^4.2.0" }, "peerDependenciesMeta": { + "@modelcontextprotocol/server": { + "optional": true + }, "react": { "optional": true }, @@ -517,163 +136,92 @@ } } }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", - "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@hono/node-server": "^1.19.9 || ^2.0.5", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } - } + "node_modules/@oddbird/popover-polyfill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@oddbird/popover-polyfill/-/popover-polyfill-0.5.2.tgz", + "integrity": "sha512-iFrvar5SOMtKFOSjYvs4z9UlLqDdJbMx0mgISLcPedv+g0ac5sgeETLGtipHCVIae6HJPclNEH5aCyD1RZaEHw==", + "license": "BSD-3-Clause" }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", - "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", + "node_modules/@oxc-project/types": { + "version": "0.152.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.152.0.tgz", + "integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.2" - }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@oddbird/popover-polyfill": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@oddbird/popover-polyfill/-/popover-polyfill-0.3.8.tgz", - "integrity": "sha512-+aK7EHL3VggfsWGVqUwvtli2+kP5OWyseAsrefhzR2XWoi2oALUCeoDn63i5WS3ZOmLiXHRNBwHPeta8w+aM1g==", - "license": "BSD-3-Clause" - }, - "node_modules/@oxc-project/types": { - "version": "0.133.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" } }, "node_modules/@primer/behaviors": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@primer/behaviors/-/behaviors-1.10.1.tgz", - "integrity": "sha512-9iNr3ulh2W4zmp1e2COu3XBNjq/eqXbHkCvg2SMD/g8zSe7oBXa/FFg8gdaXmyykElfWRytvZkaJh14FrY22Gw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@primer/behaviors/-/behaviors-1.11.0.tgz", + "integrity": "sha512-kDHtaw9kfCjPVT61eM731ftckhGyWiPX+OKmUccLN9sMFt9E6FXmsPAbny7O0RBxeW1Xj/NHNdbMEjYEg2dBYg==", "license": "MIT" }, "node_modules/@primer/live-region-element": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@primer/live-region-element/-/live-region-element-0.7.2.tgz", - "integrity": "sha512-wdxCHfcJzE1IPPjZNFR4RTwRcSWb7TN0fRdMH5HcxphLEnuZBWy0TAxk3xPA+/6lwiN3uEJ+ZWV4UF/glXh43A==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@primer/live-region-element/-/live-region-element-0.8.1.tgz", + "integrity": "sha512-g7bNs9R2A2LfETMOWYsvtT0b5Q/QLM1Fz1vrTcWa5r2LbEhrPm7cZV5pEOleFKiGybrYWmdXteV2sBAQLXuxvg==", "license": "MIT", "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.2.0" + "@lit-labs/ssr-dom-shim": "^1.6.0" } }, "node_modules/@primer/octicons-react": { - "version": "19.21.2", - "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-19.21.2.tgz", - "integrity": "sha512-Bk+S08EpeeWLFscUxwEY8t5z14KxByhIbPG6OiYXSNrkbzN4fmRetnB/C+K1srn4BWuRSwwFxUwvDI2ytgNrFw==", + "version": "19.38.0", + "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-19.38.0.tgz", + "integrity": "sha512-ZzWVrX6kluBaCepsDj/jAxagBOMaRX18LEf723IKpr4E/jdkTdCOtlsN+60Oj7IV9YEVrlUpo9Oky0Tn1heSCA==", "license": "MIT", - "engines": { - "node": ">=8" - }, "peerDependencies": { "react": ">=16.3" } }, "node_modules/@primer/primitives": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-7.17.1.tgz", - "integrity": "sha512-SiPzEb+up1nDpV2NGwNiY8m6sGnF3OUqRb0has5s6T40vq6Li/g3cYVgl+oolEa4DUoNygEPs09jwJt24f/3zg==", - "license": "MIT" + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-11.10.0.tgz", + "integrity": "sha512-ihZxIst4Izry284StKFjpbYvX6R/IBFTLu+qiuAmR93PsKF2hdjoabhSYwKI8iCxOUx94jVP55bI3NoY7STjYQ==", + "license": "MIT", + "workspaces": [ + ".", + "docs/storybook" + ] }, "node_modules/@primer/react": { - "version": "36.27.0", - "resolved": "https://registry.npmjs.org/@primer/react/-/react-36.27.0.tgz", - "integrity": "sha512-dVyp0f9zbbQYQZ6ztfMET43vVaWhvSz+qWirBzpRjDxvCk8vCQsvWrVGUU/PR0kAxxDHf6hqeLG7vcDL229NLA==", + "version": "38.40.1", + "resolved": "https://registry.npmjs.org/@primer/react/-/react-38.40.1.tgz", + "integrity": "sha512-yTd+QTtWXdqJbWQLCnLibwh280j2TKw1+tREEXtJR6lRYiqrE1dnu5ifCsKhsqIYvxqaKZBNWg4KRqiFG6opZA==", "license": "MIT", "dependencies": { - "@github/combobox-nav": "^2.1.5", - "@github/markdown-toolbar-element": "^2.1.0", - "@github/paste-markdown": "^1.4.0", - "@github/relative-time-element": "^4.4.1", - "@github/tab-container-element": "^4.8.0", + "@github/mini-throttle": "^2.1.1", + "@github/relative-time-element": "^5.0.0", "@lit-labs/react": "1.2.1", - "@oddbird/popover-polyfill": "^0.3.1", - "@primer/behaviors": "^1.7.0", - "@primer/live-region-element": "^0.7.0", - "@primer/octicons-react": "^19.9.0", - "@primer/primitives": "^7.16.0", - "@styled-system/css": "^5.1.5", - "@styled-system/props": "^5.1.5", - "@styled-system/theme-get": "^5.1.2", - "@types/react-is": "^18.2.1", - "@types/styled-system": "^5.1.12", - "@types/styled-system__css": "^5.0.16", - "@types/styled-system__theme-get": "^5.0.1", - "clsx": "^1.2.1", + "@oddbird/popover-polyfill": "^0.5.2", + "@primer/behaviors": "^1.10.3", + "@primer/live-region-element": "^0.8.0", + "@primer/octicons-react": "^19.28.1", + "@primer/primitives": "10.x || 11.x", + "@tanstack/react-virtual": "^3.13.18", + "clsx": "^2.1.1", "color2k": "^2.0.3", - "deepmerge": "^4.2.2", - "focus-visible": "^5.2.0", - "fzy.js": "^0.4.1", + "deepmerge": "^4.3.1", + "focus-visible": "^5.2.1", "history": "^5.0.0", + "hsluv": "1.0.1", "lodash.isempty": "^4.4.0", "lodash.isobject": "^3.0.2", - "react-intersection-observer": "^9.4.3", - "react-is": "^18.2.0", - "react-markdown": "8.0.7", - "styled-system": "^5.1.5" + "react-intersection-observer": "^10.0.3" }, "engines": { "node": ">=12", "npm": ">=7" }, "peerDependencies": { - "@types/react": "^18.0.0", - "@types/react-dom": "^18.0.0", - "@types/styled-components": "^5.1.11", - "react": "^18.0.0", - "react-dom": "^18.0.0", - "styled-components": "5.x" + "@types/react": "18.x || 19.x", + "@types/react-dom": "18.x || 19.x", + "@types/react-is": "18.x || 19.x", + "react": "18.x || 19.x", + "react-dom": "18.x || 19.x", + "react-is": "18.x || 19.x" }, "peerDependenciesMeta": { "@types/react": { @@ -682,758 +230,32 @@ "@types/react-dom": { "optional": true }, - "@types/styled-components": { - "optional": true - } - } - }, - "node_modules/@primer/react/node_modules/@types/hast": { - "version": "2.3.10", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", - "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2" - } - }, - "node_modules/@primer/react/node_modules/@types/mdast": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", - "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2" - } - }, - "node_modules/@primer/react/node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "license": "MIT" - }, - "node_modules/@primer/react/node_modules/hast-util-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", - "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@primer/react/node_modules/inline-style-parser": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", - "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==", - "license": "MIT" - }, - "node_modules/@primer/react/node_modules/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==", - "license": "MIT", - "dependencies": { - "@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" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@primer/react/node_modules/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==", - "license": "MIT", - "dependencies": { - "@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" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@primer/react/node_modules/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==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@primer/react/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" - } - ], - "license": "MIT", - "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/@primer/react/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" - } - ], - "license": "MIT", - "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/@primer/react/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" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/@primer/react/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" - } - ], - "license": "MIT", - "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/@primer/react/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" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/@primer/react/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" - } - ], - "license": "MIT", - "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/@primer/react/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" - } - ], - "license": "MIT", - "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/@primer/react/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" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/@primer/react/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" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/@primer/react/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" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/@primer/react/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" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/@primer/react/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" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/@primer/react/node_modules/micromark-util-decode-string": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", - "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/@primer/react/node_modules/micromark-util-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", - "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/@primer/react/node_modules/micromark-util-html-tag-name": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", - "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/@primer/react/node_modules/micromark-util-normalize-identifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", - "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/@primer/react/node_modules/micromark-util-resolve-all": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", - "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/@primer/react/node_modules/micromark-util-sanitize-uri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", - "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/@primer/react/node_modules/micromark-util-subtokenize": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", - "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "node_modules/@primer/react/node_modules/micromark-util-symbol": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", - "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/@primer/react/node_modules/micromark-util-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/@primer/react/node_modules/property-information": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", - "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/@primer/react/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" - }, - "node_modules/@primer/react/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==", - "license": "MIT", - "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/@primer/react/node_modules/remark-parse": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", - "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-from-markdown": "^1.0.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@primer/react/node_modules/remark-rehype": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", - "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^2.0.0", - "@types/mdast": "^3.0.0", - "mdast-util-to-hast": "^12.1.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@primer/react/node_modules/style-to-object": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", - "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", - "license": "MIT", - "dependencies": { - "inline-style-parser": "0.1.1" - } - }, - "node_modules/@primer/react/node_modules/unified": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", - "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "bail": "^2.0.0", - "extend": "^3.0.0", - "is-buffer": "^2.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@primer/react/node_modules/unist-util-is": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", - "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@primer/react/node_modules/unist-util-position": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", - "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@primer/react/node_modules/unist-util-stringify-position": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", - "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@primer/react/node_modules/unist-util-visit": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", - "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^5.1.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@primer/react/node_modules/unist-util-visit-parents": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", - "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@primer/react/node_modules/vfile": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", - "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "is-buffer": "^2.0.0", - "unist-util-stringify-position": "^3.0.0", - "vfile-message": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "@types/react-is": { + "optional": true + } } }, - "node_modules/@primer/react/node_modules/vfile-message": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", - "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz", + "integrity": "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-stringify-position": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.12.tgz", + "integrity": "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==", "cpu": [ "arm64" ], @@ -1448,9 +270,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", - "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.12.tgz", + "integrity": "sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==", "cpu": [ "arm64" ], @@ -1465,9 +287,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.12.tgz", + "integrity": "sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==", "cpu": [ "x64" ], @@ -1482,9 +304,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.12.tgz", + "integrity": "sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==", "cpu": [ "x64" ], @@ -1499,9 +321,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.12.tgz", + "integrity": "sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==", "cpu": [ "arm" ], @@ -1516,9 +338,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.12.tgz", + "integrity": "sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==", "cpu": [ "arm64" ], @@ -1533,9 +355,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.12.tgz", + "integrity": "sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==", "cpu": [ "arm64" ], @@ -1550,9 +372,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.12.tgz", + "integrity": "sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==", "cpu": [ "ppc64" ], @@ -1567,9 +389,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.12.tgz", + "integrity": "sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==", "cpu": [ "s390x" ], @@ -1584,9 +406,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.12.tgz", + "integrity": "sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==", "cpu": [ "x64" ], @@ -1601,9 +423,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.12.tgz", + "integrity": "sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==", "cpu": [ "x64" ], @@ -1618,9 +440,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.12.tgz", + "integrity": "sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==", "cpu": [ "arm64" ], @@ -1634,29 +456,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.12.tgz", + "integrity": "sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==", "cpu": [ "arm64" ], @@ -1671,9 +474,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.12.tgz", + "integrity": "sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==", "cpu": [ "x64" ], @@ -1700,287 +503,470 @@ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "license": "MIT" }, - "node_modules/@styled-system/background": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/background/-/background-5.1.2.tgz", - "integrity": "sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A==", - "license": "MIT", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/border": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/border/-/border-5.1.5.tgz", - "integrity": "sha512-JvddhNrnhGigtzWRCVuAHepniyVi6hBlimxWDVAdcTuk7aRn9BYJUwfHslURtwYFsF5FoEs8Zmr1oZq2M1AP0A==", + "node_modules/@tanstack/react-virtual": { + "version": "3.14.13", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.13.tgz", + "integrity": "sha512-JbDTAwtzZ99aOeCrAfW5EsE5KSq5RWh6Af2dtFwyLIIk48Ja7vm6n4axu/43T3vfjFnEGJalxJ1wyYjSQD6bSg==", "license": "MIT", "dependencies": { - "@styled-system/core": "^5.1.2" + "@tanstack/virtual-core": "3.17.11" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/@styled-system/color": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/color/-/color-5.1.2.tgz", - "integrity": "sha512-1kCkeKDZkt4GYkuFNKc7vJQMcOmTl3bJY3YBUs7fCNM6mMYJeT1pViQ2LwBSBJytj3AB0o4IdLBoepgSgGl5MA==", + "node_modules/@tanstack/virtual-core": { + "version": "3.17.11", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.11.tgz", + "integrity": "sha512-+ILjvtHup6Y2hzQ6YzwMgX1Q+oQpxEGOXCEsCNaPoIP0VxMbizIBTmYTDtkerkIQS8/CbP1BRuyt8V/8BCsy1g==", "license": "MIT", - "dependencies": { - "@styled-system/core": "^5.1.2" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" } }, - "node_modules/@styled-system/core": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/core/-/core-5.1.2.tgz", - "integrity": "sha512-XclBDdNIy7OPOsN4HBsawG2eiWfCcuFt6gxKn1x4QfMIgeO6TOlA2pZZ5GWZtIhCUqEPTgIBta6JXsGyCkLBYw==", + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "license": "MIT", "dependencies": { - "object-assign": "^4.1.1" + "@types/ms": "*" } }, - "node_modules/@styled-system/css": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/css/-/css-5.1.5.tgz", - "integrity": "sha512-XkORZdS5kypzcBotAMPBoeckDs9aSZVkvrAlq5K3xP8IMAUek+x2O4NtwoSgkYkWWzVBu6DGdFZLR790QWGG+A==", + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "license": "MIT" }, - "node_modules/@styled-system/flexbox": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/flexbox/-/flexbox-5.1.2.tgz", - "integrity": "sha512-6hHV52+eUk654Y1J2v77B8iLeBNtc+SA3R4necsu2VVinSD7+XY5PCCEzBFaWs42dtOEDIa2lMrgL0YBC01mDQ==", + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", "license": "MIT", "dependencies": { - "@styled-system/core": "^5.1.2" + "@types/estree": "*" } }, - "node_modules/@styled-system/grid": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/grid/-/grid-5.1.2.tgz", - "integrity": "sha512-K3YiV1KyHHzgdNuNlaw8oW2ktMuGga99o1e/NAfTEi5Zsa7JXxzwEnVSDSBdJC+z6R8WYTCYRQC6bkVFcvdTeg==", + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", "license": "MIT", "dependencies": { - "@styled-system/core": "^5.1.2" + "@types/unist": "*" } }, - "node_modules/@styled-system/layout": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/layout/-/layout-5.1.2.tgz", - "integrity": "sha512-wUhkMBqSeacPFhoE9S6UF3fsMEKFv91gF4AdDWp0Aym1yeMPpqz9l9qS/6vjSsDPF7zOb5cOKC3tcKKOMuDCPw==", + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", "license": "MIT", "dependencies": { - "@styled-system/core": "^5.1.2" + "@types/unist": "*" } }, - "node_modules/@styled-system/position": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/position/-/position-5.1.2.tgz", - "integrity": "sha512-60IZfMXEOOZe3l1mCu6sj/2NAyUmES2kR9Kzp7s2D3P4qKsZWxD1Se1+wJvevb+1TP+ZMkGPEYYXRyU8M1aF5A==", + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.6.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.4.tgz", + "integrity": "sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==", + "dev": true, "license": "MIT", "dependencies": { - "@styled-system/core": "^5.1.2" + "undici-types": "~8.9.0" } }, - "node_modules/@styled-system/props": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/props/-/props-5.1.5.tgz", - "integrity": "sha512-FXhbzq2KueZpGaHxaDm8dowIEWqIMcgsKs6tBl6Y6S0njG9vC8dBMI6WSLDnzMoSqIX3nSKHmOmpzpoihdDewg==", + "node_modules/@types/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", "license": "MIT", "dependencies": { - "styled-system": "^5.1.5" + "csstype": "^3.2.2" } }, - "node_modules/@styled-system/shadow": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/shadow/-/shadow-5.1.2.tgz", - "integrity": "sha512-wqniqYb7XuZM7K7C0d1Euxc4eGtqEe/lvM0WjuAFsQVImiq6KGT7s7is+0bNI8O4Dwg27jyu4Lfqo/oIQXNzAg==", + "node_modules/@types/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", + "devOptional": true, "license": "MIT", - "dependencies": { - "@styled-system/core": "^5.1.2" + "peerDependencies": { + "@types/react": "^19.3.0" } }, - "node_modules/@styled-system/space": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/space/-/space-5.1.2.tgz", - "integrity": "sha512-+zzYpR8uvfhcAbaPXhH8QgDAV//flxqxSjHiS9cDFQQUSznXMQmxJegbhcdEF7/eNnJgHeIXv1jmny78kipgBA==", - "license": "MIT", - "dependencies": { - "@styled-system/core": "^5.1.2" + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/@styled-system/theme-get": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/theme-get/-/theme-get-5.1.2.tgz", - "integrity": "sha512-afAYdRqrKfNIbVgmn/2Qet1HabxmpRnzhFwttbGr6F/mJ4RDS/Cmn+KHwHvNXangQsWw/5TfjpWV+rgcqqIcJQ==", - "license": "MIT", - "dependencies": { - "@styled-system/core": "^5.1.2" + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/@styled-system/typography": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/typography/-/typography-5.1.2.tgz", - "integrity": "sha512-BxbVUnN8N7hJ4aaPOd7wEsudeT7CxarR+2hns8XCX1zp0DFfbWw4xYa/olA0oQaqx7F1hzDg+eRaGzAJbF+jOg==", - "license": "MIT", - "dependencies": { - "@styled-system/core": "^5.1.2" + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/@styled-system/variant": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/variant/-/variant-5.1.5.tgz", - "integrity": "sha512-Yn8hXAFoWIro8+Q5J8YJd/mP85Teiut3fsGVR9CAxwgNfIAiqlYxsk5iHU7VHJks/0KjL4ATSjmbtCDC/4l1qw==", - "license": "MIT", - "dependencies": { - "@styled-system/core": "^5.1.2", - "@styled-system/css": "^5.1.5" + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT", + "license": "Apache-2.0", "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "license": "MIT", - "dependencies": { - "@types/ms": "*" + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } }, - "node_modules/@types/estree-jsx": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", - "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", - "license": "MIT", - "dependencies": { - "@types/estree": "*" + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } }, - "node_modules/@types/node": { - "version": "25.2.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.0.tgz", - "integrity": "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==", + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.3.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", - "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.2.2" + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "devOptional": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^18.0.0" + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/@types/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-zts4lhQn5ia0cF/y2+3V6Riu0MAfez9/LJYavdM8TvcVl+S91A/7VWxyBT8hbRuWspmuCaiGI0F41OJYGrKhRA==", - "license": "MIT", - "dependencies": { - "@types/react": "^18" + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/@types/styled-system": { - "version": "5.1.25", - "resolved": "https://registry.npmjs.org/@types/styled-system/-/styled-system-5.1.25.tgz", - "integrity": "sha512-B1oyjE4oeAbVnkigcB0WqU2gPFuTwLV/KkLa/uJZWFB9JWVKq1Fs0QwodZXZ9Sq6cb9ngY4kDqRY/dictIchjA==", - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/@types/styled-system__css": { - "version": "5.0.22", - "resolved": "https://registry.npmjs.org/@types/styled-system__css/-/styled-system__css-5.0.22.tgz", - "integrity": "sha512-1oOWbdcL1SE2t6hTC3LlwrVHK3Z1Py4KYFehl6NL2XcLxS/L0ELEmN6APNWIYqUywPdeaKlQkRpV5dn0trLjGA==", - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/@types/styled-system__theme-get": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/styled-system__theme-get/-/styled-system__theme-get-5.0.4.tgz", - "integrity": "sha512-dbzwxQ+8x6Bo3EKZMo9M3Knzo77ukwoC/isKW+GAuF5TenXlPkvgzx4t4+Lp0+fKs2M4owSef0KO3gtGW3Hpkw==", - "license": "MIT" - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } }, "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", "license": "ISC" }, "node_modules/@vitejs/plugin-react": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", - "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", + "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==", "dev": true, "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "^1.0.0" + "@rolldown/pluginutils": "^1.0.1" }, "engines": { "node": "^20.19.0 || >=22.12.0" @@ -1988,6 +974,7 @@ "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": "^0.145.0", "vite": "^8.0.0" }, "peerDependenciesMeta": { @@ -1996,75 +983,12 @@ }, "babel-plugin-react-compiler": { "optional": true - } - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "peer": true, - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { + }, + "oxc-transform-react": { "optional": true } } }, - "node_modules/babel-plugin-styled-components": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz", - "integrity": "sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.22.5", - "lodash": "^4.17.21", - "picomatch": "^2.3.1" - }, - "peerDependencies": { - "styled-components": ">= 2" - } - }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -2075,58 +999,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/baseline-browser-mapping": { - "version": "2.11.20", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", - "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", - "license": "Apache-2.0", - "peer": true, - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", - "license": "MIT", - "peer": true, - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -2140,112 +1012,6 @@ "node": ">=8" } }, - "node_modules/browserslist": { - "version": "4.28.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", - "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "baseline-browser-mapping": "^2.11.12", - "caniuse-lite": "^1.0.30001809", - "electron-to-chromium": "^1.5.402", - "node-releases": "^2.0.53", - "update-browserslist-db": "^1.3.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "peer": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelize": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", - "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", - "license": "MIT", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001810", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", - "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0", - "peer": true - }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -2297,97 +1063,28 @@ } }, "node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/color2k": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", - "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.4.tgz", + "integrity": "sha512-OXAPGFRNeLFnUfqDtloYdxkwsJoIdXe28+bjbpJiPqyei2HPa3VHmMCWa0Qe62+U4Ftf9Hj7hRssOkxz7WiWbg==", "license": "MIT" }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT", - "peer": true - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", "license": "MIT", - "peer": true, - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, "node_modules/cross-spawn": { @@ -2395,7 +1092,6 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", - "peer": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2405,28 +1101,6 @@ "node": ">= 8" } }, - "node_modules/css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/css-to-react-native": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", - "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -2472,16 +1146,6 @@ "node": ">=0.10.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", @@ -2514,104 +1178,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/diff": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", - "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "peer": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT", - "peer": true - }, - "node_modules/electron-to-chromium": { - "version": "1.5.420", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.420.tgz", - "integrity": "sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA==", - "license": "ISC", - "peer": true - }, - "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/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "license": "MIT", - "peer": true, - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "peer": true, - "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": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", @@ -2634,22 +1200,11 @@ "url": "https://opencollective.com/unified" } }, - "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/eventsource": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", "license": "MIT", - "peer": true, "dependencies": { "eventsource-parser": "^3.0.1" }, @@ -2658,107 +1213,37 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", - "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", + "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" } }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "peer": true, - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", - "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", - "license": "MIT", - "peer": true, - "dependencies": { - "ip-address": "^10.2.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", - "peer": true - }, - "node_modules/fast-uri": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", - "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true } - ], - "license": "BSD-3-Clause", - "peer": true + } }, "node_modules/fill-range": { "version": "7.1.1", @@ -2773,54 +1258,12 @@ "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "peer": true, - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/focus-visible": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/focus-visible/-/focus-visible-5.2.1.tgz", "integrity": "sha512-8Bx950VD1bWTQJEH/AM6SpEk+SU55aVnp4Ujhuuxy3eMEBCRwBnTBnVXr9YAPvZL3/CNjCa8u4IWfNmEO53whA==", "license": "W3C" }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2836,120 +1279,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/fzy.js": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/fzy.js/-/fzy.js-0.4.1.tgz", - "integrity": "sha512-4sPVXf+9oGhzg2tYzgWe4hgAY0wEbkqeuKVEgdnqX8S8VcLosQsDjb0jV+f5uoQlf8INWId1w0IGoufAoik1TA==", - "license": "MIT" - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "peer": true, - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "license": "MIT", - "peer": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", @@ -2999,32 +1328,11 @@ "@babel/runtime": "^7.7.6" } }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hoist-non-react-statics/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==", - "license": "MIT", - "peer": true - }, - "node_modules/hono": { - "version": "4.13.5", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.5.tgz", - "integrity": "sha512-O6+/eCYRkzzzy0rPWwKLiGBR1nFuUPZynnwjxN1MBA62NNqbT0wQEzQyK2gSO5yDIDB336sXQleAhOHrzlYyKw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=16.9.0" - } + "node_modules/hsluv": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hsluv/-/hsluv-1.0.1.tgz", + "integrity": "sha512-zCaFTiDqBLQjCCFBu0qg7z9ASYPd+Bxx2GDCVZJsnehjK80S+jByqhuFz0pCd2Aw3FSKr18AWbRlwnKR0YdizQ==", + "license": "MIT" }, "node_modules/html-url-attributes": { "version": "3.0.1", @@ -3036,77 +1344,12 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC", - "peer": true - }, "node_modules/inline-style-parser": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", "license": "MIT" }, - "node_modules/ip-address": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz", - "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -3131,29 +1374,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/is-decimal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", @@ -3192,93 +1412,29 @@ "engines": { "node": ">=12" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT", - "peer": true + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/jose": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", - "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/panva" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "peer": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT", - "peer": true - }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "license": "BSD-2-Clause", - "peer": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "peer": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -3292,23 +1448,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -3327,9 +1483,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], @@ -3348,9 +1504,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], @@ -3369,9 +1525,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], @@ -3390,9 +1546,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], @@ -3411,9 +1567,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], @@ -3432,9 +1588,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], @@ -3453,9 +1609,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], @@ -3474,9 +1630,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], @@ -3495,9 +1651,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -3516,9 +1672,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -3536,13 +1692,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "license": "MIT", - "peer": true - }, "node_modules/lodash.isempty": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", @@ -3565,28 +1714,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/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==", - "license": "ISC", - "peer": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", @@ -3597,88 +1724,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/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==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "unist-util-visit": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-definitions/node_modules/@types/mdast": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", - "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2" - } - }, - "node_modules/mdast-util-definitions/node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "license": "MIT" - }, - "node_modules/mdast-util-definitions/node_modules/unist-util-is": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", - "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-definitions/node_modules/unist-util-visit": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", - "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^5.1.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-definitions/node_modules/unist-util-visit-parents": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", - "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/mdast-util-find-and-replace": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", @@ -3696,9 +1741,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -3773,9 +1818,9 @@ } }, "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.1.tgz", + "integrity": "sha512-OuJHqvr455pwu2OaOrir7dbzqs4jlWKOtlu9L6GjcIjjQwNyw02VntQsr/Ek6/A13vgnXfUhBekWRUP1OkNivw==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -3916,9 +1961,9 @@ } }, "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.3.tgz", + "integrity": "sha512-wgyJtgUkUcdU7zci7uuwc/tzoAhm0TswWhaXuvnWolqug+8jY7bgNkBBL0dYBnHgc/tHL7lCzRdPsdUxUKKBqw==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -3926,8 +1971,10 @@ "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", + "micromark-util-character": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" }, @@ -3949,33 +1996,10 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/micromark": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.3.tgz", + "integrity": "sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw==", "funding": [ { "type": "GitHub Sponsors", @@ -3998,6 +2022,7 @@ "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-edit-map": "^1.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", @@ -4008,9 +2033,9 @@ } }, "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.4.tgz", + "integrity": "sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w==", "funding": [ { "type": "GitHub Sponsors", @@ -4027,18 +2052,19 @@ "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", + "micromark-factory-space": "^2.1.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", + "micromark-util-edit-map": "^1.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "micromark-util-types": "^2.0.3" } }, "node_modules/micromark-extension-gfm": { @@ -4116,9 +2142,9 @@ } }, "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.2.tgz", + "integrity": "sha512-pRzm4kDTu0MjlmBkxmS9yYhw60nncfcEwu9NNdPFSQEFXS95ZKyIIyTSHu/o3ReBUrLKYEq+7YaXCRn/bPB4MA==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -4206,9 +2232,9 @@ } }, "node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.1.0.tgz", + "integrity": "sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q==", "funding": [ { "type": "GitHub Sponsors", @@ -4390,6 +2416,25 @@ "micromark-util-symbol": "^2.0.0" } }, + "node_modules/micromark-util-edit-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-edit-map/-/micromark-util-edit-map-1.0.0.tgz", + "integrity": "sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, "node_modules/micromark-util-encode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", @@ -4520,9 +2565,9 @@ "license": "MIT" }, "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.3.tgz", + "integrity": "sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==", "funding": [ { "type": "GitHub Sponsors", @@ -4549,40 +2594,17 @@ "node": ">=8.6" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "mime-db": "^1.54.0" - }, "engines": { - "node": ">=18" + "node": ">=8.6" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "license": "MIT", - "engines": { - "node": ">=4" + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/ms": { @@ -4592,9 +2614,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.18", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", - "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", "dev": true, "funding": [ { @@ -4610,71 +2632,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-releases": { - "version": "2.0.54", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", - "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "peer": true, - "dependencies": { - "wrappy": "1" - } - }, "node_modules/parse-entities": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", @@ -4700,50 +2657,30 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, - "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/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "license": "MIT", - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -4754,15 +2691,14 @@ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.20.0" } }, "node_modules/postcss": { - "version": "8.5.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", - "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", "dev": true, "funding": [ { @@ -4776,138 +2712,53 @@ { "type": "github", "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.17", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT", - "peer": true - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/prop-types/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==", - "license": "MIT" - }, - "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "peer": true, - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", - "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "es-define-property": "^1.0.1", - "side-channel": "^1.1.1" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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, + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, "engines": { - "node": ">= 0.6" + "node": "^10 || ^12 || >=14" } }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", "license": "MIT", - "peer": true, - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.28.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.3.0" } }, "node_modules/react-intersection-observer": { - "version": "9.16.0", - "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.16.0.tgz", - "integrity": "sha512-w9nJSEp+DrW9KmQmeWHQyfaP6b03v+TdXynaoA964Wxt7mdR3An11z4NNCQgL4gKSK7y1ver2Fq+JKH6CWEzUA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-10.1.0.tgz", + "integrity": "sha512-V8HDu3+Llg6OEhOxx8LnUSS0t4VS+1Xk9ZatkI8Jct/H0CwKnqFTCu8NT3q7ghJTghTdIrEMPSWr2dkKPG+gdQ==", "license": "MIT", "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0", @@ -4920,9 +2771,9 @@ } }, "node_modules/react-is": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", - "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.3.0.tgz", + "integrity": "sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA==", "license": "MIT", "peer": true }, @@ -5019,24 +2870,14 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/rolldown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz", + "integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.133.0", + "@oxc-project/types": "=0.152.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -5046,145 +2887,34 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.3", - "@rolldown/binding-darwin-arm64": "1.0.3", - "@rolldown/binding-darwin-x64": "1.0.3", - "@rolldown/binding-freebsd-x64": "1.0.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", - "@rolldown/binding-linux-arm64-gnu": "1.0.3", - "@rolldown/binding-linux-arm64-musl": "1.0.3", - "@rolldown/binding-linux-ppc64-gnu": "1.0.3", - "@rolldown/binding-linux-s390x-gnu": "1.0.3", - "@rolldown/binding-linux-x64-gnu": "1.0.3", - "@rolldown/binding-linux-x64-musl": "1.0.3", - "@rolldown/binding-openharmony-arm64": "1.0.3", - "@rolldown/binding-wasm32-wasi": "1.0.3", - "@rolldown/binding-win32-arm64-msvc": "1.0.3", - "@rolldown/binding-win32-x64-msvc": "1.0.3" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "license": "MIT", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" + "@rolldown/binding-android-arm-eabi": "1.2.12", + "@rolldown/binding-android-arm64": "1.2.12", + "@rolldown/binding-darwin-arm64": "1.2.12", + "@rolldown/binding-darwin-x64": "1.2.12", + "@rolldown/binding-freebsd-x64": "1.2.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", + "@rolldown/binding-linux-arm64-gnu": "1.2.12", + "@rolldown/binding-linux-arm64-musl": "1.2.12", + "@rolldown/binding-linux-ppc64-gnu": "1.2.12", + "@rolldown/binding-linux-s390x-gnu": "1.2.12", + "@rolldown/binding-linux-x64-gnu": "1.2.12", + "@rolldown/binding-linux-x64-musl": "1.2.12", + "@rolldown/binding-openharmony-arm64": "1.2.12", + "@rolldown/binding-win32-arm64-msvc": "1.2.12", + "@rolldown/binding-win32-x64-msvc": "1.2.12" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT", - "peer": true - }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/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/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "peer": true, - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "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/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", - "license": "MIT", - "peer": true + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "license": "MIT" }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "license": "MIT", - "peer": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -5197,91 +2927,14 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } }, - "node_modules/side-channel": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4", - "side-channel-list": "^1.0.1", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", - "peer": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "peer": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "peer": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -5298,16 +2951,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -5340,71 +2983,6 @@ "inline-style-parser": "0.2.7" } }, - "node_modules/styled-components": { - "version": "5.3.11", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", - "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/traverse": "^7.4.5", - "@emotion/is-prop-valid": "^1.1.0", - "@emotion/stylis": "^0.8.4", - "@emotion/unitless": "^0.7.4", - "babel-plugin-styled-components": ">= 1.12.0", - "css-to-react-native": "^3.0.0", - "hoist-non-react-statics": "^3.0.0", - "shallowequal": "^1.1.0", - "supports-color": "^5.5.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/styled-components" - }, - "peerDependencies": { - "react": ">= 16.8.0", - "react-dom": ">= 16.8.0", - "react-is": ">= 16.8.0" - } - }, - "node_modules/styled-system": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/styled-system/-/styled-system-5.1.5.tgz", - "integrity": "sha512-7VoD0o2R3RKzOzPK0jYrVnS8iJdfkKsQJNiLRDjikOpQVqQHns/DXWaPZOH4tIKkhAT7I6wIsy9FWTWh2X3q+A==", - "license": "MIT", - "dependencies": { - "@styled-system/background": "^5.1.2", - "@styled-system/border": "^5.1.5", - "@styled-system/color": "^5.1.2", - "@styled-system/core": "^5.1.2", - "@styled-system/flexbox": "^5.1.2", - "@styled-system/grid": "^5.1.2", - "@styled-system/layout": "^5.1.2", - "@styled-system/position": "^5.1.2", - "@styled-system/shadow": "^5.1.2", - "@styled-system/space": "^5.1.2", - "@styled-system/typography": "^5.1.2", - "@styled-system/variant": "^5.1.5", - "object-assign": "^4.1.1" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "license": "MIT", - "peer": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -5422,37 +3000,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5466,16 +3013,6 @@ "node": ">=8.0" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.6" - } - }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -5496,65 +3033,45 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", - "license": "MIT", - "peer": true, - "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", "dev": true, "license": "Apache-2.0", "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "tsc": "bin/tsc" }, "engines": { - "node": ">=14.17" + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", "dev": true, "license": "MIT" }, @@ -5577,16 +3094,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/unist-util-generated": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", - "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unist-util-is": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", @@ -5655,75 +3162,6 @@ "url": "https://opencollective.com/unified" } }, - "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/update-browserslist-db": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", - "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uvu": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", - "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0", - "diff": "^5.0.0", - "kleur": "^4.0.3", - "sade": "^1.7.3" - }, - "bin": { - "uvu": "bin.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -5753,16 +3191,16 @@ } }, "node_modules/vite": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", + "integrity": "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.11", "tinyglobby": "^0.2.17" }, "bin": { @@ -5779,7 +3217,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", + "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -5852,25 +3290,11 @@ } } }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "license": "ISC", - "peer": true, "dependencies": { "isexe": "^2.0.0" }, @@ -5881,40 +3305,15 @@ "node": ">= 8" } }, - "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==", - "license": "ISC", - "peer": true - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "license": "ISC", - "peer": true - }, "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "license": "ISC", - "peer": true, - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/ui/package.json b/ui/package.json index 9644b72d9e..9507f13600 100644 --- a/ui/package.json +++ b/ui/package.json @@ -5,7 +5,7 @@ "type": "module", "description": "MCP App UIs for github-mcp-server using Primer React", "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^26.0.0" }, "scripts": { "build": "node scripts/build.mjs", @@ -15,21 +15,24 @@ }, "dependencies": { "@github/markdown-toolbar-element": "^2.2.3", - "@modelcontextprotocol/ext-apps": "^1.7.2", - "@primer/octicons-react": "^19.0.0", - "@primer/react": "^36.0.0", - "react": "^18.0.0", - "react-dom": "^18.0.0", + "@modelcontextprotocol/client": "^2.0.0", + "@modelcontextprotocol/ext-apps": "^2.0.3", + "@primer/octicons-react": "^19.38.0", + "@primer/primitives": "^11.10.0", + "@primer/react": "^38.40.1", + "react": "^19.3.0", + "react-dom": "^19.3.0", "react-markdown": "^10.1.0", - "remark-gfm": "^4.0.1" + "remark-gfm": "^4.0.1", + "zod": "^4.2.0" }, "devDependencies": { - "@types/node": "^25.2.0", - "@types/react": "^18.0.0", - "@types/react-dom": "^18.0.0", - "@vitejs/plugin-react": "^6.0.2", - "typescript": "^5.7.0", - "vite": "^8.0.16", + "@types/node": "^26.6.4", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "@vitejs/plugin-react": "^6.1.1", + "typescript": "^7.0.2", + "vite": "^8.3.2", "vite-plugin-singlefile": "^2.3.3" } } diff --git a/ui/src/apps/get-me/App.tsx b/ui/src/apps/get-me/App.tsx index c181fcab90..27ba66a86d 100644 --- a/ui/src/apps/get-me/App.tsx +++ b/ui/src/apps/get-me/App.tsx @@ -1,7 +1,7 @@ import { StrictMode, useState } from "react"; import type React from "react"; import { createRoot } from "react-dom/client"; -import { Avatar, Box, Text, Link, Heading, Spinner } from "@primer/react"; +import { Avatar, Text, Link, Heading, Spinner } from "@primer/react"; import { OrganizationIcon, LocationIcon, @@ -35,30 +35,30 @@ function AvatarWithFallback({ src, login, size }: { src?: string; login: string; if (!src || imgError) { return ( - - + ); } return ( - setImgError(true)} + style={{ marginRight: 16 }} /> ); } @@ -79,43 +79,62 @@ function UserCard({ }); return ( - {/* Header with avatar and name */} - +
- - +
+ {d.name || user.login} - @{user.login} - - + @{user.login} +
+
{/* Info grid */} - +
{d.company && ( <> - +
{d.company} )} {d.location && ( <> - +
{d.location} )} {d.blog && ( <> - +
- +
{d.email} )} - +
{/* Stats */} - - - +
+
+ {d.public_repos ?? 0} - Repos - - - + Repos +
+
+ {d.followers ?? 0} - Followers - - - + Followers +
+
+ {d.following ?? 0} - Following - - - + Following +
+
+ ); } @@ -165,25 +194,25 @@ function GetMeApp() { const content = (() => { if (error) { - return Error: {error.message}; + return Error: {error.message}; } if (!toolResult) { return ( - +
- Loading user data... - + Loading user data... +
); } const textContent = toolResult.content?.find((c: { type: string }) => c.type === "text"); if (!textContent || !("text" in textContent)) { - return No user data in response; + return No user data in response; } try { const userData = JSON.parse(textContent.text as string) as UserData; return void openLink(url)} />; } catch { - return Failed to parse user data; + return Failed to parse user data; } })(); diff --git a/ui/src/apps/issue-write/App.tsx b/ui/src/apps/issue-write/App.tsx index ee48ea5d33..7c14a29ffc 100644 --- a/ui/src/apps/issue-write/App.tsx +++ b/ui/src/apps/issue-write/App.tsx @@ -1,11 +1,10 @@ import { StrictMode, useState, useCallback, useEffect, useMemo, useRef } from "react"; import { createRoot } from "react-dom/client"; import { - Box, Text, TextInput, Button, - Flash, + Banner, Spinner, FormControl, CounterLabel, @@ -13,6 +12,7 @@ import { ActionList, Label, } from "@primer/react"; +import styles from "../../styles.module.css"; import { IssueOpenedIcon, CheckCircleIcon, @@ -296,46 +296,65 @@ function SuccessView({ const issueUrl = issue.html_url || issue.url || issue.URL || "#"; return ( - - - +
- - +
+ {isUpdate ? "Issue updated successfully" : "Issue created successfully"} -
- - + +
- +
- - +
+
{issue.title || submittedTitle} {issue.number && ( - + #{issue.number} )} - + {owner}/{repo} {submittedLabels.length > 0 && ( - +
{submittedLabels.map((label) => (
)} -
- - +
+
+ ); } @@ -1162,9 +1194,9 @@ function CreateIssueApp() { const selectedOptionName = fieldValue.cleared ? undefined : fieldValue.optionName; const selectedOption = field.options.find((option) => option.name === selectedOptionName); return ( - +
- + {selectedOption ? selectedOption.name : "Select option"} @@ -1179,15 +1211,15 @@ function CreateIssueApp() { onSelect={() => updateIssueFieldValue(field.name, { optionName: option.name })} > - @@ -1198,18 +1230,26 @@ function CreateIssueApp() { - +
); } return ( updateIssueFieldValue(field.name, { value: e.target.value })} + onChange={(e) => + updateIssueFieldValue(field.name, { value: e.target.value }) + } block contrast - sx={{ flex: 1 }} + style={{ flex: 1 }} /> ); }; @@ -1217,17 +1257,22 @@ function CreateIssueApp() { const body_node = (() => { if (appError) { return ( - - Connection error: {appError.message} - + ); } if (!app) { return ( - +
- +
); } @@ -1246,52 +1291,71 @@ function CreateIssueApp() { } return ( - - {/* Repository picker */} - - + {/* Repository picker */} +
+
span:last-child": { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }} - > + size="small" + leadingVisual={selectedRepo?.isPrivate ? LockIcon : RepoIcon} + style={{ maxWidth: "100%", overflow: "hidden" }} + className={styles.truncateLastChild} + > {selectedRepo ? selectedRepo.fullName : "Select repository"} - +
setRepoFilter(e.target.value)} - sx={{ width: "100%" }} - size="small" - autoFocus - /> - + placeholder="Search repositories..." + value={repoFilter} + onChange={(e) => setRepoFilter(e.target.value)} + size="small" + autoFocus + style={{ width: "100%" }} + /> +
{repoSearchLoading ? ( - +
- +
) : repoSearchResults.length > 0 ? ( repoSearchResults.map((r) => ( ) : ( - - +
+ Type to search repositories... - +
)}
- - +
+
{/* Error banner */} {error && ( - - {error} - + )} {/* Title */} - - + + Title {/* Description */} - +
+ as="label" + style={{ + fontWeight: 500, + fontSize: 14, + display: "block", + marginBottom: 8, + }} + > Description - +
{/* Metadata section */} - +
{/* Labels dropdown */} Labels {selectedLabels.length > 0 && ( - {selectedLabels.length} + {selectedLabels.length} )} - +
- +
{labelsLoading ? ( @@ -1420,14 +1510,14 @@ function CreateIssueApp() { }} > - +
{label.text} @@ -1442,11 +1532,18 @@ function CreateIssueApp() { Assignees {selectedAssignees.length > 0 && ( - {selectedAssignees.length} + {selectedAssignees.length} )} - +
- +
{assigneesLoading ? ( @@ -1569,18 +1666,23 @@ function CreateIssueApp() {
- +
{/* Fields section */} {availableIssueFields.length > 0 && ( - - +
+ Fields - +
{availableIssueFields.map((field) => { const fieldValue = fieldValues[field.name]; const hasFieldValue = @@ -1590,56 +1692,95 @@ function CreateIssueApp() { (fieldValue.value !== undefined && fieldValue.value !== "")); return ( - - +
+ {field.name} {field.description && ( - + {field.description} )} - +
{renderIssueFieldInput(field)} {hasFieldValue && ( )} - - +
+
); })} -
- +
+
)} {/* Selected labels display */} {selectedLabels.length > 0 && ( - +
{selectedLabels.map((label) => ( ))} - +
)} {/* Selected metadata display */} {(selectedAssignees.length > 0 || selectedMilestone) && ( - +
{selectedAssignees.length > 0 && ( Assigned to: {selectedAssignees.map((a) => a.text).join(", ")} @@ -1648,36 +1789,51 @@ function CreateIssueApp() { {selectedMilestone && ( Milestone: {selectedMilestone.text} )} - +
)} {/* State and submit actions */} - +
{isUpdateMode && ( - +
{currentState === "open" ? ( <> - - + size="small" + style={{ + marginLeft: "-1px", + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, + }} + > {selectedStateReason.label} @@ -1695,18 +1851,18 @@ function CreateIssueApp() { - +
{stateReason === "duplicate" && ( - - Duplicate of + + Duplicate of setDuplicateOf(e.target.value)} - size="small" - sx={{ width: 140 }} - /> + type="number" + placeholder="Issue number" + value={duplicateOf} + onChange={(e) => setDuplicateOf(e.target.value)} + size="small" + style={{ width: 140 }} + /> )} @@ -1719,7 +1875,7 @@ function CreateIssueApp() { Reopen issue )} -
+
)} -
-
+
+ ); })(); diff --git a/ui/src/apps/pr-edit/App.tsx b/ui/src/apps/pr-edit/App.tsx index 5b4995affc..791217f33a 100644 --- a/ui/src/apps/pr-edit/App.tsx +++ b/ui/src/apps/pr-edit/App.tsx @@ -1,11 +1,10 @@ import { StrictMode, useState, useCallback, useEffect, useMemo } from "react"; import { createRoot } from "react-dom/client"; import { - Box, Text, TextInput, Button, - Flash, + Banner, Spinner, FormControl, ActionMenu, @@ -15,6 +14,7 @@ import { CounterLabel, Label, } from "@primer/react"; +import styles from "../../styles.module.css"; import { GitPullRequestIcon, CheckCircleIcon, @@ -169,46 +169,65 @@ function SuccessView({ const prUrl = pr.html_url || pr.url || pr.URL || "#"; return ( - - - +
- - +
+ Pull request updated successfully -
- - + + + ); } @@ -516,9 +541,16 @@ function EditPRApp() { if (!app && !appError) { return ( - +
- +
); } @@ -526,7 +558,7 @@ function EditPRApp() { if (appError) { return ( - {appError.message} + ); } @@ -534,9 +566,16 @@ function EditPRApp() { if (toolInput === null) { return ( - +
- +
); } @@ -544,56 +583,78 @@ function EditPRApp() { if (!owner || !repo || !pullNumber) { return ( - Pull request owner, repo, and pullNumber are required. + ); } return ( - - - +
- - - #{pullNumber} · {owner}/{repo} +
+
+ #{pullNumber} · {owner}/{repo} {title && ( - + {title} )} - - +
+ - {error && {error}} + {error && } {isLoadingPR && !initialValues ? ( - +
- Loading pull request... - + Loading pull request... +
) : ( <> - - Title + + Title setTitle(e.target.value)} @@ -603,22 +664,42 @@ function EditPRApp() { /> - - +
+ Description - - - - base +
+ +
+ base - span": { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }}> + {baseBranch || "Select base"} - +
- +
{branchesLoading ? ( Loading... @@ -647,11 +728,27 @@ function EditPRApp() {
- - - - - state +
+ +
+
+ state - - - +
+ + +
+ +
+ reviewers - + {selectedReviewers.length === 0 ? ( "No reviewers" ) : ( <> Reviewers - {selectedReviewers.length} + {selectedReviewers.length} )} - +
- +
{reviewersLoading ? ( Loading... @@ -734,28 +857,51 @@ function EditPRApp() {
{selectedReviewers.length > 0 && ( - +
{selectedReviewers.map((reviewer) => ( ))} - +
)} -
- - - +
+ +
+ - +
)} -
+
); } diff --git a/ui/src/apps/pr-write/App.tsx b/ui/src/apps/pr-write/App.tsx index 6975434bea..a7014a544e 100644 --- a/ui/src/apps/pr-write/App.tsx +++ b/ui/src/apps/pr-write/App.tsx @@ -1,11 +1,10 @@ import { StrictMode, useState, useCallback, useEffect, useMemo } from "react"; import { createRoot } from "react-dom/client"; import { - Box, Text, TextInput, Button, - Flash, + Banner, Spinner, FormControl, ActionMenu, @@ -15,6 +14,7 @@ import { CounterLabel, Label, } from "@primer/react"; +import styles from "../../styles.module.css"; import { GitPullRequestIcon, CheckCircleIcon, @@ -82,46 +82,65 @@ function SuccessView({ const prUrl = pr.html_url || pr.url || pr.URL || "#"; return ( - - - +
- - +
+ Pull request created successfully -
- - + + + ); } @@ -462,9 +487,16 @@ function CreatePRApp() { if (!app && !appError) { return ( - +
- +
); } @@ -472,59 +504,78 @@ function CreatePRApp() { if (appError) { return ( - {appError.message} + ); } return ( - {/* Repository picker */} - - +
span:last-child": { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }} + style={{ maxWidth: "100%", overflow: "hidden" }} + className={styles.truncateLastChild} > {selectedRepo ? selectedRepo.fullName : "Select repository"} - +
setRepoFilter(e.target.value)} - sx={{ width: "100%" }} - size="small" - autoFocus - /> - + placeholder="Search repositories..." + value={repoFilter} + onChange={(e) => setRepoFilter(e.target.value)} + size="small" + autoFocus + style={{ width: "100%" }} + /> +
{repoSearchLoading ? ( - +
- +
) : repoSearchResults.length > 0 ? ( repoSearchResults.map((r) => ( ) : ( - - Type to search repositories... - +
+ Type to search repositories... +
)}
- - +
+ {/* Branch selectors */} - - - base +
+
+ base - span": { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }}> + {baseBranch || "Select base"} - +
- +
{branchesLoading ? ( Loading... @@ -604,19 +685,39 @@ function CreatePRApp() {
- - - ← +
- - compare + ← + +
+ compare - span": { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }}> + {headBranch || "Select head"} - +
- +
{branchesLoading ? ( Loading... @@ -644,15 +745,15 @@ function CreatePRApp() {
- - +
+
{/* Error banner */} - {error && {error}} + {error && } {/* Title */} - - Title + + Title setTitle(e.target.value)} @@ -663,29 +764,55 @@ function CreatePRApp() { {/* Description */} - - +
+ Description - +
{/* Reviewers */} - - reviewers +
+ reviewers - + {selectedReviewers.length === 0 ? ( "No reviewers" ) : ( <> Reviewers - {selectedReviewers.length} + {selectedReviewers.length} )} - +
- +
{reviewersLoading ? ( Loading... @@ -737,29 +864,52 @@ function CreatePRApp() {
{selectedReviewers.length > 0 && ( - +
{selectedReviewers.map((reviewer) => ( ))} - +
)} -
+
{/* Options and Submit */} - - +
+ @@ -810,8 +966,8 @@ function CreatePRApp() { - - +
+
); } diff --git a/ui/src/components/AppProvider.tsx b/ui/src/components/AppProvider.tsx index e27bf96a02..9258ff99d6 100644 --- a/ui/src/components/AppProvider.tsx +++ b/ui/src/components/AppProvider.tsx @@ -1,4 +1,8 @@ -import { ThemeProvider, BaseStyles, Box } from "@primer/react"; +import { BaseStyles } from "@primer/react"; +import { ThemeProvider } from "@primer/react/next"; +import "@primer/primitives/dist/css/primitives.css"; +import "@primer/primitives/dist/css/functional/themes/light.css"; +import "@primer/primitives/dist/css/functional/themes/dark.css"; import type { ReactNode, CSSProperties } from "react"; import { useEffect, useMemo } from "react"; import type { McpUiHostContext } from "@modelcontextprotocol/ext-apps"; @@ -44,10 +48,10 @@ export function AppProvider({ children, hostContext }: AppProviderProps) { return ( - +
{children} - +
); diff --git a/ui/src/components/FeedbackFooter.tsx b/ui/src/components/FeedbackFooter.tsx index 10fbdf44e6..c8f4c38403 100644 --- a/ui/src/components/FeedbackFooter.tsx +++ b/ui/src/components/FeedbackFooter.tsx @@ -1,17 +1,19 @@ -import { Box, Text } from "@primer/react"; +import { Text } from "@primer/react"; export function FeedbackFooter() { return ( - - +
+ Help us improve MCP Apps support in the GitHub MCP Server
github.com/github/github-mcp-server/issues/new?template=insiders-feedback.md
- +
); } diff --git a/ui/src/components/MarkdownEditor.tsx b/ui/src/components/MarkdownEditor.tsx index 5ba25932d0..fde12dae9a 100644 --- a/ui/src/components/MarkdownEditor.tsx +++ b/ui/src/components/MarkdownEditor.tsx @@ -6,7 +6,8 @@ * @see https://github.com/github/markdown-toolbar-element */ import { useId, useRef, useState, useEffect } from "react"; -import { Box, Text, Button, IconButton, useTheme } from "@primer/react"; +import { Text, Button, IconButton, useTheme } from "@primer/react"; +import styles from "../styles.module.css"; import { BoldIcon, ItalicIcon, @@ -25,7 +26,7 @@ import remarkGfm from "remark-gfm"; import "@github/markdown-toolbar-element"; // Declare types for the web component elements -declare global { +declare module "react/jsx-runtime" { namespace JSX { interface IntrinsicElements { "markdown-toolbar": React.DetailedHTMLProps< @@ -168,41 +169,48 @@ export function MarkdownEditor({ }; return ( - {/* Header with tabs and toolbar */} - {/* Write/Preview tabs */} - +
@@ -210,24 +218,32 @@ export function MarkdownEditor({ size="small" variant="invisible" onClick={() => setViewMode("preview")} - sx={{ - fontWeight: viewMode === "preview" ? "semibold" : "normal", - color: viewMode === "preview" ? "fg.default" : "fg.muted", - bg: viewMode === "preview" ? "actionListItem.default.hoverBg" : "transparent", - borderRadius: 2, - "&:hover": { - color: "fg.default", - }, + style={{ + fontWeight: viewMode === "preview" ? 500 : 400, + backgroundColor: + viewMode === "preview" + ? "var(--control-transparent-bgColor-hover)" + : "transparent", + borderRadius: 6, }} + className={styles.editorTab} + data-active={viewMode === "preview"} > Preview - +
{/* Toolbar - uses GitHub's official markdown-toolbar-element */} {viewMode === "write" && ( - +
- @@ -302,10 +319,10 @@ export function MarkdownEditor({ variant="invisible" /> - +
)} -
+ {/* Content area */} {viewMode === "write" ? ( @@ -333,115 +350,51 @@ export function MarkdownEditor({ }} /> ) : ( - :first-child": { mt: 0 }, - // GitHub Flavored Markdown styles - "& h1, & h2, & h3, & h4, & h5, & h6": { - mt: 3, - mb: 2, - fontWeight: "semibold", - lineHeight: 1.25, - }, - "& h1": { fontSize: 4, borderBottom: "1px solid", borderColor: "border.default", pb: 2 }, - "& h2": { fontSize: 3, borderBottom: "1px solid", borderColor: "border.default", pb: 2 }, - "& h3": { fontSize: 2 }, - "& p": { my: 2 }, - "& ul, & ol": { pl: 4, my: 2 }, - "& li": { my: 1 }, - "& code": { - bg: "neutral.muted", - px: 1, - py: "2px", - borderRadius: 1, - fontFamily: "mono", - fontSize: "85%", - }, - "& pre": { - bg: "neutral.muted", - p: 3, - borderRadius: 2, - overflow: "auto", - my: 2, - }, - "& pre code": { - bg: "transparent", - p: 0, - }, - "& blockquote": { - borderLeft: "4px solid", - borderColor: "border.default", - pl: 3, - ml: 0, - mr: 0, - my: 2, - color: "fg.muted", - bg: "canvas.subtle", - }, - "& a": { - color: "accent.fg", - textDecoration: "none", - "&:hover": { textDecoration: "underline" }, - }, - "& table": { - borderCollapse: "collapse", - width: "100%", - my: 2, - }, - "& th, & td": { - border: "1px solid", - borderColor: "border.default", - p: 2, - }, - "& th": { - bg: "canvas.subtle", - fontWeight: "semibold", - }, - "& input[type='checkbox']": { - mr: 2, - }, - "& hr": { - border: "none", - borderTop: "1px solid", - borderColor: "border.default", - my: 3, - }, - }}> + color: "var(--fgColor-default)", + }} + className={styles.markdownPreview} + > {value ? ( {value} ) : ( - + Nothing to preview )} - + )} {/* Footer */} - - + Markdown is supported - -
+ + ); } diff --git a/ui/src/hooks/useMcpApp.ts b/ui/src/hooks/useMcpApp.ts index e4146a234f..f45276abf0 100644 --- a/ui/src/hooks/useMcpApp.ts +++ b/ui/src/hooks/useMcpApp.ts @@ -5,7 +5,7 @@ import type { McpUiHostContext, McpUiUpdateModelContextRequest, } from "@modelcontextprotocol/ext-apps"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/client"; import { useState, useCallback, useEffect } from "react"; interface UseMcpAppOptions { diff --git a/ui/src/lib/toolResult.ts b/ui/src/lib/toolResult.ts index e985537f49..cb39015754 100644 --- a/ui/src/lib/toolResult.ts +++ b/ui/src/lib/toolResult.ts @@ -1,4 +1,4 @@ -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/client"; /** * Returns the parsed JSON payload of a *completed* tool result, or `null` when diff --git a/ui/src/styles.module.css b/ui/src/styles.module.css new file mode 100644 index 0000000000..cfe72d499b --- /dev/null +++ b/ui/src/styles.module.css @@ -0,0 +1,148 @@ +.truncateLastChild > span:last-child { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.truncateChildren > span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.editorTab { + color: var(--fgColor-muted); +} + +.editorTab[data-active="true"], +.editorTab:hover { + color: var(--fgColor-default); +} + +.markdownPreview > :first-child { + margin-top: 0; +} + +.markdownPreview h1, +.markdownPreview h2, +.markdownPreview h3, +.markdownPreview h4, +.markdownPreview h5, +.markdownPreview h6 { + margin-top: 16px; + margin-bottom: 8px; + font-weight: 500; + line-height: 1.25; +} + +.markdownPreview h1 { + font-size: 24px; + border-bottom: 1px solid; + border-color: var(--borderColor-default); + padding-bottom: 8px; +} + +.markdownPreview h2 { + font-size: 20px; + border-bottom: 1px solid; + border-color: var(--borderColor-default); + padding-bottom: 8px; +} + +.markdownPreview h3 { + font-size: 16px; +} + +.markdownPreview p { + margin-top: 8px; + margin-bottom: 8px; +} + +.markdownPreview ul, +.markdownPreview ol { + padding-left: 24px; + margin-top: 8px; + margin-bottom: 8px; +} + +.markdownPreview li { + margin-top: 4px; + margin-bottom: 4px; +} + +.markdownPreview code { + background-color: var(--bgColor-neutral-muted); + padding-left: 4px; + padding-right: 4px; + padding-top: 2px; + padding-bottom: 2px; + border-radius: 3px; + font-family: + ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; + font-size: 85%; +} + +.markdownPreview pre { + background-color: var(--bgColor-neutral-muted); + padding: 16px; + border-radius: 6px; + overflow: auto; + margin-top: 8px; + margin-bottom: 8px; +} + +.markdownPreview pre code { + background-color: transparent; + padding: 0; +} + +.markdownPreview blockquote { + border-left: 4px solid; + border-color: var(--borderColor-default); + padding-left: 16px; + margin-left: 0; + margin-right: 0; + margin-top: 8px; + margin-bottom: 8px; + color: var(--fgColor-muted); + background-color: var(--bgColor-muted); +} + +.markdownPreview a { + color: var(--fgColor-accent); + text-decoration: none; +} +.markdownPreview a:hover { + text-decoration: underline; +} + +.markdownPreview table { + border-collapse: collapse; + width: 100%; + margin-top: 8px; + margin-bottom: 8px; +} + +.markdownPreview th, +.markdownPreview td { + border: 1px solid; + border-color: var(--borderColor-default); + padding: 8px; +} + +.markdownPreview th { + background-color: var(--bgColor-muted); + font-weight: 500; +} + +.markdownPreview input[type="checkbox"] { + margin-right: 8px; +} + +.markdownPreview hr { + border: none; + border-top: 1px solid; + border-color: var(--borderColor-default); + margin-top: 16px; + margin-bottom: 16px; +}