Skip to content

Commit 7b86d02

Browse files
revert: readd release script
1 parent ef1783e commit 7b86d02

1 file changed

Lines changed: 235 additions & 0 deletions

File tree

src/scripts/release.ts

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
import { Argument, Command } from "@commander-js/extra-typings";
2+
import * as console from "node:console";
3+
import process from "node:process";
4+
5+
const program = new Command();
6+
7+
function logAndError(msg: string) {
8+
console.error(msg);
9+
process.exit(1);
10+
}
11+
12+
function bumpVersion(
13+
version: string,
14+
type: "major" | "minor" | "patch" | "prerelease",
15+
) {
16+
const [major, minor, patch] = version.split(".").map((v) =>
17+
Number.parseInt(
18+
// Remove everything after the - if there is one
19+
v.includes("-") ? v.split("-")[0] : v,
20+
)
21+
);
22+
switch (type) {
23+
case "major":
24+
return `${major + 1}.0.0`;
25+
case "minor":
26+
return `${major}.${minor + 1}.0`;
27+
case "patch":
28+
return `${major}.${minor}.${patch + 1}`;
29+
case "prerelease":
30+
return `${major}.${minor}.${patch}-pre.${Date.now()}`;
31+
default:
32+
throw new Error(`Invalid release type: ${type}`);
33+
}
34+
}
35+
36+
async function getLocalVersion() {
37+
const packageJson = await Deno.readTextFile("package.json");
38+
return JSON.parse(packageJson).version;
39+
}
40+
41+
async function saveVersion(version: string) {
42+
const packageJson = await Deno.readTextFile("package.json");
43+
const packageObj = JSON.parse(packageJson);
44+
packageObj.version = version;
45+
// Ensure exactly one newline at the end of the file
46+
await Deno.writeTextFile(
47+
"package.json",
48+
`${JSON.stringify(packageObj, null, 2)}\n`,
49+
);
50+
}
51+
52+
const COMPILE_TARGETS: string[] = [
53+
"x86_64-unknown-linux-gnu",
54+
"aarch64-unknown-linux-gnu",
55+
"x86_64-apple-darwin",
56+
"aarch64-apple-darwin",
57+
];
58+
59+
async function compileDistribution() {
60+
for (const target of COMPILE_TARGETS) {
61+
const result = await new Deno.Command("deno", {
62+
args: [
63+
"compile",
64+
"-A",
65+
"--target",
66+
target,
67+
"--output",
68+
`dist/sf-${target}`,
69+
"./src/index.ts",
70+
],
71+
}).output();
72+
73+
if (!result.success) {
74+
console.error(new TextDecoder().decode(result.stderr));
75+
logAndError(`Failed to compile for ${target}`);
76+
}
77+
console.log(`✅ Compiled for ${target}`);
78+
79+
const zipFileName = `dist/sf-${target}.zip`;
80+
const zipResult = await new Deno.Command("zip", {
81+
args: ["-j", zipFileName, `dist/sf-${target}`],
82+
}).output();
83+
84+
if (!zipResult.success) {
85+
console.error(zipResult.stderr);
86+
logAndError(`Failed to zip the binary for ${target}`);
87+
}
88+
console.log(`✅ Zipped binary for ${target}`);
89+
}
90+
}
91+
92+
async function asyncSpawn(cmds: string[]) {
93+
console.log("cmds", cmds);
94+
const result = await new Deno.Command(cmds[0], {
95+
args: cmds.slice(1),
96+
}).output();
97+
98+
return {
99+
exitCode: result.success ? 0 : 1,
100+
};
101+
}
102+
async function createRelease(version: string) {
103+
// Verify zip files are valid before creating release
104+
const distFiles = Array.from(Deno.readDirSync("./dist"));
105+
const zipFiles = distFiles
106+
.filter((entry) => entry.isFile)
107+
.filter((entry) => entry.name.endsWith(".zip"))
108+
.map((entry) => `./dist/${entry.name}`);
109+
110+
console.log(zipFiles);
111+
112+
// Verify each zip file is valid
113+
for (const zipFile of zipFiles) {
114+
const verifyResult = await new Deno.Command("unzip", {
115+
args: ["-t", zipFile],
116+
}).output();
117+
118+
if (!verifyResult.success) {
119+
logAndError(`Invalid zip file: ${zipFile}`);
120+
}
121+
console.log(`✅ Verified zip file: ${zipFile}`);
122+
}
123+
124+
const releaseFlag = version.includes("pre") ? "--prerelease" : "--latest";
125+
const result = await asyncSpawn([
126+
"gh",
127+
"release",
128+
"create",
129+
version,
130+
...zipFiles,
131+
"--generate-notes",
132+
releaseFlag,
133+
]);
134+
if (result.exitCode !== 0) {
135+
console.log(
136+
"GitHub release creation failed with exit code:",
137+
result.exitCode,
138+
);
139+
console.log("Common failure reasons:");
140+
console.log("- GitHub CLI not installed or not authenticated");
141+
console.log("- Release tag already exists");
142+
console.log("- No write permissions to repository");
143+
console.log("- Network connectivity issues");
144+
logAndError(`Failed to create GitHub release for version ${version}`);
145+
}
146+
console.log(`✅ Created GitHub release for version ${version}`);
147+
148+
const gitAddResult = await asyncSpawn(["git", "add", "package.json"]);
149+
if (gitAddResult.exitCode !== 0) {
150+
logAndError("Failed to add package.json to git");
151+
}
152+
console.log("✅ Added package.json to git");
153+
154+
const gitCommitResult = await asyncSpawn([
155+
"git",
156+
"commit",
157+
"-m",
158+
`release: v${version}`,
159+
]);
160+
if (gitCommitResult.exitCode !== 0) {
161+
logAndError(`Failed to commit with message "release: v${version}"`);
162+
}
163+
console.log(`✅ Committed with message "release: v${version}"`);
164+
165+
const gitPushResult = await asyncSpawn(["git", "push", "origin", "main"]);
166+
if (gitPushResult.exitCode !== 0) {
167+
logAndError("Failed to push to origin main");
168+
}
169+
console.log("✅ Pushed to origin main");
170+
}
171+
172+
async function cleanDist() {
173+
try {
174+
await Deno.remove("./dist", { recursive: true });
175+
} catch (error) {
176+
if (!(error instanceof Deno.errors.NotFound)) {
177+
throw error;
178+
}
179+
}
180+
}
181+
182+
program
183+
.name("release")
184+
.description(
185+
"A github release tool for the project. Valid types are: major, minor, patch, prerelease",
186+
)
187+
.addArgument(
188+
new Argument("type").choices(
189+
[
190+
"major",
191+
"minor",
192+
"patch",
193+
"prerelease",
194+
] as const,
195+
),
196+
)
197+
.action(async (type) => {
198+
try {
199+
const ghCheckResult = await new Deno.Command("which", {
200+
args: ["gh"],
201+
}).output();
202+
203+
if (!ghCheckResult.success) {
204+
console.error(
205+
`The 'gh' command is not installed. Please install it.
206+
207+
$ brew install gh
208+
209+
`,
210+
);
211+
process.exit(1);
212+
}
213+
214+
process.on("SIGINT", () => {
215+
console.log(
216+
"\nRelease process interrupted. Please confirm to exit (ctrl-c again to confirm).",
217+
);
218+
process.once("SIGINT", () => {
219+
console.log("Exiting...");
220+
process.exit(1);
221+
});
222+
});
223+
224+
await cleanDist();
225+
const version = await getLocalVersion();
226+
const bumpedVersion = bumpVersion(version, type);
227+
await saveVersion(bumpedVersion);
228+
await compileDistribution();
229+
await createRelease(bumpedVersion);
230+
} catch (err) {
231+
console.error(err);
232+
}
233+
});
234+
235+
program.parse(process.argv);

0 commit comments

Comments
 (0)