refactor: rename sandbox-daemon to sandbox-agent

This commit is contained in:
Nathan Flurry 2026-01-25 02:30:12 -08:00
parent f92ecd9b9a
commit a49ea094f3
41 changed files with 808 additions and 134 deletions

324
scripts/release/main.ts Executable file
View file

@ -0,0 +1,324 @@
#!/usr/bin/env tsx
import fs from "node:fs";
import path from "node:path";
import { spawnSync, execFileSync } from "node:child_process";
const ENDPOINT_URL =
"https://2a94c6a0ced8d35ea63cddc86c2681e7.r2.cloudflarestorage.com";
const BUCKET = "rivet-releases";
const PREFIX = "sandbox-agent";
const BINARY_FILES = [
"sandbox-agent-x86_64-unknown-linux-musl",
"sandbox-agent-x86_64-pc-windows-gnu.exe",
"sandbox-agent-x86_64-apple-darwin",
"sandbox-agent-aarch64-apple-darwin",
];
function parseArgs(argv: string[]) {
const args = new Map<string, string>();
const flags = new Set<string>();
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
if (!arg.startsWith("--")) continue;
if (arg.includes("=")) {
const [key, value] = arg.split("=");
args.set(key, value ?? "");
continue;
}
const next = argv[i + 1];
if (next && !next.startsWith("--")) {
args.set(arg, next);
i += 1;
} else {
flags.add(arg);
}
}
return { args, flags };
}
function run(cmd: string, cmdArgs: string[], options: Record<string, any> = {}) {
const result = spawnSync(cmd, cmdArgs, { stdio: "inherit", ...options });
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
}
function runCapture(
cmd: string,
cmdArgs: string[],
options: Record<string, any> = {},
) {
const result = spawnSync(cmd, cmdArgs, {
stdio: ["ignore", "pipe", "pipe"],
encoding: "utf8",
...options,
});
if (result.status !== 0) {
const stderr = result.stderr ? String(result.stderr).trim() : "";
throw new Error(`${cmd} failed: ${stderr}`);
}
return (result.stdout || "").toString().trim();
}
interface ParsedSemver {
major: number;
minor: number;
patch: number;
prerelease: string[];
}
function parseSemver(version: string): ParsedSemver {
const match = version.match(
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/,
);
if (!match) {
throw new Error(`Invalid semantic version: ${version}`);
}
return {
major: Number(match[1]),
minor: Number(match[2]),
patch: Number(match[3]),
prerelease: match[4] ? match[4].split(".") : [],
};
}
function compareSemver(a: ParsedSemver, b: ParsedSemver) {
if (a.major !== b.major) return a.major - b.major;
if (a.minor !== b.minor) return a.minor - b.minor;
return a.patch - b.patch;
}
function isStable(version: string) {
return parseSemver(version).prerelease.length === 0;
}
function getAllGitVersions() {
try {
execFileSync("git", ["fetch", "--tags", "--force", "--quiet"], {
stdio: "ignore",
});
} catch {
// best-effort
}
const output = runCapture("git", ["tag", "-l", "v*"]);
if (!output) return [];
return output
.split("\n")
.map((tag) => tag.replace(/^v/, ""))
.filter((tag) => {
try {
parseSemver(tag);
return true;
} catch {
return false;
}
})
.sort((a, b) => compareSemver(parseSemver(b), parseSemver(a)));
}
function getLatestStableVersion() {
const versions = getAllGitVersions();
const stable = versions.filter((version) => isStable(version));
return stable[0] || null;
}
function shouldTagAsLatest(version: string) {
const parsed = parseSemver(version);
if (parsed.prerelease.length > 0) {
return false;
}
const latestStable = getLatestStableVersion();
if (!latestStable) {
return true;
}
return compareSemver(parsed, parseSemver(latestStable)) > 0;
}
function getAwsEnv() {
const accessKey =
process.env.AWS_ACCESS_KEY_ID || process.env.R2_RELEASES_ACCESS_KEY_ID;
const secretKey =
process.env.AWS_SECRET_ACCESS_KEY ||
process.env.R2_RELEASES_SECRET_ACCESS_KEY;
if (!accessKey || !secretKey) {
throw new Error("Missing AWS credentials for releases bucket");
}
return {
AWS_ACCESS_KEY_ID: accessKey,
AWS_SECRET_ACCESS_KEY: secretKey,
AWS_DEFAULT_REGION: "auto",
};
}
function uploadDir(localPath: string, remotePath: string) {
const env = { ...process.env, ...getAwsEnv() };
run(
"aws",
[
"s3",
"cp",
localPath,
`s3://${BUCKET}/${remotePath}`,
"--recursive",
"--checksum-algorithm",
"CRC32",
"--endpoint-url",
ENDPOINT_URL,
],
{ env },
);
}
function uploadFile(localPath: string, remotePath: string) {
const env = { ...process.env, ...getAwsEnv() };
run(
"aws",
[
"s3",
"cp",
localPath,
`s3://${BUCKET}/${remotePath}`,
"--checksum-algorithm",
"CRC32",
"--endpoint-url",
ENDPOINT_URL,
],
{ env },
);
}
function uploadContent(content: string, remotePath: string) {
const env = { ...process.env, ...getAwsEnv() };
const result = spawnSync(
"aws",
[
"s3",
"cp",
"-",
`s3://${BUCKET}/${remotePath}`,
"--endpoint-url",
ENDPOINT_URL,
],
{
env,
input: content,
stdio: ["pipe", "inherit", "inherit"],
},
);
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
}
function buildTypescript(rootDir: string) {
const sdkDir = path.join(rootDir, "sdks", "typescript");
if (!fs.existsSync(sdkDir)) {
throw new Error(`TypeScript SDK not found at ${sdkDir}`);
}
run("npm", ["install"], { cwd: sdkDir });
run("npm", ["run", "build"], { cwd: sdkDir });
return path.join(sdkDir, "dist");
}
function uploadTypescriptArtifacts(rootDir: string, version: string, latest: boolean) {
console.log("==> Building TypeScript SDK");
const distPath = buildTypescript(rootDir);
console.log("==> Uploading TypeScript artifacts");
uploadDir(distPath, `${PREFIX}/${version}/typescript/`);
if (latest) {
uploadDir(distPath, `${PREFIX}/latest/typescript/`);
}
}
function uploadInstallScript(rootDir: string, version: string, latest: boolean) {
const installPath = path.join(
rootDir,
"scripts",
"release",
"static",
"install.sh",
);
let installContent = fs.readFileSync(installPath, "utf8");
const uploadForVersion = (versionValue: string, remoteVersion: string) => {
const content = installContent.replace(/__VERSION__/g, versionValue);
uploadContent(content, `${PREFIX}/${remoteVersion}/install.sh`);
};
uploadForVersion(version, version);
if (latest) {
uploadForVersion("latest", "latest");
}
}
function uploadBinaries(rootDir: string, version: string, latest: boolean) {
const distDir = path.join(rootDir, "dist");
if (!fs.existsSync(distDir)) {
throw new Error(`dist directory not found at ${distDir}`);
}
for (const fileName of BINARY_FILES) {
const localPath = path.join(distDir, fileName);
if (!fs.existsSync(localPath)) {
throw new Error(`Missing binary: ${localPath}`);
}
uploadFile(localPath, `${PREFIX}/${version}/${fileName}`);
if (latest) {
uploadFile(localPath, `${PREFIX}/latest/${fileName}`);
}
}
}
function main() {
const { args, flags } = parseArgs(process.argv.slice(2));
const versionArg = args.get("--version");
if (!versionArg) {
console.error("--version is required");
process.exit(1);
}
const version = versionArg.replace(/^v/, "");
parseSemver(version);
let latest: boolean;
if (flags.has("--latest")) {
latest = true;
} else if (flags.has("--no-latest")) {
latest = false;
} else {
latest = shouldTagAsLatest(version);
}
const outputPath = args.get("--output");
if (flags.has("--print-latest")) {
if (outputPath) {
fs.appendFileSync(outputPath, `latest=${latest}\n`);
} else {
process.stdout.write(latest ? "true" : "false");
}
}
if (flags.has("--upload-typescript")) {
uploadTypescriptArtifacts(process.cwd(), version, latest);
}
if (flags.has("--upload-install")) {
uploadInstallScript(process.cwd(), version, latest);
}
if (flags.has("--upload-binaries")) {
uploadBinaries(process.cwd(), version, latest);
}
}
main();

