diff --git a/packages/create/src/recipes/_edgedb/index.ts b/packages/create/src/recipes/_edgedb/index.ts index 14c6fca61..47e29dd21 100644 --- a/packages/create/src/recipes/_edgedb/index.ts +++ b/packages/create/src/recipes/_edgedb/index.ts @@ -2,38 +2,116 @@ import * as p from "@clack/prompts"; import fs from "node:fs/promises"; import path from "node:path"; import debug from "debug"; -import util from "node:util"; -import childProcess from "node:child_process"; -import { BaseOptions, Recipe } from "../types.js"; +import type { BaseOptions, Recipe } from "../types.js"; +import { copyTemplateFiles, execInLoginShell } from "../../utils.js"; const logger = debug("@edgedb/create:recipe:edgedb"); -const exec = util.promisify(childProcess.exec); -const recipe: Recipe = { - async apply({ projectDir, useEdgeDBAuth }: BaseOptions) { +interface EdgeDBOptions { + initializeProject: boolean; +} + +const recipe: Recipe = { + getOptions() { + return p.group({ + initializeProject: () => + p.confirm({ + message: "Initialize a new EdgeDB project with edgedb project init?", + initialValue: true, + }), + }); + }, + + async apply( + { projectDir, useEdgeDBAuth }: BaseOptions, + { initializeProject }: EdgeDBOptions + ) { logger("Running edgedb recipe"); + logger("Checking for existing EdgeDB CLI"); const spinner = p.spinner(); - spinner.start("Initializing EdgeDB project"); - await exec("edgedb project init --non-interactive", { cwd: projectDir }); - const { stdout, stderr } = await exec( - "edgedb query 'select sys::get_version_as_str()'", - { cwd: projectDir } - ); - const serverVersion = JSON.parse(stdout.trim()); - logger(`EdgeDB version: ${serverVersion}`); - if (serverVersion === "") { - const err = new Error( - "There was a problem initializing the EdgeDB project" - ); - spinner.stop(err.message); - logger({ stdout, stderr }); + if (initializeProject) { + let edgedbCliVersion: string | null = null; + let shouldInstallCli: boolean | symbol = true; + try { + const { stdout } = await execInLoginShell("edgedb --version"); + edgedbCliVersion = stdout.trim(); + logger(edgedbCliVersion); + } catch (error) { + logger("No EdgeDB CLI detected"); + shouldInstallCli = await p.confirm({ + message: + "The EdgeDB CLI is required to initialize a project. Install now?", + initialValue: true, + }); + } + + if (edgedbCliVersion === null) { + if (shouldInstallCli === false) { + logger("User declined to install EdgeDB CLI"); + throw new Error("EdgeDB CLI is required"); + } + + logger("Installing EdgeDB CLI"); - throw err; + spinner.start("Installing EdgeDB CLI"); + const { stdout, stderr } = await execInLoginShell( + "curl --proto '=https' --tlsv1.2 -sSf https://sh.edgedb.com | sh -s -- -y" + ); + logger({ stdout, stderr }); + spinner.stop("EdgeDB CLI installed"); + } + + try { + const { stdout } = await execInLoginShell("edgedb --version"); + edgedbCliVersion = stdout.trim(); + logger(edgedbCliVersion); + } catch (error) { + logger("EdgeDB CLI could not be installed."); + logger(error); + throw new Error("EdgeDB CLI could not be installed."); + } + + spinner.start("Initializing EdgeDB project"); + try { + await execInLoginShell("edgedb project init --non-interactive", { + cwd: projectDir, + }); + const { stdout, stderr } = await execInLoginShell( + "edgedb query 'select sys::get_version_as_str()'", + { cwd: projectDir } + ); + const serverVersion = JSON.parse(stdout.trim()); + logger(`EdgeDB server version: ${serverVersion}`); + if (serverVersion === "") { + const err = new Error( + "There was a problem initializing the EdgeDB project" + ); + spinner.stop(err.message); + logger({ stdout, stderr }); + + throw err; + } + spinner.stop(`EdgeDB v${serverVersion} project initialized`); + } catch (error) { + logger(error); + throw error; + } finally { + spinner.stop(); + } + } else { + logger("Skipping edgedb project init"); + logger("Copying basic EdgeDB project files"); + + const dirname = path.dirname(new URL(import.meta.url).pathname); + await copyTemplateFiles( + path.resolve(dirname, "./template"), + projectDir, + new Set() + ); } - spinner.stop(`EdgeDB v${serverVersion} project initialized`); if (useEdgeDBAuth) { logger("Adding auth extension to project"); diff --git a/packages/create/src/recipes/_edgedb/template/dbschema/default.esdl b/packages/create/src/recipes/_edgedb/template/dbschema/default.esdl new file mode 100644 index 000000000..4bda52523 --- /dev/null +++ b/packages/create/src/recipes/_edgedb/template/dbschema/default.esdl @@ -0,0 +1,3 @@ +module default { + +} diff --git a/packages/create/src/recipes/_install/index.ts b/packages/create/src/recipes/_install/index.ts index 241b1d779..249aa1bc0 100644 --- a/packages/create/src/recipes/_install/index.ts +++ b/packages/create/src/recipes/_install/index.ts @@ -1,8 +1,7 @@ import debug from "debug"; import * as p from "@clack/prompts"; -import * as utils from "../../utils.js"; -import { BaseOptions, Recipe } from "../types.js"; +import type { BaseOptions, Recipe } from "../types.js"; const logger = debug("@edgedb/create:recipe:install"); diff --git a/packages/create/src/recipes/index.ts b/packages/create/src/recipes/index.ts index a8a644506..2a1da9f1d 100644 --- a/packages/create/src/recipes/index.ts +++ b/packages/create/src/recipes/index.ts @@ -5,7 +5,7 @@ import _install from "./_install/index.js"; import express from "./express/index.js"; import nextjs from "./nextjs/index.js"; -import { Recipe } from "./types.js"; +import { type Recipe } from "./types.js"; export { baseRecipe }; diff --git a/packages/create/src/utils.ts b/packages/create/src/utils.ts index 97a8b5053..046c90d8e 100644 --- a/packages/create/src/utils.ts +++ b/packages/create/src/utils.ts @@ -2,6 +2,7 @@ import process from "node:process"; import fs from "node:fs/promises"; import { type Dirent } from "node:fs"; import path from "node:path"; +import { spawn, type SpawnOptionsWithoutStdio } from "node:child_process"; export type PackageManager = "npm" | "yarn" | "pnpm" | "bun"; @@ -117,3 +118,34 @@ async function _walkDir( } } } + +export async function execInLoginShell( + command: string, + options?: SpawnOptionsWithoutStdio +): Promise<{ stdout: string; stderr: string }> { + return new Promise((resolve, reject) => { + let stdout = ""; + let stderr = ""; + const child = spawn("/bin/bash", ["-l", "-c", command], options); + child.stdout.on("data", (data) => { + stdout += data; + }); + child.stderr.on("data", (data) => { + stderr += data; + }); + child.on("close", (code) => { + if (code !== 0) { + reject( + new Error( + `\ +Command "${command}" exited with code ${code} +stderr: ${stderr} +stdout: ${stdout}` + ) + ); + } else { + resolve({ stdout, stderr }); + } + }); + }); +}