summaryrefslogtreecommitdiffhomepage
path: root/script/beta.ts
blob: 401ca79901b920b8d9a7f6f8ecf50a30488a1740 (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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!/usr/bin/env bun

interface PR {
  number: number
  headRefName: string
  headRefOid: string
  createdAt: string
  isDraft: boolean
  title: string
}

async function main() {
  console.log("Fetching open contributor PRs...")

  const prsResult =
    await $`gh pr list --label contributor --state open --json number,headRefName,headRefOid,createdAt,isDraft,title --limit 100`.nothrow()
  if (prsResult.exitCode !== 0) {
    throw new Error(`Failed to fetch PRs: ${prsResult.stderr}`)
  }

  const allPRs: PR[] = JSON.parse(prsResult.stdout)
  const prs = allPRs.filter((pr) => !pr.isDraft)

  console.log(`Found ${prs.length} open non-draft contributor PRs`)

  console.log("Fetching latest dev branch...")
  const fetchDev = await $`git fetch origin dev`.nothrow()
  if (fetchDev.exitCode !== 0) {
    throw new Error(`Failed to fetch dev branch: ${fetchDev.stderr}`)
  }

  console.log("Checking out beta branch...")
  const checkoutBeta = await $`git checkout -B beta origin/dev`.nothrow()
  if (checkoutBeta.exitCode !== 0) {
    throw new Error(`Failed to checkout beta branch: ${checkoutBeta.stderr}`)
  }

  const applied: number[] = []
  const skipped: Array<{ number: number; reason: string }> = []

  for (const pr of prs) {
    console.log(`\nProcessing PR #${pr.number}: ${pr.title}`)

    // Fetch the PR
    const fetchPR = await $`git fetch origin pull/${pr.number}/head:pr-${pr.number}`.nothrow()
    if (fetchPR.exitCode !== 0) {
      console.log(`  Failed to fetch PR #${pr.number}, skipping`)
      skipped.push({ number: pr.number, reason: "Failed to fetch" })
      continue
    }

    // Find merge base and get diff from base to PR head (just the PR's changes)
    console.log(`  Finding merge base for PR #${pr.number}...`)
    const mergeBaseResult = await $`git merge-base dev pr-${pr.number}`.nothrow()
    if (mergeBaseResult.exitCode !== 0 || !mergeBaseResult.stdout.trim()) {
      console.log(`  Failed to find merge base for PR #${pr.number}`)
      skipped.push({ number: pr.number, reason: "Failed to find merge base" })
      continue
    }
    const mergeBase = mergeBaseResult.stdout.trim()

    console.log(`  Getting diff for PR #${pr.number}...`)
    const diff = await $`git diff ${mergeBase}..pr-${pr.number}`.nothrow()
    if (diff.exitCode !== 0) {
      console.log(`  Failed to get diff for PR #${pr.number}`)
      console.log(`  Error: ${diff.stderr}`)
      skipped.push({ number: pr.number, reason: `Failed to get diff: ${diff.stderr}` })
      continue
    }

    if (!diff.stdout.trim()) {
      console.log(`  No changes in PR #${pr.number}, skipping`)
      skipped.push({ number: pr.number, reason: "No changes" })
      continue
    }

    // Apply the diff
    console.log(`  Applying diff for PR #${pr.number}...`)
    const apply = await Bun.spawn(["git", "apply"], {
      stdin: new TextEncoder().encode(diff.stdout),
      stdout: "pipe",
      stderr: "pipe",
    })
    const applyExit = await apply.exited
    const applyStderr = await Bun.readableStreamToText(apply.stderr)

    if (applyExit !== 0) {
      console.log(`  Failed to apply diff for PR #${pr.number}`)
      console.log(`  Error: ${applyStderr}`)
      await $`git checkout -- .`.nothrow()
      skipped.push({ number: pr.number, reason: `Failed to apply diff: ${applyStderr}` })
      continue
    }

    // Stage all changes
    const add = await $`git add -A`.nothrow()
    if (add.exitCode !== 0) {
      console.log(`  Failed to stage changes for PR #${pr.number}`)
      await $`git checkout -- .`.nothrow()
      skipped.push({ number: pr.number, reason: "Failed to stage" })
      continue
    }

    // Commit
    const commitMsg = `Apply PR #${pr.number}: ${pr.title}`
    const commit = await Bun.spawn(["git", "commit", "-m", commitMsg], { stdout: "pipe", stderr: "pipe" })
    const commitExit = await commit.exited
    const commitStderr = await Bun.readableStreamToText(commit.stderr)

    if (commitExit !== 0) {
      console.log(`  Failed to commit PR #${pr.number}`)
      console.log(`  Error: ${commitStderr}`)
      await $`git checkout -- .`.nothrow()
      skipped.push({ number: pr.number, reason: `Commit failed: ${commitStderr}` })
      continue
    }

    console.log(`  Successfully applied PR #${pr.number}`)
    applied.push(pr.number)
  }

  console.log("\n--- Summary ---")
  console.log(`Applied: ${applied.length} PRs`)
  applied.forEach((num) => console.log(`  - PR #${num}`))
  console.log(`Skipped: ${skipped.length} PRs`)
  skipped.forEach((x) => console.log(`  - PR #${x.number}: ${x.reason}`))

  console.log("\nForce pushing beta branch...")
  const push = await $`git push origin beta --force --no-verify`.nothrow()
  if (push.exitCode !== 0) {
    throw new Error(`Failed to push beta branch: ${push.stderr}`)
  }

  console.log("Successfully synced beta branch")
}

main().catch((err) => {
  console.error("Error:", err)
  process.exit(1)
})

function $(strings: TemplateStringsArray, ...values: unknown[]) {
  const cmd = strings.reduce((acc, str, i) => acc + str + (values[i] ?? ""), "")
  return {
    async nothrow() {
      const proc = Bun.spawn(cmd.split(" "), {
        stdout: "pipe",
        stderr: "pipe",
      })
      const exitCode = await proc.exited
      const stdout = await new Response(proc.stdout).text()
      const stderr = await new Response(proc.stderr).text()
      return { exitCode, stdout, stderr }
    },
  }
}