Skip to content

Commit f60a6f0

Browse files
committed
Pass release constants into the Homebrew tap generator
release.sh already owned the repo, binary, formula, and description; the tap generator hard-coded copies of the same values. It now takes them from the environment release.sh sets, so they cannot drift.
1 parent e8aa3e1 commit f60a6f0

3 files changed

Lines changed: 55 additions & 20 deletions

File tree

scripts/generate-homebrew-tap.ts

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,26 @@ export interface HomebrewRelease {
1111
checksums: Record<Platform, string>;
1212
}
1313

14-
const releaseURL = (version: string, platform: Platform): string =>
15-
`https://github.com/corbitsdev/corbits-code/releases/download/v${version}/corbits-${version}-${platform}.tar.gz`;
14+
/** Release facts owned by scripts/release.sh; passed in so they live in one place. */
15+
export interface HomebrewPackage {
16+
repo: string; // GitHub owner/name
17+
binary: string; // CLI binary, tarball stem, and legacy formula name
18+
formula: string; // `brew install` name
19+
description: string;
20+
}
21+
22+
const formulaClass = (formula: string): string =>
23+
formula.replace(/(?:^|-)([a-z])/g, (_, c: string) => c.toUpperCase());
1624

17-
function renderFormula(release: HomebrewRelease): string {
25+
function renderFormula(pkg: HomebrewPackage, release: HomebrewRelease): string {
1826
const source = (
1927
platform: Platform,
20-
): string => ` url "${releaseURL(release.version, platform)}"
28+
): string => ` url "https://github.com/${pkg.repo}/releases/download/v${release.version}/${pkg.binary}-${release.version}-${platform}.tar.gz"
2129
sha256 "${release.checksums[platform]}"`;
2230

23-
return `class CorbitsCode < Formula
24-
desc "Single-process coding agent CLI built on the Interchange runtime"
25-
homepage "https://github.com/corbitsdev/corbits-code"
31+
return `class ${formulaClass(pkg.formula)} < Formula
32+
desc "${pkg.description}"
33+
homepage "https://github.com/${pkg.repo}"
2634
version "${release.version}"
2735
license "GPL-2.0-only"
2836
@@ -45,15 +53,15 @@ ${source("linux-x64")}
4553
end
4654
4755
def install
48-
bin.install "corbits"
56+
bin.install "${pkg.binary}"
4957
if File.directory?("plugins")
5058
(bin/"plugins").mkpath
5159
cp_r "plugins/.", bin/"plugins"
5260
end
5361
end
5462
5563
test do
56-
assert_predicate bin/"corbits", :executable?
64+
assert_predicate bin/"${pkg.binary}", :executable?
5765
end
5866
end
5967
`;
@@ -79,17 +87,21 @@ async function readFormulaRenames(path: string): Promise<Record<string, string>>
7987
return renames;
8088
}
8189

82-
export async function generateHomebrewTap(tapDir: string, release: HomebrewRelease): Promise<void> {
90+
export async function generateHomebrewTap(
91+
tapDir: string,
92+
pkg: HomebrewPackage,
93+
release: HomebrewRelease,
94+
): Promise<void> {
8395
const formulaDir = join(tapDir, "Formula");
8496
const renamesPath = join(tapDir, "formula_renames.json");
85-
const formula = renderFormula(release);
97+
const formula = renderFormula(pkg, release);
8698
const renames = await readFormulaRenames(renamesPath);
87-
renames.corbits = "corbits-code";
99+
renames[pkg.binary] = pkg.formula;
88100
const renameMetadata = `${JSON.stringify(renames, null, 2)}\n`;
89101

90102
await mkdir(formulaDir, { recursive: true });
91-
await rm(join(formulaDir, "corbits.rb"), { force: true });
92-
await writeFile(join(formulaDir, "corbits-code.rb"), formula);
103+
await rm(join(formulaDir, `${pkg.binary}.rb`), { force: true });
104+
await writeFile(join(formulaDir, `${pkg.formula}.rb`), formula);
93105
await writeFile(renamesPath, renameMetadata);
94106
}
95107

@@ -128,7 +140,22 @@ function parseRelease(args: string[]): { tapDir: string; release: HomebrewReleas
128140
};
129141
}
130142

143+
function requireEnv(name: string): string {
144+
const value = process.env[name];
145+
if (!value) throw new Error(`missing ${name} (set by scripts/release.sh)`);
146+
return value;
147+
}
148+
131149
if (import.meta.main) {
132150
const { tapDir, release } = parseRelease(process.argv.slice(2));
133-
await generateHomebrewTap(tapDir, release);
151+
await generateHomebrewTap(
152+
tapDir,
153+
{
154+
repo: requireEnv("MAIN_REPO"),
155+
binary: requireEnv("BINARY"),
156+
formula: requireEnv("BREW_FORMULA"),
157+
description: requireEnv("DESC"),
158+
},
159+
release,
160+
);
134161
}

scripts/release.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,8 @@ else
483483
sha_for() { # sha_for LABEL -> sha256 of that tarball
484484
cut -d' ' -f1 "$STAGE/$BINARY-$VERSION-$1.tar.gz.sha256"
485485
}
486-
bun "$ROOT/scripts/generate-homebrew-tap.ts" \
486+
MAIN_REPO="$MAIN_REPO" BINARY="$BINARY" BREW_FORMULA="$BREW_FORMULA" DESC="$DESC" \
487+
bun "$ROOT/scripts/generate-homebrew-tap.ts" \
487488
"$TAP_DIR" \
488489
"$VERSION" \
489490
"$(sha_for macos-arm64)" \

tests/unit/generate-homebrew-tap.test.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ import { join } from "node:path";
55

66
import { generateHomebrewTap } from "../../scripts/generate-homebrew-tap.js";
77

8+
const pkg = {
9+
repo: "corbitsdev/corbits-code",
10+
binary: "corbits",
11+
formula: "corbits-code",
12+
description: "Single-process coding agent CLI built on the Interchange runtime",
13+
};
14+
815
const release = {
916
version: "1.2.3",
1017
checksums: {
@@ -31,7 +38,7 @@ describe("generateHomebrewTap", () => {
3138
await mkdir(formulaDir);
3239
await writeFile(join(formulaDir, "corbits.rb"), "class Corbits < Formula\nend\n");
3340

34-
await generateHomebrewTap(tapDir, release);
41+
await generateHomebrewTap(tapDir, pkg, release);
3542

3643
expect((await readdir(formulaDir)).sort()).toEqual(["corbits-code.rb"]);
3744
const formula = await readFile(join(formulaDir, "corbits-code.rb"), "utf8");
@@ -54,7 +61,7 @@ describe("generateHomebrewTap", () => {
5461
await writeFile(join(formulaDir, "corbits-code.rb"), currentFormula);
5562
await writeFile(join(caseDir, "formula_renames.json"), metadata);
5663

57-
await expect(generateHomebrewTap(caseDir, release)).rejects.toThrow(
64+
await expect(generateHomebrewTap(caseDir, pkg, release)).rejects.toThrow(
5865
"Invalid formula rename metadata",
5966
);
6067

@@ -69,15 +76,15 @@ describe("generateHomebrewTap", () => {
6976
`${JSON.stringify({ retained: "other-formula" }, null, 2)}\n`,
7077
);
7178

72-
await generateHomebrewTap(tapDir, release);
79+
await generateHomebrewTap(tapDir, pkg, release);
7380

7481
const first = await readFile(join(tapDir, "formula_renames.json"), "utf8");
7582
expect(JSON.parse(first)).toEqual({
7683
retained: "other-formula",
7784
corbits: "corbits-code",
7885
});
7986

80-
await generateHomebrewTap(tapDir, release);
87+
await generateHomebrewTap(tapDir, pkg, release);
8188
expect(await readFile(join(tapDir, "formula_renames.json"), "utf8")).toBe(first);
8289
});
8390
});

0 commit comments

Comments
 (0)