diff --git a/actions/setup/js/github_rate_limit_logger.cjs b/actions/setup/js/github_rate_limit_logger.cjs index 1ddd7d717c1..4959c4a4c17 100644 --- a/actions/setup/js/github_rate_limit_logger.cjs +++ b/actions/setup/js/github_rate_limit_logger.cjs @@ -183,11 +183,23 @@ function createRateLimitAwareGithub(github) { get(target, method) { const fn = target[method]; if (typeof fn !== "function") return fn; - return async (/** @type {any[]} */ ...args) => { + const wrapper = async (/** @type {any[]} */ ...args) => { const response = await fn.apply(target, args); logRateLimitFromResponse(response, `${namespaceName}.${String(method)}`); return response; }; + // Wrap the wrapper in a Proxy so that Octokit-specific property accesses + // (e.g. .endpoint, used by github.paginate()) fall back to the original fn. + // Without this, github.paginate(github.rest.checks.listForRef, ...) throws + // "route.endpoint is not a function" because the wrapper does not have + // the .endpoint decorator that Octokit endpoint methods carry. + return new Proxy(wrapper, { + get(wrapperTarget, prop) { + const own = Reflect.get(wrapperTarget, prop); + if (own !== undefined) return own; + return Reflect.get(fn, prop); + }, + }); }, }); } diff --git a/actions/setup/js/github_rate_limit_logger.test.cjs b/actions/setup/js/github_rate_limit_logger.test.cjs index 2f695c7124a..eea782cc148 100644 --- a/actions/setup/js/github_rate_limit_logger.test.cjs +++ b/actions/setup/js/github_rate_limit_logger.test.cjs @@ -354,4 +354,49 @@ describe("createRateLimitAwareGithub", () => { expect(first.remaining).toBe(4999); expect(second.remaining).toBe(4998); }); + + it("preserves .endpoint on the wrapped method for github.paginate() compatibility", () => { + // Octokit endpoint-decorated methods (e.g. github.rest.checks.listForRef) carry + // a .endpoint property used by github.paginate() internally. The proxy must + // forward .endpoint to the original function so paginate() doesn't throw + // "route.endpoint is not a function". + const endpointObj = { merge: vi.fn(), defaults: vi.fn() }; + const mockListForRef = vi.fn().mockResolvedValue({ data: [], headers: {} }); + mockListForRef.endpoint = endpointObj; + + const mockGithub = { + rest: { + checks: { listForRef: mockListForRef }, + }, + }; + + const gh = createRateLimitAwareGithub(mockGithub); + const wrapped = gh.rest.checks.listForRef; + + // The wrapped function must expose .endpoint from the original + expect(typeof wrapped).toBe("function"); + expect(wrapped.endpoint).toBe(endpointObj); + expect(wrapped.endpoint.merge).toBe(endpointObj.merge); + }); + + it("allows github.paginate() to call endpoint.merge on the wrapped method", () => { + // Simulate how @octokit/plugin-paginate-rest v9+ uses the endpoint method: + // const options = route.endpoint.merge(parameters) + const mergedOptions = { url: "/repos/o/r/commits/main/check-runs", per_page: 100 }; + const endpointObj = { merge: vi.fn().mockReturnValue(mergedOptions) }; + const mockListForRef = vi.fn().mockResolvedValue({ data: [], headers: {} }); + mockListForRef.endpoint = endpointObj; + + const mockGithub = { + rest: { checks: { listForRef: mockListForRef } }, + }; + + const gh = createRateLimitAwareGithub(mockGithub); + const wrapped = gh.rest.checks.listForRef; + + // paginate() would call this internally + const result = wrapped.endpoint.merge({ owner: "o", repo: "r", ref: "main", per_page: 100 }); + expect(result).toBe(mergedOptions); + expect(endpointObj.merge).toHaveBeenCalledWith({ owner: "o", repo: "r", ref: "main", per_page: 100 }); + }); }); diff --git a/actions/setup/setup.sh b/actions/setup/setup.sh index 50c0c80f3f0..4f3f823f19c 100755 --- a/actions/setup/setup.sh +++ b/actions/setup/setup.sh @@ -216,6 +216,7 @@ MCP_SCRIPTS_FILES=( "read_buffer.cjs" "generate_mcp_scripts_config.cjs" "setup_globals.cjs" + "github_rate_limit_logger.cjs" "error_helpers.cjs" "error_codes.cjs" "constants.cjs" @@ -288,6 +289,7 @@ SAFE_OUTPUTS_FILES=( "write_large_content_to_file.cjs" "generate_compact_schema.cjs" "setup_globals.cjs" + "github_rate_limit_logger.cjs" "error_helpers.cjs" "error_codes.cjs" "constants.cjs"