diff --git a/package.json b/package.json index 8fd387b..92046ad 100644 --- a/package.json +++ b/package.json @@ -42,11 +42,11 @@ "@babel/core": "^7.23.2", "@babel/preset-env": "^7.22.20", "@babel/preset-typescript": "^7.23.2", + "@fensak-io/front-matter": "^1.0.0", "@octokit/rest": "^20.0.2", "babel-preset-minify": "^0.5.2" }, "devDependencies": { - "JS-Interpreter": "git://github.com/yorinasub17/JS-Interpreter.git#c1e9044f99041dead8c6a49a4f3784486df0fdd9", "@jest/globals": "^29.7.0", "@parcel/config-default": "2.9.3", "@parcel/packager-ts": "2.9.3", @@ -56,6 +56,7 @@ "@types/node": "^20.8.2", "@typescript-eslint/eslint-plugin": "^6.7.2", "@typescript-eslint/parser": "^6.7.2", + "JS-Interpreter": "git://github.com/yorinasub17/JS-Interpreter.git#c1e9044f99041dead8c6a49a4f3784486df0fdd9", "eslint": "^8.50.0", "eslint-config-prettier": "^9.0.0", "jest": "^29.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87b7fd8..7567f78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ dependencies: '@babel/preset-typescript': specifier: ^7.23.2 version: 7.23.2(@babel/core@7.23.2) + '@fensak-io/front-matter': + specifier: ^1.0.0 + version: 1.0.0 '@octokit/rest': specifier: ^20.0.2 version: 20.0.2 @@ -1317,6 +1320,13 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@fensak-io/front-matter@1.0.0: + resolution: {integrity: sha512-9LjvcRQXpA9/KoKFlhmQtxFJz+zC498hf4lAt4JmwNgbzpDPdFSWAb0IMRMMq/4lwtlnpTYG5nBCiJsSMI3tYw==} + dependencies: + toml: 3.0.0 + yaml: 2.3.3 + dev: false + /@humanwhocodes/config-array@0.11.11: resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} engines: {node: '>=10.10.0'} @@ -6836,6 +6846,10 @@ packages: is-number: 7.0.0 dev: true + /toml@3.0.0: + resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + dev: false + /traverse@0.6.7: resolution: {integrity: sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==} dev: true @@ -7104,6 +7118,11 @@ packages: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true + /yaml@2.3.3: + resolution: {integrity: sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ==} + engines: {node: '>= 14'} + dev: false + /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} diff --git a/src/engine/from_github.ts b/src/engine/from_github.ts index 9116ee3..f44e121 100644 --- a/src/engine/from_github.ts +++ b/src/engine/from_github.ts @@ -4,9 +4,18 @@ import * as nodecrypto from "crypto"; import { Octokit } from "@octokit/rest"; +import { + hasParsableFrontMatter, + extract as extractFrontMatter, +} from "@fensak-io/front-matter"; import { parseUnifiedDiff } from "./patch.ts"; -import { IChangeSetMetadata, IPatch, PatchOp } from "./patch_types.ts"; +import { + ILinkedPR, + IChangeSetMetadata, + IPatch, + PatchOp, +} from "./patch_types.ts"; import { SourcePlatform } from "./from.ts"; const crypto = nodecrypto.webcrypto; @@ -71,6 +80,7 @@ export async function patchFromGitHubPullRequest( metadata: { sourceBranch: pullReq.head.ref, targetBranch: pullReq.base.ref, + linkedPRs: extractLinkedPRs(pullReq.body), }, patchList: [], patchFetchMap: {}, @@ -152,6 +162,20 @@ async function getGitHubPRFileID(salt: string, url: URL): Promise { return hexEncode(new Uint8Array(digest)); } +function extractLinkedPRs(prDescription: string | null): ILinkedPR[] { + if (!prDescription || !hasParsableFrontMatter(prDescription)) { + return []; + } + + interface IExpectedFrontMatter { + fensak: { + linked: ILinkedPR[]; + }; + } + const fm = extractFrontMatter(prDescription); + return fm.attrs.fensak.linked; +} + function hexEncode(hb: Uint8Array): string { const hashArray = Array.from(hb); const hashHex = hashArray diff --git a/src/engine/interpreter.test.ts b/src/engine/interpreter.test.ts index 49c06d6..57b6f7f 100644 --- a/src/engine/interpreter.test.ts +++ b/src/engine/interpreter.test.ts @@ -10,6 +10,7 @@ import { compileRuleFn, RuleFnSourceLang } from "./compile.ts"; const nullMeta = { sourceBranch: "foo", targetBranch: "bar", + linkedPRs: [], }; test("sanity check", async () => { diff --git a/src/engine/patch_types.ts b/src/engine/patch_types.ts index 2f51981..9f2ad04 100644 --- a/src/engine/patch_types.ts +++ b/src/engine/patch_types.ts @@ -77,12 +77,36 @@ export interface IPatch { diff: IHunk[]; } +/** + * Represents another PR that this PR is linked to. A linked PR can be used to represent a dependency where the upstream PR needs to be merged before this PR should be merged. + * @property repo The name of the repository (in the same organization) where the linked PR is located. Blank if the + * same repo. + * @property prNum The PR number of the linked PR. + * @property isMerged Whether the linked PR has been merged. + * @property isClosed Whether the linked PR has been closed. Note that a merged PR is also closed. + */ +export interface ILinkedPR { + repo: string; + prNum: number; + isMerged: boolean; + isClosed: boolean; +} + /** * Represents metadata about the change set that is under evaluation. * @property sourceBranch The branch that the change set originates from. * @property targetBranch The branch that the change set is merging into. + * @property linkedPRs The list of PRs that this PR is linked to. */ export interface IChangeSetMetadata { sourceBranch: string; targetBranch: string; + linkedPRs: ILinkedPR[]; } + +// A convenient const for test cases to initialize a blank changeset metadata. +export const emptyChangeSetMetadata: IChangeSetMetadata = { + sourceBranch: "", + targetBranch: "", + linkedPRs: [], +};