68 lines
1.7 KiB
TypeScript
68 lines
1.7 KiB
TypeScript
import type { Role, RoleResult, StartStep } from "@uncaged/nerve-core";
|
|
|
|
export type IntakeMeta = {
|
|
issueUrl: string;
|
|
host: string | null;
|
|
owner: string | null;
|
|
repo: string | null;
|
|
issueNumber: number | null;
|
|
valid: boolean;
|
|
failureKind: "none" | "invalid_input";
|
|
failureReason: string | null;
|
|
};
|
|
|
|
function parseIssueUrl(raw: string): IntakeMeta {
|
|
const issueUrl = raw.trim();
|
|
const match = issueUrl.match(/^https?:\/\/([^/\s]+)\/([^/\s]+)\/([^/\s]+)\/issues\/(\d+)(?:[/?#].*)?$/i);
|
|
if (match === null) {
|
|
return {
|
|
issueUrl,
|
|
host: null,
|
|
owner: null,
|
|
repo: null,
|
|
issueNumber: null,
|
|
valid: false,
|
|
failureKind: "invalid_input",
|
|
failureReason: `invalid issue URL: ${issueUrl}`,
|
|
};
|
|
}
|
|
|
|
const issueNumber = Number(match[4]);
|
|
if (!Number.isInteger(issueNumber) || issueNumber <= 0) {
|
|
return {
|
|
issueUrl,
|
|
host: null,
|
|
owner: null,
|
|
repo: null,
|
|
issueNumber: null,
|
|
valid: false,
|
|
failureKind: "invalid_input",
|
|
failureReason: `invalid issue number: ${match[4]}`,
|
|
};
|
|
}
|
|
|
|
return {
|
|
issueUrl,
|
|
host: match[1],
|
|
owner: match[2],
|
|
repo: match[3],
|
|
issueNumber,
|
|
valid: true,
|
|
failureKind: "none",
|
|
failureReason: null,
|
|
};
|
|
}
|
|
|
|
export function buildIntakeRole(): Role<IntakeMeta> {
|
|
return async (start: StartStep): Promise<RoleResult<IntakeMeta>> => {
|
|
const parsed = parseIssueUrl(start.content);
|
|
if (!parsed.valid) {
|
|
return { content: parsed.failureReason ?? "invalid issue URL", meta: parsed };
|
|
}
|
|
return {
|
|
content: `parsed issue URL: ${parsed.owner}/${parsed.repo}#${parsed.issueNumber} @ ${parsed.host}`,
|
|
meta: parsed,
|
|
};
|
|
};
|
|
}
|