Skip to content

Commit 3f6cfc1

Browse files
feat: Add CI/CD to publish validator binaries (#310)
## Description Add CI/CD action to publish validator binaries <!-- greptile_comment --> ## Summary This PR adds a comprehensive CI/CD workflow for building and publishing ephemeral validator binaries across multiple platforms and architectures. - Added `.github/workflows/publish-packages.yml` to automate building and publishing binaries for 7 platform/architecture combinations (Linux x64/arm64, Windows x64, macOS x64/arm64) - Configured workflow to trigger on release publications, pushes to release branches, or manual dispatch with version input - Implemented binary publishing to both GitHub releases and NPM registry with proper naming conventions - Added support for dry-run mode when triggered from release branches for testing the publishing process - Created a separate job for publishing a wrapper NPM package that depends on the platform-specific packages <!-- /greptile_comment --> # Issues Closes #285 Closes #265
1 parent 69d0762 commit 3f6cfc1

17 files changed

+532
-51
lines changed

.github/actions/setup-build-env/action.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ runs:
5050

5151
- name: Install Rust
5252
shell: "bash"
53-
run: rustup toolchain install ${{ inputs.rust_toolchain_release }} --profile minimal
53+
run: rustup toolchain install ${{ inputs.rust_toolchain_release }} --profile default
5454

5555
- uses: Swatinem/rust-cache@v2
5656
with:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lib/**
2+
scripts/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/usr/bin/env node
2+
import fs from "fs";
3+
import { spawn, spawnSync } from "child_process";
4+
import path from "path";
5+
import { arch, platform } from "os";
6+
import { version } from "./package.json";
7+
8+
const PACKAGE_VERSION = `ephemeral-validator ${version}`;
9+
10+
function getBinaryVersion(location: string): [string | null, string | null] {
11+
const result = spawnSync(location, ["--version"]);
12+
const error: string | null =
13+
(result.error && result.error.toString()) ||
14+
(result.stderr.length > 0 && result.stderr.toString().trim()) ||
15+
null;
16+
return [error, result.stdout && result.stdout.toString().trim()];
17+
}
18+
19+
function getExePath(): string {
20+
let os: string = platform();
21+
let extension = "";
22+
if (["win32", "cygwin"].includes(os)) {
23+
os = "windows";
24+
extension = ".exe";
25+
}
26+
const binaryName = `@magicblock-labs/ephemeral-validator-${os}-${arch()}/bin/ephemeral-validator${extension}`;
27+
try {
28+
return require.resolve(binaryName);
29+
} catch (e) {
30+
throw new Error(
31+
`Couldn't find application binary inside node_modules for ${os}-${arch()}, expected location: ${binaryName}`,
32+
);
33+
}
34+
}
35+
36+
function runEphemeralValidator(location: string): void {
37+
const args = process.argv.slice(2);
38+
const ephemeralValidator = spawn(location, args, { stdio: "inherit" });
39+
ephemeralValidator.on("exit", (code: number | null, signal: NodeJS.Signals | null) => {
40+
process.on("exit", () => {
41+
if (signal) {
42+
process.kill(process.pid, signal);
43+
} else if (code !== null) {
44+
process.exit(code);
45+
}
46+
});
47+
});
48+
49+
process.on("SIGINT", () => {
50+
ephemeralValidator.kill("SIGINT");
51+
ephemeralValidator.kill("SIGTERM");
52+
});
53+
}
54+
55+
function tryPackageEphemeralValidator(): boolean {
56+
try {
57+
const path = getExePath();
58+
runEphemeralValidator(path);
59+
return true;
60+
} catch (e) {
61+
console.error(
62+
"Failed to run bolt from package:",
63+
e instanceof Error ? e.message : e,
64+
);
65+
return false;
66+
}
67+
}
68+
69+
function trySystemEphemeralValidator(): void {
70+
const absolutePath = process.env.PATH?.split(path.delimiter)
71+
.filter((dir) => dir !== path.dirname(process.argv[1]))
72+
.find((dir) => {
73+
try {
74+
fs.accessSync(`${dir}/ephemeral-validator`, fs.constants.X_OK);
75+
return true;
76+
} catch {
77+
return false;
78+
}
79+
});
80+
81+
if (!absolutePath) {
82+
console.error(
83+
`Could not find globally installed ephemeral-validator, please install with cargo.`,
84+
);
85+
process.exit(1);
86+
}
87+
88+
const absoluteBinaryPath = `${absolutePath}/ephemeral-validator`;
89+
const [error, binaryVersion] = getBinaryVersion(absoluteBinaryPath);
90+
91+
if (error !== null) {
92+
console.error(`Failed to get version of global binary: ${error}`);
93+
return;
94+
}
95+
if (binaryVersion !== PACKAGE_VERSION) {
96+
console.error(
97+
`Globally installed ephemeral-validator version is not correct. Expected "${PACKAGE_VERSION}", found "${binaryVersion}".`,
98+
);
99+
return;
100+
}
101+
102+
runEphemeralValidator(absoluteBinaryPath);
103+
}
104+
tryPackageEphemeralValidator() || trySystemEphemeralValidator();
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "@magicblock-labs/ephemeral-validator",
3+
"version": "0.1.0",
4+
"description": "MagicBlock Ephemeral Validator",
5+
"homepage": "https://github.com/magicblock-labs/ephemeral-validator#readme",
6+
"bugs": {
7+
"url": "https://github.com/magicblock-labs/ephemeral-validator/issues"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "https://github.com/magicblock-labs/ephemeral-validator.git"
12+
},
13+
"license": "Business Source License 1.1",
14+
"bin": {
15+
"ephemeral-validator": "ephemeralValidator.js"
16+
},
17+
"scripts": {
18+
"typecheck": "tsc --noEmit",
19+
"lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check ",
20+
"lint:fix": "node_modules/.bin/prettier */*.js \"*/**/*{.js,.ts}\" -w",
21+
"build": "tsc",
22+
"dev": "yarn build && node lib/index.js"
23+
},
24+
"devDependencies": {
25+
"@types/node": "^20.8.8",
26+
"prettier": "^3.3.3",
27+
"typescript": "^4.9.4"
28+
},
29+
"optionalDependencies": {
30+
"@magicblock-labs/ephemeral-validator-darwin-arm64": "^0.1.0",
31+
"@magicblock-labs/ephemeral-validator-darwin-x64": "0.1.0",
32+
"@magicblock-labs/ephemeral-validator-linux-arm64": "0.1.0",
33+
"@magicblock-labs/ephemeral-validator-linux-x64": "0.1.0",
34+
"@magicblock-labs/ephemeral-validator-windows-x64": "0.1.0"
35+
},
36+
"publishConfig": {
37+
"access": "public"
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "@magicblock-labs/${node_pkg}",
3+
"description": "Ephemeral Validator (${node_pkg})",
4+
"version": "0.1.0",
5+
"repository": {
6+
"type": "git",
7+
"url": "git+https://github.com/magicblock-labs/ephemeral-validator.git"
8+
},
9+
"bugs": {
10+
"url": "https://github.com/magicblock-labs/ephemeral-validator/issues"
11+
},
12+
"license": "Business Source License 1.1",
13+
"private": false,
14+
"author": "Magicblock Labs <[email protected]>",
15+
"homepage": "https://www.magicblock.xyz/",
16+
"os": [
17+
"${node_os}"
18+
],
19+
"cpu": [
20+
"${node_arch}"
21+
],
22+
"publishConfig": {
23+
"access": "public",
24+
"registry": "https://registry.npmjs.org/"
25+
}
26+
}
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es2016",
4+
"module": "commonjs",
5+
"esModuleInterop": true,
6+
"resolveJsonModule": true,
7+
"baseUrl": "./",
8+
"outDir": "lib",
9+
"forceConsistentCasingInFileNames": true,
10+
"strict": true,
11+
"skipLibCheck": true,
12+
}
13+
}

