mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 09:01:17 +00:00
refactor: rename sandbox-daemon to sandbox-agent
This commit is contained in:
parent
f92ecd9b9a
commit
a49ea094f3
41 changed files with 808 additions and 134 deletions
324
scripts/release/main.ts
Executable file
324
scripts/release/main.ts
Executable 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();
|
||||
Loading…
Add table
Add a link
Reference in a new issue