@intentius/chant 0.124.7 → 0.124.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -182,6 +182,27 @@ export interface PullRequest {
182
182
  /** Its base (target) branch. */
183
183
  base?: string;
184
184
  }
185
+ /** What {@link PrForge.mergePullRequest} merges, and how. */
186
+ export interface MergeOptions {
187
+ /** The head commit the caller reviewed. The merge happens only while the pull request's head is still this commit. */
188
+ sha: string;
189
+ /** Default `merge`. GitLab has no one-call rebase merge, so `rebase` is refused there. */
190
+ method?: "merge" | "squash" | "rebase";
191
+ /** The merge (squash) commit's title. */
192
+ title?: string;
193
+ /** The merge (squash) commit's message body. */
194
+ message?: string;
195
+ /** Delete the head branch after a merge. A failure to delete does not undo the merge. */
196
+ deleteBranch?: boolean;
197
+ }
198
+ /** The outcome of {@link PrForge.mergePullRequest}. */
199
+ export interface MergeResult {
200
+ merged: boolean;
201
+ /** The commit the merge produced, when the forge says. */
202
+ sha?: string;
203
+ /** Why it was not merged: the head moved, a conflict, a failing check, a required review. */
204
+ reason?: string;
205
+ }
185
206
  export interface PrForge {
186
207
  kind: ForgeKind;
187
208
  /** Lower-cased, such as `github.com`. */
@@ -263,6 +284,13 @@ export interface PrForge {
263
284
  }): Promise<PullRequest | undefined>;
264
285
  /** Add labels to a pull request by name. Forgejo needs them to exist; GitHub and GitLab make them. */
265
286
  addLabels(pr: number, labels: readonly string[]): Promise<void>;
287
+ /**
288
+ * Merge pull request `pr` only if its head is still `opts.sha`. A moved
289
+ * head, a conflict or a branch rule the forge enforces comes back as
290
+ * `{ merged: false, reason }`; auth, missing-pull-request and server errors
291
+ * still throw.
292
+ */
293
+ mergePullRequest(pr: number, opts: MergeOptions): Promise<MergeResult>;
266
294
  }
267
295
  /** A GitHub or Forgejo client. */
