| 
 | 1 | +import { type Rivet, RivetClient } from "@rivetkit/engine-api-full";  | 
 | 2 | +import { execSync } from "child_process";  | 
 | 3 | +import dotenv from "dotenv";  | 
 | 4 | +import { FreestyleSandboxes } from "freestyle-sandboxes";  | 
 | 5 | +import { prepareDirForDeploymentSync } from "freestyle-sandboxes/utils";  | 
 | 6 | +import { readFileSync } from "fs";  | 
 | 7 | + | 
 | 8 | +dotenv.config({ path: new URL("../.env", import.meta.url).pathname });  | 
 | 9 | + | 
 | 10 | +const FREESTYLE_DOMAIN = getEnv("FREESTYLE_DOMAIN");  | 
 | 11 | +const FREESTYLE_API_KEY = getEnv("FREESTYLE_API_KEY");  | 
 | 12 | +const RIVET_ENDPOINT = getEnv("RIVET_ENDPOINT");  | 
 | 13 | +const RIVET_TOKEN = getEnv("RIVET_TOKEN");  | 
 | 14 | +const RIVET_NAMESPACE_NAME = getEnv("RIVET_NAMESPACE");  | 
 | 15 | +const RIVET_RUNNER_NAME = "freestyle";  | 
 | 16 | + | 
 | 17 | +function getEnv(key: string): string {  | 
 | 18 | +	const value = process.env[key];  | 
 | 19 | +	if (typeof value === "undefined") {  | 
 | 20 | +		throw new Error(`Missing env var: ${key}`);  | 
 | 21 | +	}  | 
 | 22 | +	return value;  | 
 | 23 | +}  | 
 | 24 | + | 
 | 25 | +const rivet = new RivetClient({  | 
 | 26 | +	environment: RIVET_ENDPOINT,  | 
 | 27 | +	token: RIVET_TOKEN,  | 
 | 28 | +});  | 
 | 29 | + | 
 | 30 | +const freestyle = new FreestyleSandboxes({  | 
 | 31 | +	apiKey: FREESTYLE_API_KEY,  | 
 | 32 | +});  | 
 | 33 | + | 
 | 34 | +async function main() {  | 
 | 35 | +	const namespace = await getOrCreateNamespace({  | 
 | 36 | +		displayName: RIVET_NAMESPACE_NAME,  | 
 | 37 | +		name: RIVET_NAMESPACE_NAME,  | 
 | 38 | +	});  | 
 | 39 | +	console.log("Got namespace " + namespace.name);  | 
 | 40 | + | 
 | 41 | +	runBuildSteps();  | 
 | 42 | + | 
 | 43 | +	await deployToFreestyle();  | 
 | 44 | +	console.log("Deployed to freestyle");  | 
 | 45 | + | 
 | 46 | +	await updateRunnerConfig(namespace);  | 
 | 47 | +	console.log("Updated runner config");  | 
 | 48 | + | 
 | 49 | +	console.log("🎉 Deployment complete! 🎉");  | 
 | 50 | +	console.log(  | 
 | 51 | +		"Visit https://" +  | 
 | 52 | +			FREESTYLE_DOMAIN +  | 
 | 53 | +			"/ to see your frontend, which is connected to Rivet",  | 
 | 54 | +	);  | 
 | 55 | +}  | 
 | 56 | + | 
 | 57 | +function runBuildSteps() {  | 
 | 58 | +	console.log("Running build steps...");  | 
 | 59 | + | 
 | 60 | +	console.log("- Running vite build");  | 
 | 61 | +	execSync("vite build", {  | 
 | 62 | +		stdio: "inherit",  | 
 | 63 | +		env: {  | 
 | 64 | +			...process.env,  | 
 | 65 | +			VITE_RIVET_ENDPOINT: RIVET_ENDPOINT,  | 
 | 66 | +			VITE_RIVET_NAMESPACE: RIVET_NAMESPACE_NAME,  | 
 | 67 | +			VITE_RIVET_RUNNER_NAME: RIVET_RUNNER_NAME,  | 
 | 68 | +		},  | 
 | 69 | +	});  | 
 | 70 | + | 
 | 71 | +	console.log("- Running tsup");  | 
 | 72 | +	execSync("tsup", {  | 
 | 73 | +		stdio: "inherit",  | 
 | 74 | +	});  | 
 | 75 | + | 
 | 76 | +	console.log("Build complete!");  | 
 | 77 | +}  | 
 | 78 | + | 
 | 79 | +async function getOrCreateNamespace({  | 
 | 80 | +	name,  | 
 | 81 | +	displayName,  | 
 | 82 | +}: {  | 
 | 83 | +	name: string;  | 
 | 84 | +	displayName: string;  | 
 | 85 | +}): Promise<Rivet.Namespace> {  | 
 | 86 | +	console.log("- Checking for existing " + name + " namespace");  | 
 | 87 | +	const { namespaces } = await rivet.namespaces.list({  | 
 | 88 | +		limit: 32,  | 
 | 89 | +	});  | 
 | 90 | +	const existing = namespaces.find((ns) => ns.name === name);  | 
 | 91 | +	if (existing) {  | 
 | 92 | +		console.log("- Found existing namespace " + name);  | 
 | 93 | +		return existing;  | 
 | 94 | +	}  | 
 | 95 | +	console.log("- Creating namespace " + name);  | 
 | 96 | +	const { namespace } = await rivet.namespaces.create({  | 
 | 97 | +		displayName,  | 
 | 98 | +		name,  | 
 | 99 | +	});  | 
 | 100 | +	return namespace;  | 
 | 101 | +}  | 
 | 102 | + | 
 | 103 | +async function updateRunnerConfig(namespace: Rivet.Namespace) {  | 
 | 104 | +	console.log("- Updating runner config for " + RIVET_RUNNER_NAME);  | 
 | 105 | +	await rivet.runnerConfigs.upsert(RIVET_RUNNER_NAME, {  | 
 | 106 | +		serverless: {  | 
 | 107 | +			url: "https://" + FREESTYLE_DOMAIN + "/api/start",  | 
 | 108 | +			headers: {},  | 
 | 109 | +			runnersMargin: 1,  | 
 | 110 | +			minRunners: 1,  | 
 | 111 | +			maxRunners: 1,  | 
 | 112 | +			slotsPerRunner: 1,  | 
 | 113 | +			requestLifespan: 100,  | 
 | 114 | +		},  | 
 | 115 | +		namespace: namespace.name,  | 
 | 116 | +	});  | 
 | 117 | +}  | 
 | 118 | + | 
 | 119 | +async function deployToFreestyle() {  | 
 | 120 | +	console.log("- Deploying to freestyle at https://" + FREESTYLE_DOMAIN);  | 
 | 121 | + | 
 | 122 | +	const buildDir = prepareDirForDeploymentSync(  | 
 | 123 | +		new URL("../dist", import.meta.url).pathname,  | 
 | 124 | +	);  | 
 | 125 | +	if (buildDir.kind === "files") {  | 
 | 126 | +		buildDir.files["deno.json"] = {  | 
 | 127 | +			// Fix imports for Deno  | 
 | 128 | +			content: readFileSync(  | 
 | 129 | +				new URL("../deno.json", import.meta.url).pathname,  | 
 | 130 | +				"utf-8",  | 
 | 131 | +			),  | 
 | 132 | +			encoding: "utf-8",  | 
 | 133 | +		};  | 
 | 134 | +	} else {  | 
 | 135 | +		throw new Error("Expected buildDir to be files");  | 
 | 136 | +	}  | 
 | 137 | +	const res = await freestyle.deployWeb(buildDir, {  | 
 | 138 | +		envVars: {  | 
 | 139 | +			LOG_LEVEL: "debug",  | 
 | 140 | +			FREESTYLE_ENDPOINT: `https://${FREESTYLE_DOMAIN}`,  | 
 | 141 | +			RIVET_ENDPOINT,  | 
 | 142 | +			RIVET_RUNNER_KIND: "serverless",  | 
 | 143 | +		},  | 
 | 144 | +		timeout: 120,  | 
 | 145 | +		entrypoint: "server.cjs",  | 
 | 146 | +		domains: [FREESTYLE_DOMAIN],  | 
 | 147 | +		build: false,  | 
 | 148 | +	});  | 
 | 149 | + | 
 | 150 | +	console.log("Deployment id=" + res.deploymentId);  | 
 | 151 | +}  | 
 | 152 | + | 
 | 153 | +main();  | 
0 commit comments