Files
deepseek-harness/.github/issue-management/pull-request.mjs
T

127 lines
5.0 KiB
JavaScript

/** Read-only PR snapshots and policy preflight/check workflow entry points. */
import fs from 'node:fs'
import process from 'node:process'
import config from './config.json' with { type: 'json' }
import { api, projectContext } from './github.mjs'
import {
parseReferences,
retainIssueReferences,
requiresPullRequestPolicy,
validatePullRequest,
} from './rules.mjs'
/**
* Resolve all same-repository body references with REST, excluding pull-request numbers.
* @param {number} number Pull-request number.
* @param {{body?: string}} pull Current REST pull-request data.
* @returns {Promise<object>} Issue-only references and placeholder priorities; never reads Project data.
*/
export async function resolvingReferencesSnapshot(number, pull) {
const references = parseReferences({
body: pull.body ?? '',
repository: `${config.organization}/${config.repository}`,
})
const issues = new Map()
for (const issueNumber of references.all) {
const issue = await api(
`/repos/${config.organization}/${config.repository}/issues/${issueNumber}`,
)
if (!issue.pull_request) issues.set(issueNumber, { priority: null })
}
return {
number,
references: retainIssueReferences(references, issues),
issues,
}
}
/**
* Read current PR policy inputs, skipping references for exempt PRs.
* @param {number} number Pull-request number.
* @param {boolean} includeProject Read Project Priority for resolving Issues when true.
* @returns {Promise<object>} Policy snapshot; rejects any failed read and performs no writes.
*/
export async function pullRequestSnapshot(number, includeProject = true) {
const pull = await api(`/repos/${config.organization}/${config.repository}/pulls/${number}`)
const snapshot = {
number,
isDraft: pull.draft,
authorType: pull.user?.type ?? 'User',
reviewRequestCount: 0,
reviewCount: 0,
labels: pull.labels.map((label) => label.name),
references: { all: [], resolving: [], related: [] },
issues: new Map(),
}
if (snapshot.isDraft || ['Bot', 'App'].includes(snapshot.authorType)) return snapshot
const [reviewRequests, reviews] = await Promise.all([
api(`/repos/${config.organization}/${config.repository}/pulls/${number}/requested_reviewers`),
api(`/repos/${config.organization}/${config.repository}/pulls/${number}/reviews?per_page=100`),
])
snapshot.reviewRequestCount = reviewRequests.users.length + reviewRequests.teams.length
snapshot.reviewCount = reviews.length
if (!requiresPullRequestPolicy(snapshot)) return snapshot
Object.assign(snapshot, await resolvingReferencesSnapshot(number, pull))
if (includeProject) {
for (const issueNumber of snapshot.references.resolving) {
const context = await projectContext(issueNumber)
snapshot.issues.get(issueNumber).priority = context.item?.priorityValue?.name ?? null
}
}
return snapshot
}
/**
* Read PR creation time and Issue references without review eligibility gating.
* @param {number} number Pull-request number.
* @returns {Promise<object>} Lifecycle snapshot without Project reads or writes.
*/
export async function lifecyclePullRequestSnapshot(number) {
const pull = await api(`/repos/${config.organization}/${config.repository}/pulls/${number}`)
return {
...(await resolvingReferencesSnapshot(number, pull)),
createdAt: pull.created_at,
}
}
const EXEMPT_MESSAGE =
'Issue policy exempt:当前 PR 不在强制范围(Draft、Bot/App 或尚无 review request/review)。\n'
/**
* Determine current policy eligibility and Project access needs without Project credentials.
* @param {{pull_request: {number: number}}} event GitHub event identifying the PR.
* @returns {Promise<{eligible: boolean, needsProject: boolean}>} Trusted workflow decisions.
*/
export async function runPullRequestPreflight(event) {
const pull = await pullRequestSnapshot(event.pull_request.number, false)
const eligible = requiresPullRequestPolicy(pull)
const needsProject = eligible && pull.references.resolving.length > 0
if (process.env.GITHUB_OUTPUT) {
fs.appendFileSync(
process.env.GITHUB_OUTPUT,
`eligible=${eligible}\nexempt=${!eligible}\nneeds-project=${needsProject}\n`,
)
}
process.stdout.write(eligible ? 'Issue policy applicable;执行完整校验。\n' : EXEMPT_MESSAGE)
return { eligible, needsProject }
}
/**
* Enforce all PR rules against current GitHub state, independently of preflight.
* @param {{pull_request: {number: number}}} event GitHub event identifying the PR.
* @returns {Promise<void>} Resolves on success or exemption; rejects policy failures.
*/
export async function runPullRequestCheck(event) {
const pull = await pullRequestSnapshot(event.pull_request.number)
const errors = validatePullRequest(pull)
if (errors.length > 0) {
for (const error of errors) process.stdout.write(`::error::${error}\n`)
throw new Error(`Issue policy 未通过,共 ${errors.length} 项`)
}
process.stdout.write(
requiresPullRequestPolicy(pull) ? 'Issue policy 通过。\n' : EXEMPT_MESSAGE,
)
}