268
296
  export type GithubShapedForge = PrForge & {
@@ -273,6 +301,7 @@ export declare class ForgeApiError extends Error {
273
301
  readonly method: string;
274
302
  readonly url: string;
275
303
  readonly status: number;
304
+ readonly detail: string;
276
305
  constructor(method: string, url: string, status: number, detail: string);
277
306
  }
278
307
  /** `<forge>:<login>` on the forge's default host, `<forge>@<host>:<login>` elsewhere, lower-cased (#3163's form). */
@@ -1 +1 @@
1
- {"version":3,"file":"pr-forge.d.ts","sourceRoot":"","sources":["../src/pr-forge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pr-forge.d.ts","sourceRoot":"","sources":["../src/pr-forge.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.124.7",
3
+ "version": "0.124.8",
4
4
  "description": "Declarative infrastructure-as-code toolkit — TypeScript on Node.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -824,6 +824,64 @@ describe("pull requests over REST (#3798)", () => {
824
824
  expect(l.calls.map((c) => c.body)).toEqual([{ description: "d" }, { add_labels: "minor,breaking" }]);
825
825
  });
826
826
 
827
+ test("Forgejo's 404 on the pull list of a just-pushed empty repository is no pull request (#3845)", async () => {
828
+ const empty = fakeFetch({ "GET /api/v1/repos/acme/infra": { default_branch: "main" } });
829
+ expect(await forgejoForge({ ...fjOpts, fetch: empty.fetch }).findPullRequest("chant/x")).toBeUndefined();
830
+ // A repository that is really missing 404s on its own read, and that still throws.
831
+ const gone = fakeFetch({});
832
+ await expect(forgejoForge({ ...fjOpts, fetch: gone.fetch }).findPullRequest("chant/x")).rejects.toThrow(/404/);
833
+ // GitHub's list 404 stays an error.
834
+ await expect(githubForge({ ...gh, fetch: fakeFetch({}).fetch }).findPullRequest("chant/x")).rejects.toThrow(/404/);
835
+ });
836
+
837
+ const answering = (status: number, body: unknown, calls: Call[] = []): ForgeFetch => async (url, init) => {
838
+ calls.push({ method: init.method, url, headers: init.headers, ...(init.body ? { body: JSON.parse(init.body) } : {}) });
839
+ return { ok: status < 400, status, text: async () => (body === undefined ? "" : JSON.stringify(body)) };
840
+ };
841
+
842
+ test("mergePullRequest merges only at the reviewed sha on GitHub", async () => {
843
+ const f = fakeFetch({
844
+ "PUT /repos/acme/infra/pulls/5/merge": { merged: true, sha: "m1" },
845
+ "GET /repos/acme/infra/pulls/5": { number: 5, head: { ref: "chant/x", repo: { full_name: "acme/infra" } } },
846
+ "DELETE /repos/acme/infra/git/refs/heads/chant/x": null,
847
+ });
848
+ const r = await githubForge({ ...gh, fetch: f.fetch }).mergePullRequest(5, { sha: SHA, method: "squash", title: "t", message: "m", deleteBranch: true });
849
+ expect(r).toEqual({ merged: true, sha: "m1" });
850
+ expect(f.calls[0].body).toEqual({ sha: SHA, merge_method: "squash", commit_title: "t", commit_message: "m" });
851
+ expect(f.calls.map((c) => c.method)).toEqual(["PUT", "GET", "DELETE"]);
852
+ const calls: Call[] = [];
853
+ const moved = await githubForge({ ...gh, fetch: answering(409, { message: "Head branch was modified. Review and try the merge again." }, calls) }).mergePullRequest(5, { sha: SHA });
854
+ expect(moved).toEqual({ merged: false, reason: "Head branch was modified. Review and try the merge again." });
855
+ expect(calls[0].body).toEqual({ sha: SHA, merge_method: "merge" });
856
+ const blocked = await githubForge({ ...gh, fetch: answering(405, { message: "Required status check is expected." }) }).mergePullRequest(5, { sha: SHA });
857
+ expect(blocked.merged).toBe(false);
858
+ await expect(githubForge({ ...gh, fetch: answering(401, { message: "Bad credentials" }) }).mergePullRequest(5, { sha: SHA })).rejects.toThrow(/401/);
859
+ });
860
+
861
+ test("mergePullRequest on Forgejo sends head_commit_id and reads the merge commit back", async () => {
862
+ const f = fakeFetch({
863
+ "POST /api/v1/repos/acme/infra/pulls/5/merge": null,
864
+ "GET /api/v1/repos/acme/infra/pulls/5": { number: 5, merge_commit_sha: "m2" },
865
+ });
866
+ const r = await forgejoForge({ ...fjOpts, fetch: f.fetch }).mergePullRequest(5, { sha: SHA, method: "rebase", title: "t", message: "m", deleteBranch: true });
867
+ expect(r).toEqual({ merged: true, sha: "m2" });
868
+ expect(f.calls[0].body).toEqual({ Do: "rebase", head_commit_id: SHA, MergeTitleField: "t", MergeMessageField: "m", delete_branch_after_merge: true });
869
+ const moved = await forgejoForge({ ...fjOpts, fetch: answering(409, { message: "head commit id does not match" }) }).mergePullRequest(5, { sha: SHA });
870
+ expect(moved).toEqual({ merged: false, reason: "head commit id does not match" });
871
+ expect((await forgejoForge({ ...fjOpts, fetch: answering(405, undefined) }).mergePullRequest(5, { sha: SHA })).merged).toBe(false);
872
+ });
873
+
874
+ test("mergePullRequest on GitLab passes sha and squash, and refuses rebase", async () => {
875
+ const f = fakeFetch({ "PUT /api/v4/projects/group%2Finfra/merge_requests/7/merge": { state: "merged", merge_commit_sha: "m3" } });
876
+ const g = gitlabForge({ ...gl, fetch: f.fetch });
877
+ expect(await g.mergePullRequest(7, { sha: SHA, method: "squash", title: "t", message: "m", deleteBranch: true })).toEqual({ merged: true, sha: "m3" });
878
+ expect(f.calls[0].body).toEqual({ sha: SHA, squash: true, squash_commit_message: "t\n\nm", merge_commit_message: "t\n\nm", should_remove_source_branch: true });
879
+ const moved = await gitlabForge({ ...gl, fetch: answering(409, { message: "SHA does not match HEAD of source branch" }) }).mergePullRequest(7, { sha: SHA });
880
+ expect(moved).toEqual({ merged: false, reason: "SHA does not match HEAD of source branch" });
881
+ expect((await gitlabForge({ ...gl, fetch: answering(405, { message: "Branch cannot be merged" }) }).mergePullRequest(7, { sha: SHA })).merged).toBe(false);
882
+ await expect(g.mergePullRequest(7, { sha: SHA, method: "rebase" })).rejects.toThrow(/rebase/);
883
+ });
884
+
827
885
  test("a repository with no default branch is refused by name", async () => {
828
886
  const { fetch } = fakeFetch({ "GET /api/v1/repos/acme/infra": {} });
829
887
  await expect(forgejoForge({ ...fjOpts, fetch }).openPullRequest({ head: "h", title: "t", body: "b" })).rejects.toThrow(/no default branch.*pass base/);
package/src/pr-forge.ts CHANGED
@@ -212,6 +212,48 @@ export interface PullRequest {
212
212
  base?: string;
213
213
  }
214
214
 
215
+ /** What {@link PrForge.mergePullRequest} merges, and how. */
216
+ export interface MergeOptions {
217
+ /** The head commit the caller reviewed. The merge happens only while the pull request's head is still this commit. */
218
+ sha: string;
219
+ /** Default `merge`. GitLab has no one-call rebase merge, so `rebase` is refused there. */
220
+ method?: "merge" | "squash" | "rebase";
221
+ /** The merge (squash) commit's title. */
222
+ title?: string;
223
+ /** The merge (squash) commit's message body. */
224
+ message?: string;
225
+ /** Delete the head branch after a merge. A failure to delete does not undo the merge. */
226
+ deleteBranch?: boolean;
227
+ }
228
+
229
+ /** The outcome of {@link PrForge.mergePullRequest}. */
230
+ export interface MergeResult {
231
+ merged: boolean;
232
+ /** The commit the merge produced, when the forge says. */
233
+ sha?: string;
234
+ /** Why it was not merged: the head moved, a conflict, a failing check, a required review. */
235
+ reason?: string;
236
+ }
237
+
238
+ /** Statuses a forge answers a refused merge with: not allowed, head moved, conflict, invalid. */
239
+ const MERGE_REFUSED = new Set([405, 406, 409, 422]);
240
+
241
+ /** A refused merge as a result, any other failure rethrown. */
242
+ function refusedMerge(err: unknown): MergeResult {
243
+ if (err instanceof ForgeApiError && MERGE_REFUSED.has(err.status)) {
244
+ let reason = err.detail;
245
+ try {
246
+ const j = JSON.parse(err.detail) as Record<string, unknown>;
247
+ const m = j.message ?? j.error;
248
+ if (typeof m === "string") reason = m;
249
+ } catch {
250
+ /* not JSON: the raw text is the reason */
251
+ }
252
+ return { merged: false, reason: reason.slice(0, 300) || `the forge refused the merge (${err.status})` };
253
+ }
254
+ throw err;
255
+ }
256
+
215
257
  export interface PrForge {
216
258
  kind: ForgeKind;
217
259
  /** Lower-cased, such as `github.com`. */
@@ -290,6 +332,13 @@ export interface PrForge {
290
332
  editPullRequest(pr: number, change: { title?: string; body?: string }): Promise<PullRequest | undefined>;
291
333
  /** Add labels to a pull request by name. Forgejo needs them to exist; GitHub and GitLab make them. */
292
334
  addLabels(pr: number, labels: readonly string[]): Promise<void>;
335
+ /**
336
+ * Merge pull request `pr` only if its head is still `opts.sha`. A moved
337
+ * head, a conflict or a branch rule the forge enforces comes back as
338
+ * `{ merged: false, reason }`; auth, missing-pull-request and server errors
339
+ * still throw.
340
+ */
341
+ mergePullRequest(pr: number, opts: MergeOptions): Promise<MergeResult>;
293
342
  }
294
343
 
295
344
  /** A GitHub or Forgejo client. */
@@ -301,7 +350,7 @@ export class ForgeApiError extends Error {
301
350
  readonly method: string,
302
351
  readonly url: string,
303
352
  readonly status: number,
304
- detail: string,
353
+ readonly detail: string,
305
354
  ) {
306
355
  super(`${method} ${url} answered ${status}${detail ? `: ${detail.slice(0, 300)}` : ""}`);
307
356
  this.name = "ForgeApiError";
@@ -604,7 +653,18 @@ function githubShaped(kind: "github" | "forgejo", opts: GithubForgeOptions): Git
604
653
  // head filter, so every open page is read and matched here.
605
654
  const owner = opts.repo.split("/")[0];
606
655
  const query = kind === "github" ? `?state=open&head=${encodeURIComponent(`${owner}:${head}`)}` : "?state=open";
607
- const open = (await api.list(`${repo}/pulls${query}`, pageParams, pageSize)).filter((p) => fromHere(p, head));
656
+ let listed: unknown[];
657
+ try {
658
+ listed = await api.list(`${repo}/pulls${query}`, pageParams, pageSize);
659
+ } catch (err) {
660
+ // Forgejo answers the list with 404 while it still counts the repository
661
+ // as empty (just after the first push). That is no pull request; a
662
+ // repository that is really missing 404s on its own read too.
663
+ if (kind !== "forgejo" || !(err instanceof ForgeApiError) || err.status !== 404) throw err;
664
+ await api.call("GET", repo);
665
+ return undefined;
666
+ }
667
+ const open = listed.filter((p) => fromHere(p, head));
608
668
  return open.map(pull).filter((p): p is PullRequest => p !== undefined).sort((a, b) => a.number - b.number)[0];
609
669
  },
610
670
  async editPullRequest(pr, change) {
@@ -614,6 +674,38 @@ function githubShaped(kind: "github" | "forgejo", opts: GithubForgeOptions): Git
614
674
  if (labels.length === 0) return;
615
675
  await api.call("POST", `${repo}/issues/${pr}/labels`, { labels: [...labels] });
616
676
  },
677
+ async mergePullRequest(pr, m) {
678
+ const method = m.method ?? "merge";
679
+ let res: unknown;
680
+ try {
681
+ res =
682
+ kind === "github"
683
+ ? await api.call("PUT", `${repo}/pulls/${pr}/merge`, {
684
+ sha: m.sha,
685
+ merge_method: method,
686
+ ...(m.title !== undefined ? { commit_title: m.title } : {}),
687
+ ...(m.message !== undefined ? { commit_message: m.message } : {}),
688
+ })
689
+ : await api.call("POST", `${repo}/pulls/${pr}/merge`, {
690
+ Do: method,
691
+ head_commit_id: m.sha,
692
+ ...(m.title !== undefined ? { MergeTitleField: m.title } : {}),
693
+ ...(m.message !== undefined ? { MergeMessageField: m.message } : {}),
694
+ ...(m.deleteBranch ? { delete_branch_after_merge: true } : {}),
695
+ });
696
+ } catch (err) {
697
+ return refusedMerge(err);
698
+ }
699
+ if (isObject(res) && res.merged === false) return { merged: false, reason: str(res.message) ?? "the forge did not merge it" };
700
+ // Forgejo answers an empty body, so the merge commit is read back.
701
+ const after = await api.call("GET", `${repo}/pulls/${pr}`).catch(() => undefined);
702
+ const sha = (isObject(res) ? str(res.sha) : undefined) ?? (isObject(after) ? str(after.merge_commit_sha) : undefined);
703
+ if (kind === "github" && m.deleteBranch && isObject(after) && isObject(after.head) && str(after.head.ref) && fromHere(after, str(after.head.ref) as string)) {
704
+ const ref = (str(after.head.ref) as string).split("/").map(encodeURIComponent).join("/");
705
+ await api.call("DELETE", `${repo}/git/refs/heads/${ref}`).catch(() => undefined);
706
+ }
707
+ return { merged: true, ...(sha ? { sha } : {}) };
708
+ },
617
709
  };
618
710
  }
619
711
 
@@ -773,6 +865,25 @@ export function gitlabForge(opts: GitlabForgeOptions): PrForge {
773
865
  if (labels.length === 0) return;
774
866
  await api.call("PUT", `${notes}/${mr}`, { add_labels: labels.join(",") });
775
867
  },
868
+ async mergePullRequest(mr, m) {
869
+ const method = m.method ?? "merge";
870
+ if (method === "rebase") throw new Error("GitLab has no one-call rebase merge; use method merge or squash");
871
+ const text = m.title !== undefined ? (m.message ? `${m.title}\n\n${m.message}` : m.title) : m.message;
872
+ let res: unknown;
873
+ try {
874
+ res = await api.call("PUT", `${notes}/${mr}/merge`, {
875
+ sha: m.sha,
876
+ ...(method === "squash" ? { squash: true, ...(text !== undefined ? { squash_commit_message: text } : {}) } : {}),
877
+ ...(text !== undefined ? { merge_commit_message: text } : {}),
878
+ ...(m.deleteBranch ? { should_remove_source_branch: true } : {}),
879
+ });
880
+ } catch (err) {
881
+ return refusedMerge(err);
882
+ }
883
+ const sha = isObject(res) ? (str(res.merge_commit_sha) ?? str(res.squash_commit_sha)) : undefined;
884
+ if (isObject(res) && res.state !== undefined && res.state !== "merged") return { merged: false, reason: `the merge request is ${String(res.state)}` };
885
+ return { merged: true, ...(sha ? { sha } : {}) };
886
+ },
776
887
  };
777
888
  }
778
889