@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
166 lines • 7.75 kB
JavaScript
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import { githubPages } from "./paginate.js";
import { graphqlData } from "./repo.js";
function path(ref, tail = '') {
return `/repos/${ref.owner}/${ref.repo}/pulls${tail}`;
}
export function listPulls(transport, ref, params, limit, include) {
return githubPages(transport, path(ref), {
params,
limit,
...(include === undefined ? {} : { include }),
});
}
export function getPull(transport, ref, number) {
return transport.get(path(ref, `/${String(number)}`));
}
/**
* The selected fields of one pull request, over GraphQL, as gh's
* PullRequestByNumber asks for them for `pr view --json`: one query naming
* only what was asked for. A selection that reads the page of a connection
* after `$endCursor` is given that cursor as `endCursor`.
*
* Args:
* transport (GitHubTransport): the API client.
* ref (RepoRef): the repository.
* number (number): the pull request.
* selection (string): the GraphQL selection inside `pullRequest { }`.
* endCursor (string | undefined): the cursor `$endCursor` carries.
*/
export async function pullRequestFields(transport, ref, number, selection, endCursor) {
const variables = { owner: ref.owner, repo: ref.repo, pr_number: number };
if (endCursor !== undefined)
variables.endCursor = endCursor;
const cursor = endCursor === undefined ? '' : ', $endCursor: String';
const data = await graphqlData(transport, `query PullRequestByNumber($owner: String!, $repo: String!, $pr_number: Int!${cursor}) {\n` +
' repository(owner: $owner, name: $repo) {\n' +
` pullRequest(number: $pr_number) {${selection}}\n` +
' }\n }', variables);
const repository = data.repository;
const pull = repository?.pullRequest;
return pull !== null && typeof pull === 'object' ? pull : {};
}
const PULL_LIST = ' query PullRequestList(\n $owner: String!,\n $repo: String!,\n' +
' $limit: Int!,\n $endCursor: String,\n $baseBranch: String,\n' +
' $headBranch: String,\n $state: [PullRequestState!] = OPEN\n ) {\n' +
' repository(owner: $owner, name: $repo) {\n pullRequests(\n' +
' states: $state,\n baseRefName: $baseBranch,\n' +
' headRefName: $headBranch,\n first: $limit,\n' +
' after: $endCursor,\n orderBy: {field: CREATED_AT, direction: DESC}\n' +
' ) {\n totalCount\n nodes {\n ...pr\n' +
' }\n pageInfo {\n hasNextPage\n endCursor\n' +
' }\n }\n }\n }';
/**
* The selected fields of a repository's pull requests, over GraphQL, as
* gh's PullRequestList asks for them for `pr list --json`: newest first, a
* page of up to 100 at a time until `limit`. A pull request a later page
* repeats is listed once, which gh can only tell when the line asked for
* `number`.
*
* Args:
* transport (GitHubTransport): the API client.
* ref (RepoRef): the repository.
* filter (PullListFilter): the states and the base and head branches.
* limit (number): how many pull requests at most.
* selection (string): the GraphQL selection for each pull request.
*/
export async function listPullRequestFields(transport, ref, filter, limit, selection) {
const query = `fragment pr on PullRequest{${selection}}\n${PULL_LIST}`;
const rows = [];
const seen = new Set();
let cursor = null;
let pageLimit = Math.min(limit, 100);
while (rows.length < limit) {
const variables = {
owner: ref.owner,
repo: ref.repo,
limit: pageLimit,
state: filter.states,
};
if (filter.base !== undefined && filter.base !== '')
variables.baseBranch = filter.base;
if (filter.head !== undefined && filter.head !== '')
variables.headBranch = filter.head;
if (cursor !== null)
variables.endCursor = cursor;
const data = await graphqlData(transport, query, variables);
const page = data.repository?.pullRequests;
for (const node of page?.nodes ?? []) {
const number = node.number;
if (typeof number === 'number' && number > 0) {
if (seen.has(number))
continue;
seen.add(number);
}
rows.push(node);
if (rows.length === limit)
break;
}
const next = page?.pageInfo?.endCursor ?? null;
if (page?.pageInfo?.hasNextPage !== true || next === null || next === cursor)
break;
cursor = next;
pageLimit = Math.min(pageLimit, limit - rows.length);
}
return rows;
}
export function createPull(transport, ref, body) {
return transport.request('POST', path(ref), body);
}
export function editPull(transport, ref, number, body) {
return transport.request('PATCH', path(ref, `/${String(number)}`), body);
}
export function mergePull(transport, ref, number, body) {
return transport.request('PUT', path(ref, `/${String(number)}/merge`), Object.keys(body).length === 0 ? undefined : body);
}
export async function commentPull(transport, ref, number, body) {
await getPull(transport, ref, number);
return transport.request('POST', `/repos/${ref.owner}/${ref.repo}/issues/${String(number)}/comments`, { body });
}
export async function diffPull(transport, ref, number) {
const value = await transport.request('GET', path(ref, `/${String(number)}`), undefined, undefined, { Accept: 'application/vnd.github.v3.diff' });
return typeof value === 'string' ? value : '';
}
const STATUS_CONCLUSIONS = ['error', 'failure', 'success'];
function statusCheck(row) {
const state = typeof row.state === 'string' ? row.state : '';
const done = STATUS_CONCLUSIONS.includes(state);
return {
name: row.context ?? '',
status: done ? 'completed' : state,
conclusion: done ? state : null,
details_url: row.target_url ?? '',
output: { summary: row.description ?? '' },
started_at: row.created_at ?? null,
completed_at: row.updated_at ?? null,
};
}
export async function commitStatuses(transport, ref, sha) {
const value = (await transport.get(`/repos/${ref.owner}/${ref.repo}/commits/${sha}/status`));
const rows = value?.statuses;
if (!Array.isArray(rows))
return [];
return rows.filter((row) => typeof row === 'object' && row !== null);
}
export async function pullChecks(transport, ref, number, limit = 100) {
const pull = (await getPull(transport, ref, number));
const sha = pull.head?.sha;
if (typeof sha !== 'string')
return [];
const runs = await githubPages(transport, `/repos/${ref.owner}/${ref.repo}/commits/${sha}/check-runs`, { limit, key: 'check_runs' });
const statuses = await commitStatuses(transport, ref, sha);
return [...runs, ...statuses.map(statusCheck)];
}
//# sourceMappingURL=pull.js.map