.github/version-align.sh

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
# Step 1: Read the version from Cargo.toml
6+
version=$(grep '^version = ' ../Cargo.toml | head -n 1 | sed 's/version = "\(.*\)"/\1/')
7+
8+
if [ -z "$version" ]; then
9+
echo "Version not found in Cargo.toml"
10+
exit 1
11+
fi
12+
13+
echo "Aligning for version: $version"
14+
15+
# GNU/BSD compat
16+
sedi=(-i'')
17+
case "$(uname)" in
18+
# For macOS, use two parameters
19+
Darwin*) sedi=(-i '')
20+
esac
21+
22+
# Update the version in crates/bolt-cli/npm-package/package.json.tmpl
23+
jq --arg version "$version" '.version = $version' packages/npm-package/package.json.tmpl > temp.json && mv temp.json packages/npm-package/package.json.tmpl
24+
25+
# Update the main package version and all optionalDependencies versions in crates/bolt-cli/npm-package/package.json
26+
jq --arg version "$version" '(.version = $version) | (.optionalDependencies[] = $version)' packages/npm-package/package.json > temp.json && mv temp.json packages/npm-package/package.json
27+
28+
# Check if the any changes have been made to the specified files, if running with --check
29+
if [[ "$1" == "--check" ]]; then
30+
files_to_check=(
31+
"clients/typescript/package.json"
32+
"packages/npm-package/package.json.tmpl"
33+
"packages/package.json"
34+
)
35+
36+
for file in "${files_to_check[@]}"; do
37+
# Check if the file has changed from the previous commit
38+
if git diff --name-only | grep -q "$file"; then
39+
echo "Error: version not aligned for $file. Align the version, commit and try again."
40+
exit 1
41+
fi
42+
done
43+
exit 0
44+
fi

.github/workflows/ci-fmt.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- uses: ./magicblock-validator/.github/actions/setup-build-env
2121
with:
2222
build_cache_key_name: "magicblock-validator-ci-fmt-v001"
23-
rust_toolchain_release: "1.83.0"
23+
rust_toolchain_release: "1.84.1"
2424
github_access_token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
2525
github_token: ${{ secrets.GITHUB_TOKEN }}
2626

.github/workflows/ci-lint.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- uses: ./magicblock-validator/.github/actions/setup-build-env
2121
with:
2222
build_cache_key_name: "magicblock-validator-ci-lint-v002"
23-
rust_toolchain_release: "1.83.0"
23+
rust_toolchain_release: "1.84.1"
2424
github_access_token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
2525
github_token: ${{ secrets.GITHUB_TOKEN }}
2626

.github/workflows/ci-test-integration.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- uses: ./magicblock-validator/.github/actions/setup-build-env
2121
with:
2222
build_cache_key_name: "magicblock-validator-ci-test-integration-v000"
23-
rust_toolchain_release: "1.83.0"
23+
rust_toolchain_release: "1.84.1"
2424
github_access_token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
2525
github_token: ${{ secrets.GITHUB_TOKEN }}
2626

.github/workflows/ci-test-unit.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- uses: ./magicblock-validator/.github/actions/setup-build-env
2121
with:
2222
build_cache_key_name: "magicblock-validator-ci-test-unit-v000"
23-
rust_toolchain_release: "1.83.0"
23+
rust_toolchain_release: "1.84.1"
2424
github_access_token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
2525
github_token: ${{ secrets.GITHUB_TOKEN }}
2626

0 commit comments

Comments
 (0)