@reliverse/rse-sdk
Version:
@reliverse/rse-sdk allows you to create new plugins for @reliverse/rse CLI, interact with reliverse.org, and even extend your own CLI functionality (you may also try @reliverse/dler-sdk for this case).
263 lines (262 loc) • 8.25 kB
JavaScript
import { re } from "@reliverse/relico";
import { relinka } from "@reliverse/relinka";
import {
cancel,
confirm,
isCancel,
select,
spinner,
text
} from "@reliverse/rempts";
import { $ } from "execa";
import os from "node:os";
import path from "node:path";
import {
addEnvVariablesToFile
} from "../project-generation/env-setup.js";
import { commandExists } from "../../utils/command-exists.js";
async function isTursoInstalled() {
return commandExists("turso");
}
async function isTursoLoggedIn() {
try {
const output = await $`turso auth whoami`;
return !output.stdout.includes("You are not logged in");
} catch {
return false;
}
}
async function loginToTurso() {
const s = spinner();
try {
s.start("Logging in to Turso...");
await $`turso auth login`;
s.stop("Logged into Turso");
return true;
} catch (_error) {
s.stop(re.red("Failed to log in to Turso"));
}
}
async function installTursoCLI(isMac) {
const s = spinner();
try {
s.start("Installing Turso CLI...");
if (isMac) {
await $`brew install tursodatabase/tap/turso`;
} else {
const { stdout: installScript } = await $`curl -sSfL https://get.tur.so/install.sh`;
await $`bash -c '${installScript}'`;
}
s.stop("Turso CLI installed");
return true;
} catch (error) {
if (error instanceof Error && error.message.includes("User force closed")) {
s.stop("Turso CLI installation cancelled");
relinka("warn", re.yellow("Turso CLI installation cancelled by user"));
throw new Error("Installation cancelled");
}
s.stop(re.red("Failed to install Turso CLI"));
}
}
async function getTursoGroups() {
const s = spinner();
try {
s.start("Fetching Turso groups...");
const { stdout } = await $`turso group list`;
const lines = stdout.trim().split("\n");
if (lines.length <= 1) {
s.stop("No Turso groups found");
return [];
}
const groups = lines.slice(1).map((line) => {
const [name, locations, version, status] = line.trim().split(/\s{2,}/);
if (!name || !locations || !version || !status) return null;
return { name, locations, version, status };
}).filter((group) => group !== null);
s.stop(`Found ${groups.length} Turso groups`);
return groups;
} catch (error) {
s.stop(re.red("Error fetching Turso groups"));
console.error("Error fetching Turso groups:", error);
return [];
}
}
async function selectTursoGroup() {
const groups = await getTursoGroups();
if (groups.length === 0) {
return null;
}
if (groups.length === 1) {
relinka(
"info",
`Using the only available group: ${re.blue(groups[0]?.name ?? "")}`
);
return groups[0]?.name ?? null;
}
const groupOptions = groups.map((group) => ({
value: group.name,
label: `${group.name} (${group.locations})`
}));
const selectedGroup = await select({
message: "Select a Turso database group:",
options: groupOptions
});
if (isCancel(selectedGroup)) {
cancel(re.red("Operation cancelled"));
process.exit(0);
}
return selectedGroup;
}
async function createTursoDatabase(dbName, groupName) {
const s = spinner();
try {
s.start(
`Creating Turso database "${dbName}"${groupName ? ` in group "${groupName}"` : ""}...`
);
if (groupName) {
await $`turso db create ${dbName} --group ${groupName}`;
} else {
await $`turso db create ${dbName}`;
}
s.stop(`Turso database "${dbName}" created`);
} catch (error) {
s.stop(re.red(`Failed to create database "${dbName}"`));
if (error instanceof Error && error.message.includes("already exists")) {
throw new Error("DATABASE_EXISTS");
}
}
s.start("Retrieving database connection details...");
try {
const { stdout: dbUrl } = await $`turso db show ${dbName} --url`;
const { stdout: authToken } = await $`turso db tokens create ${dbName}`;
s.stop("Database connection details retrieved");
return {
dbUrl: dbUrl.trim(),
authToken: authToken.trim()
};
} catch (_error) {
s.stop(re.red("Failed to retrieve database connection details"));
}
}
async function writeEnvFile(projectDir, config) {
const envPath = path.join(projectDir, "apps/server", ".env");
const variables = [
{
key: "DATABASE_URL",
value: config?.dbUrl ?? "",
condition: true
},
{
key: "DATABASE_AUTH_TOKEN",
value: config?.authToken ?? "",
condition: true
}
];
await addEnvVariablesToFile(envPath, variables);
}
function displayManualSetupInstructions() {
relinka(
"info",
`Manual Turso Setup Instructions:
1. Visit https://turso.tech and create an account
2. Create a new database from the dashboard
3. Get your database URL and authentication token
4. Add these credentials to the .env file in apps/server/.env
DATABASE_URL=your_database_url
DATABASE_AUTH_TOKEN=your_auth_token`
);
}
export async function setupTurso(config) {
const { orm, projectDir } = config;
const _isDrizzle = orm === "drizzle";
const setupSpinner = spinner();
setupSpinner.start("Checking Turso CLI availability...");
try {
const platform = os.platform();
const isMac = platform === "darwin";
const _isLinux = platform === "linux";
const isWindows = platform === "win32";
if (isWindows) {
setupSpinner.stop(re.yellow("Turso setup not supported on Windows"));
relinka(
"warn",
re.yellow("Automatic Turso setup is not supported on Windows.")
);
await writeEnvFile(projectDir);
displayManualSetupInstructions();
return;
}
setupSpinner.stop("Turso CLI availability checked");
const isCliInstalled = await isTursoInstalled();
if (!isCliInstalled) {
const shouldInstall = await confirm({
/*
Argument of type '{ message: string; initialValue: true; }' is not assignable to parameter of type 'ConfirmPromptOptions'.
Property 'title' is missing in type '{ message: string; initialValue: true; }' but required in type 'ConfirmPromptOptions'.ts(2345)
types.d.ts(118, 5): 'title' is declared here.
*/
title: "Turso CLI Installation",
message: "Would you like to install Turso CLI?",
initialValue: true
});
if (isCancel(shouldInstall)) {
cancel(re.red("Operation cancelled"));
process.exit(0);
}
if (!shouldInstall) {
await writeEnvFile(projectDir);
displayManualSetupInstructions();
return;
}
await installTursoCLI(isMac);
}
const isLoggedIn = await isTursoLoggedIn();
if (!isLoggedIn) {
await loginToTurso();
}
const selectedGroup = await selectTursoGroup();
let success = false;
let dbName = "";
let suggestedName = path.basename(projectDir);
while (!success) {
const dbNameResponse = await text({
message: "Enter a name for your database:",
defaultValue: suggestedName,
initialValue: suggestedName,
placeholder: suggestedName
});
if (isCancel(dbNameResponse)) {
cancel(re.red("Operation cancelled"));
process.exit(0);
}
dbName = dbNameResponse;
try {
const config2 = await createTursoDatabase(dbName, selectedGroup);
await writeEnvFile(projectDir, config2);
success = true;
} catch (error) {
if (error instanceof Error && error.message === "DATABASE_EXISTS") {
relinka(
"warn",
re.yellow(`Database "${re.red(dbName)}" already exists`)
);
suggestedName = `${dbName}-${Math.floor(Math.random() * 1e3)}`;
} else {
throw error;
}
}
}
relinka("success", "Turso database setup completed successfully!");
} catch (error) {
setupSpinner.stop(re.red("Turso CLI availability check failed"));
relinka(
"error",
re.red(
`Error during Turso setup: ${error instanceof Error ? error.message : String(error)}`
)
);
await writeEnvFile(projectDir);
displayManualSetupInstructions();
relinka("success", "Setup completed with manual configuration required.");
}
}