@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).
180 lines (179 loc) • 6.16 kB
JavaScript
import { re } from "@reliverse/relico";
import fs from "@reliverse/relifso";
import { relinka } from "@reliverse/relinka";
import { cancel, isCancel, select, spinner, text } from "@reliverse/rempts";
import { execa } from "execa";
import path from "node:path";
import {
addEnvVariablesToFile
} from "../project-generation/env-setup.js";
import { getPackageExecutionCommand } from "../../utils/get-package-execution-command.js";
const NEON_REGIONS = [
{ label: "AWS US East (N. Virginia)", value: "aws-us-east-1" },
{ label: "AWS US East (Ohio)", value: "aws-us-east-2" },
{ label: "AWS US West (Oregon)", value: "aws-us-west-2" },
{ label: "AWS Europe (Frankfurt)", value: "aws-eu-central-1" },
{ label: "AWS Asia Pacific (Singapore)", value: "aws-ap-southeast-1" },
{ label: "AWS Asia Pacific (Sydney)", value: "aws-ap-southeast-2" },
{ label: "Azure East US 2 region (Virginia)", value: "azure-eastus2" }
];
async function executeNeonCommand(packageManager, commandArgsString, spinnerText) {
const s = spinner();
try {
const fullCommand = getPackageExecutionCommand(
packageManager,
commandArgsString
);
if (spinnerText) s.start(spinnerText);
const result = await execa(fullCommand, { shell: true });
if (spinnerText)
s.stop(
re.green(spinnerText.replace("...", "").replace("ing ", "ed ").trim())
);
return result;
} catch (error) {
if (s) s.stop(re.red(`Failed: ${spinnerText || "Command execution"}`));
throw error;
}
}
async function createNeonProject(projectName, regionId, packageManager) {
try {
const commandArgsString = `neonctl projects create --name ${projectName} --region-id ${regionId} --output json`;
const { stdout } = await executeNeonCommand(
packageManager,
commandArgsString,
`Creating Neon project "${projectName}"...`
);
const response = JSON.parse(stdout);
if (response.project && response.connection_uris && response.connection_uris.length > 0 && response.connection_uris[0]?.connection_uri && response.connection_uris[0]?.connection_parameters) {
const projectId = response.project.id;
const connectionUri = response.connection_uris[0].connection_uri;
const params = response.connection_uris[0].connection_parameters;
return {
connectionString: connectionUri,
projectId,
dbName: params.database,
roleName: params.role
};
}
relinka(
"error",
re.red("Failed to extract connection information from response")
);
return null;
} catch (_error) {
relinka("error", re.red("Failed to create Neon project"));
}
}
async function writeEnvFile(projectDir, config) {
const envPath = path.join(projectDir, "apps/server", ".env");
const variables = [
{
key: "DATABASE_URL",
value: config?.connectionString ?? "postgresql://postgres:postgres@localhost:5432/mydb?schema=public",
condition: true
}
];
await addEnvVariablesToFile(envPath, variables);
return true;
}
async function setupWithNeonDb(projectDir, packageManager) {
try {
const s = spinner();
s.start("Creating Neon database using neondb...");
const serverDir = path.join(projectDir, "apps/server");
await fs.ensureDir(serverDir);
const packageCmd = getPackageExecutionCommand(
packageManager,
"neondb --yes"
);
await execa(packageCmd, {
shell: true,
cwd: serverDir
});
s.stop(re.green("Neon database created successfully!"));
return true;
} catch (error) {
relinka("error", re.red("Failed to create database with neondb"));
throw error;
}
}
function displayManualSetupInstructions() {
relinka(
"info",
`Manual Neon PostgreSQL Setup Instructions:
1. Visit https://neon.tech and create an account
2. Create a new project from the dashboard
3. Get your connection string
4. Add the database URL to the .env file in apps/server/.env
DATABASE_URL="your_connection_string"`
);
}
export async function setupNeonPostgres(config) {
const { packageManager, projectDir } = config;
try {
const setupMethod = await select({
message: "Choose your Neon setup method:",
options: [
{
label: "Quick setup with neondb",
value: "neondb",
hint: "fastest, no auth required"
},
{
label: "Custom setup with neonctl",
value: "neonctl",
hint: "More control - choose project name and region"
}
],
initialValue: "neondb"
});
if (isCancel(setupMethod)) {
cancel(re.red("Operation cancelled"));
process.exit(0);
}
if (setupMethod === "neondb") {
await setupWithNeonDb(projectDir, packageManager);
} else {
const suggestedProjectName = path.basename(projectDir);
const projectName = await text({
message: "Enter a name for your Neon project:",
defaultValue: suggestedProjectName,
initialValue: suggestedProjectName
});
if (NEON_REGIONS.length === 0) {
throw new Error("No Neon regions available");
}
const regionId = await select({
message: "Select a region for your Neon project:",
options: NEON_REGIONS,
initialValue: NEON_REGIONS[0].value
});
if (isCancel(projectName) || isCancel(regionId)) {
cancel(re.red("Operation cancelled"));
process.exit(0);
}
const neonConfig = await createNeonProject(
projectName,
regionId,
packageManager
);
if (!neonConfig) {
throw new Error(
"Failed to create project - couldn't get connection information"
);
}
const finalSpinner = spinner();
finalSpinner.start("Configuring database connection");
await fs.ensureDir(path.join(projectDir, "apps/server"));
await writeEnvFile(projectDir, neonConfig);
finalSpinner.stop("Neon database configured!");
}
} catch (error) {
if (error instanceof Error) {
relinka("error", re.red(error.message));
}
await writeEnvFile(projectDir);
displayManualSetupInstructions();
}
}