summaryrefslogtreecommitdiff
path: root/tools/release/04_post_publish.ts
blob: 2fa9e73561f54d9018e5653f78555f8822d18dc1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#!/usr/bin/env -S deno run -A --lock=tools/deno.lock.json
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { DenoWorkspace } from "./deno_workspace.ts";
import { $, createOctoKit, getGitHubRepository } from "./deps.ts";

const workspace = await DenoWorkspace.load();
const repo = workspace.repo;
const cliCrate = workspace.getCliCrate();

$.logStep("Creating release tag...");
await createReleaseTag();

$.logStep("Forwarding release commit to main...");
try {
  await forwardReleaseCommitToMain();
} catch (err) {
  $.logError("Failed. Please manually open a PR.", err);
}

async function createReleaseTag() {
  await repo.gitFetchTags("origin");
  const tags = await repo.getGitTags();
  const tagName = `v${cliCrate.version}`;

  if (tags.has(tagName)) {
    $.log(`Tag ${tagName} already exists.`);
  } else {
    await repo.gitTag(tagName);
    await repo.gitPush("origin", tagName);
  }
}

async function forwardReleaseCommitToMain() {
  // if this is a patch release, open a PR to forward the most recent commit back to main
  const currentBranch = await repo.gitCurrentBranch();
  const isPatchRelease = currentBranch !== "main";

  if (!isPatchRelease) {
    $.log("Not doing a patch release. Skipping.");
    return;
  }

  await repo.command("git fetch origin main");
  const releaseCommitHash = await repo.command("git rev-parse HEAD").text();
  const newBranchName = `forward_v${cliCrate.version}`;
  $.logStep(`Creating branch ${newBranchName}...`);
  await repo.command([
    "git",
    "checkout",
    "-b",
    newBranchName,
    "origin/main",
  ]);
  const cherryPickResult = await repo.command([
    "git",
    "cherry-pick",
    releaseCommitHash,
  ]).noThrow();
  if (cherryPickResult.code !== 0) {
    // commit with conflicts that can be resolved in the PR
    await repo.command("git add .");
    await repo.command(
      'git commit --no-verify -m "Cherry-pick version bump commit with conflicts"',
    ).noThrow();
  }
  await repo.gitPush("origin", newBranchName);

  $.logStep(`Opening PR...`);

  try {
    const openedPr = await createOctoKit().request(
      "POST /repos/{owner}/{repo}/pulls",
      {
        ...getGitHubRepository(),
        base: "main",
        head: newBranchName,
        draft: true,
        title: `chore: forward v${cliCrate.version} release commit to main`,
        body: getPrBody(),
      },
    );
    $.log(`Opened PR at ${openedPr.data.url}`);
  } catch (err) {
    $.logError(
      `Failed to open PR. Please open one manually: https://github.com/denoland/deno/pull/new/${newBranchName}`,
      err,
    );
  }

  function getPrBody() {
    let text = "";

    if (cherryPickResult.code !== 0) {
      text += `**THIS PR HAS GIT CONFLICTS THAT MUST BE RESOLVED**\n\n`;
    }

    text +=
      `This is the release commit being forwarded back to main for ${cliCrate.version}\n\n` +
      `Please ensure:\n` +
      `- [ ] Everything looks ok in the PR\n` +
      `- [ ] The release has been published\n\n` +
      `To make edits to this PR:\n` +
      "```shell\n" +
      `git fetch upstream ${newBranchName} && git checkout -b ${newBranchName} upstream/${newBranchName}\n` +
      "```\n\n" +
      "Don't need this PR? Close it.\n";

    const actor = Deno.env.get("GH_WORKFLOW_ACTOR");
    if (actor != null) {
      text += `\ncc @${actor}`;
    }

    return text;
  }
}