|
1 | | -import { $ } from "bun"; |
| 1 | +/// <reference types="bun" /> |
2 | 2 |
|
3 | 3 | const version = process.argv[2]; |
4 | 4 | if (!version) { |
@@ -40,8 +40,42 @@ const runCapture = async (command: string[], cwd?: string) => { |
40 | 40 | return stdout; |
41 | 41 | }; |
42 | 42 |
|
| 43 | +const runCaptureOptional = async (command: string[], cwd?: string) => { |
| 44 | + const proc = Bun.spawn(command, { |
| 45 | + cwd, |
| 46 | + stdout: "pipe", |
| 47 | + stderr: "pipe", |
| 48 | + }); |
| 49 | + const stdout = await new Response(proc.stdout).text(); |
| 50 | + const stderr = await new Response(proc.stderr).text(); |
| 51 | + const exitCode = await proc.exited; |
| 52 | + return { exitCode, stdout, stderr }; |
| 53 | +}; |
| 54 | + |
| 55 | +const runCheck = async (command: string[], cwd?: string) => { |
| 56 | + const proc = Bun.spawn(command, { |
| 57 | + cwd, |
| 58 | + stdout: "ignore", |
| 59 | + stderr: "ignore", |
| 60 | + }); |
| 61 | + return (await proc.exited) === 0; |
| 62 | +}; |
| 63 | + |
43 | 64 | const readText = async (path: string) => Bun.file(path).text(); |
44 | 65 |
|
| 66 | +const parseVersion = (input: string) => { |
| 67 | + const [major = 0, minor = 0, patch = 0] = input.split(".").map(Number); |
| 68 | + return [major, minor, patch] as const; |
| 69 | +}; |
| 70 | + |
| 71 | +const compareSemver = (left: string, right: string) => { |
| 72 | + const [lMajor, lMinor, lPatch] = parseVersion(left); |
| 73 | + const [rMajor, rMinor, rPatch] = parseVersion(right); |
| 74 | + if (lMajor !== rMajor) return lMajor - rMajor; |
| 75 | + if (lMinor !== rMinor) return lMinor - rMinor; |
| 76 | + return lPatch - rPatch; |
| 77 | +}; |
| 78 | + |
45 | 79 | const ensureClean = async () => { |
46 | 80 | const status = (await runCapture(["git", "status", "--porcelain"])).trim(); |
47 | 81 | if (status.length > 0) { |
@@ -72,6 +106,83 @@ const ensureTagAvailable = async (tag: string) => { |
72 | 106 | } |
73 | 107 | }; |
74 | 108 |
|
| 109 | +const ensureReleasePleasePrClosed = async () => { |
| 110 | + const result = await runCaptureOptional([ |
| 111 | + "gh", |
| 112 | + "pr", |
| 113 | + "list", |
| 114 | + "--state", |
| 115 | + "open", |
| 116 | + "--search", |
| 117 | + "release-please", |
| 118 | + "--json", |
| 119 | + "number,title", |
| 120 | + ]); |
| 121 | + if (result.exitCode !== 0) { |
| 122 | + console.warn("Warning: unable to check release-please PR status."); |
| 123 | + return; |
| 124 | + } |
| 125 | + |
| 126 | + const prs = JSON.parse(result.stdout) as Array<{ number: number; title: string }>; |
| 127 | + if (prs.length > 0) { |
| 128 | + console.error( |
| 129 | + [ |
| 130 | + "Release-please PR already open:", |
| 131 | + ...prs.map(pr => `#${pr.number} ${pr.title}`), |
| 132 | + ].join("\n"), |
| 133 | + ); |
| 134 | + process.exit(1); |
| 135 | + } |
| 136 | +}; |
| 137 | + |
| 138 | +const ensureDevelopContainsMain = async () => { |
| 139 | + await run(["git", "fetch", "origin"]); |
| 140 | + const hasMain = await runCheck(["git", "merge-base", "--is-ancestor", "origin/main", "develop"]); |
| 141 | + if (!hasMain) { |
| 142 | + console.error("Develop is missing commits from main. Merge main into develop first."); |
| 143 | + process.exit(1); |
| 144 | + } |
| 145 | +}; |
| 146 | + |
| 147 | +const ensureDevelopUpToDate = async () => { |
| 148 | + const output = await runCapture([ |
| 149 | + "git", |
| 150 | + "rev-list", |
| 151 | + "--left-right", |
| 152 | + "--count", |
| 153 | + "origin/develop...develop", |
| 154 | + ]); |
| 155 | + const [behind = 0] = output.trim().split("\t").map(Number); |
| 156 | + if (behind > 0) { |
| 157 | + console.error("Develop is behind origin/develop. Pull first."); |
| 158 | + process.exit(1); |
| 159 | + } |
| 160 | +}; |
| 161 | + |
| 162 | +const ensureVersionAdvances = async () => { |
| 163 | + const manifest = JSON.parse( |
| 164 | + await readText(".release-please-manifest.json"), |
| 165 | + ) as Record<string, string>; |
| 166 | + const current = manifest["packages/pg-sourcerer"]; |
| 167 | + if (!current) { |
| 168 | + console.error("Release-please manifest missing packages/pg-sourcerer version."); |
| 169 | + process.exit(1); |
| 170 | + } |
| 171 | + |
| 172 | + if (compareSemver(version, current) <= 0) { |
| 173 | + console.error(`Version must be greater than ${current}.`); |
| 174 | + process.exit(1); |
| 175 | + } |
| 176 | + |
| 177 | + const pkg = JSON.parse( |
| 178 | + await readText("packages/pg-sourcerer/package.json"), |
| 179 | + ) as { version: string }; |
| 180 | + if (pkg.version !== current) { |
| 181 | + console.error("Package version and release-please manifest are out of sync."); |
| 182 | + process.exit(1); |
| 183 | + } |
| 184 | +}; |
| 185 | + |
75 | 186 | const bumpVersion = async () => { |
76 | 187 | const pkgPath = "packages/pg-sourcerer/package.json"; |
77 | 188 | const manifestPath = ".release-please-manifest.json"; |
@@ -116,6 +227,10 @@ const pushDevelop = async () => { |
116 | 227 | const main = async () => { |
117 | 228 | await ensureClean(); |
118 | 229 | await ensureBranch("develop"); |
| 230 | + await ensureDevelopContainsMain(); |
| 231 | + await ensureDevelopUpToDate(); |
| 232 | + await ensureReleasePleasePrClosed(); |
| 233 | + await ensureVersionAdvances(); |
119 | 234 | await ensureTagAvailable(`v${version}`); |
120 | 235 | await bumpVersion(); |
121 | 236 | await runTests(); |
|
0 commit comments