View file

@ -7,28 +7,28 @@
# shellcheck enable=require-variable-braces
set -eu
WORK_DIR="/tmp/sandbox_daemon_install"
WORK_DIR="/tmp/sandbox_agent_install"
rm -rf "$WORK_DIR"
mkdir -p "$WORK_DIR"
cd "$WORK_DIR"
SANDBOX_DAEMON_VERSION="${SANDBOX_DAEMON_VERSION:-__VERSION__}"
SANDBOX_DAEMON_BASE_URL="${SANDBOX_DAEMON_BASE_URL:-https://releases.rivet.dev}"
SANDBOX_AGENT_VERSION="${SANDBOX_AGENT_VERSION:-__VERSION__}"
SANDBOX_AGENT_BASE_URL="${SANDBOX_AGENT_BASE_URL:-https://releases.rivet.dev}"
UNAME="$(uname -s)"
ARCH="$(uname -m)"
if [ "$(printf '%s' "$UNAME" | cut -c 1-6)" = "Darwin" ]; then
if [ "$ARCH" = "x86_64" ]; then
FILE_NAME="sandbox-daemon-x86_64-apple-darwin"
FILE_NAME="sandbox-agent-x86_64-apple-darwin"
elif [ "$ARCH" = "arm64" ]; then
FILE_NAME="sandbox-daemon-aarch64-apple-darwin"
FILE_NAME="sandbox-agent-aarch64-apple-darwin"
else
echo "Unknown arch $ARCH" 1>&2
exit 1
fi
elif [ "$(printf '%s' "$UNAME" | cut -c 1-5)" = "Linux" ]; then
if [ "$ARCH" = "x86_64" ]; then
FILE_NAME="sandbox-daemon-x86_64-unknown-linux-musl"
FILE_NAME="sandbox-agent-x86_64-unknown-linux-musl"
else
echo "Unsupported Linux arch $ARCH" 1>&2
exit 1
@ -44,7 +44,7 @@ if [ -z "$BIN_DIR" ]; then
fi
set -u
INSTALL_PATH="$BIN_DIR/sandbox-daemon"
INSTALL_PATH="$BIN_DIR/sandbox-agent"
if [ ! -d "$BIN_DIR" ]; then
CHECK_DIR="$BIN_DIR"
@ -61,18 +61,18 @@ if [ ! -d "$BIN_DIR" ]; then
fi
fi
URL="$SANDBOX_DAEMON_BASE_URL/sandbox-daemon/${SANDBOX_DAEMON_VERSION}/${FILE_NAME}"
URL="$SANDBOX_AGENT_BASE_URL/sandbox-agent/${SANDBOX_AGENT_VERSION}/${FILE_NAME}"
echo "> Downloading $URL"
curl -fsSL "$URL" -o sandbox-daemon
chmod +x sandbox-daemon
curl -fsSL "$URL" -o sandbox-agent
chmod +x sandbox-agent
if [ ! -w "$BIN_DIR" ]; then
echo "> Installing sandbox-daemon to $INSTALL_PATH (requires sudo)"
sudo mv ./sandbox-daemon "$INSTALL_PATH"
echo "> Installing sandbox-agent to $INSTALL_PATH (requires sudo)"
sudo mv ./sandbox-agent "$INSTALL_PATH"
else
echo "> Installing sandbox-daemon to $INSTALL_PATH"
mv ./sandbox-daemon "$INSTALL_PATH"
echo "> Installing sandbox-agent to $INSTALL_PATH"
mv ./sandbox-agent "$INSTALL_PATH"
fi
case ":$PATH:" in
@ -84,4 +84,4 @@ case ":$PATH:" in
;;
esac
echo "sandbox-daemon installed successfully."
echo "sandbox-agent installed successfully."