diff --git a/README.md b/README.md index 79302c506..75bce9447 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,7 @@ In just a few short steps we will set up a project containing a Hatchify fronten 1. Ensure you’re using [node 18 and npm 9 or above](https://nodejs.org/en/download) ```bash - node -v - npm -v + echo Node: $(node -v) && echo npm: $(npm -v) ``` 2. Create a new project: diff --git a/packages/create/src/constants.ts b/packages/create/src/constants.ts index 9f030758f..f476e8fe5 100644 --- a/packages/create/src/constants.ts +++ b/packages/create/src/constants.ts @@ -2,13 +2,6 @@ import { blue, green, yellow } from "kolorist" import type { Database, Backend, Frontend } from "./types" export const BACKENDS: Record = { - EXPRESS: { - name: "express", - display: "Express", - color: yellow, - dependencies: ["express", "@hatchifyjs/express"], - devDependencies: [], - }, KOA: { name: "koa", display: "Koa", @@ -16,16 +9,16 @@ export const BACKENDS: Record = { dependencies: ["koa", "@hatchifyjs/koa"], devDependencies: ["@types/koa", "koa-connect"], }, + EXPRESS: { + name: "express", + display: "Express", + color: yellow, + dependencies: ["express", "@hatchifyjs/express"], + devDependencies: [], + }, } export const DATABASES: Record = { - POSTGRES: { - name: "postgres", - display: "Postgres", - color: yellow, - dependencies: ["pg", "dotenv"], - devDependencies: ["@types/pg"], - }, SQLITE: { name: "sqlite", display: "SQLite", @@ -33,6 +26,13 @@ export const DATABASES: Record = { dependencies: ["sqlite3"], devDependencies: [], }, + POSTGRES: { + name: "postgres", + display: "Postgres", + color: yellow, + dependencies: ["pg", "dotenv"], + devDependencies: ["@types/pg"], + }, } export const FRONTENDS: Record = { diff --git a/packages/create/src/index.ts b/packages/create/src/index.ts index 81eeb27b8..b4c8b17e2 100755 --- a/packages/create/src/index.ts +++ b/packages/create/src/index.ts @@ -5,18 +5,19 @@ import { fileURLToPath } from "node:url" import minimist from "minimist" import prompts from "prompts" import { red, reset } from "kolorist" +import { exec } from "child_process" import { copyDir, - emptyDir, + emptyDir, execCommandExitOnError, formatTargetDir, isEmpty, isValidPackageName, - pkgFromUserAgent, + pkgFromUserAgent, readFileWithRetries, replaceStringInFile, runCommand, toValidPackageName, } from "./util" -import { DATABASES, BACKENDS, FRONTENDS } from "./constants" +import { BACKENDS, DATABASES, FRONTENDS } from "./constants" import type { Database } from "./types" // Avoids autoconversion to number of the project name by defining that the args @@ -202,6 +203,8 @@ async function init() { databaseHost, )}:${databasePort}/${encodeURIComponent(databaseName)}`) + targetDir = packageName || targetDir; + const root = path.join(cwd, targetDir) if (overwrite) { @@ -215,15 +218,15 @@ async function init() { console.log(`\nScaffolding project in ${root}...`) - runCommand( - `npm create vite@latest ${targetDir} -- --template react-ts`, - cwd, - true, - ) + execCommandExitOnError(`npm create vite@latest "${targetDir}" -- --template react-ts`) - const templatePackage = JSON.parse( - await fs.promises.readFile(path.join(root, "package.json"), "utf-8"), - ) + let templatePackage; + try { + templatePackage = JSON.parse(await readFileWithRetries(path.join(root, "package.json"))); + } catch (e) { + console.error(e) + process.exit(1) + } const backendTemplateDir = path.resolve( fileURLToPath(import.meta.url), diff --git a/packages/create/src/util.ts b/packages/create/src/util.ts index 35be1f358..ee1ad2621 100644 --- a/packages/create/src/util.ts +++ b/packages/create/src/util.ts @@ -1,6 +1,7 @@ import fs from "node:fs" import path from "node:path" import spawn from "cross-spawn" +import { exec } from "child_process" const renameFiles: Record = { _gitignore: ".gitignore", @@ -20,6 +21,34 @@ export async function replaceStringInFile( ) } +export async function readFileWithRetries(filePath: string, retries: number = 5, delay: number = 1000): Promise { + for (let attempt = 1; attempt <= retries; attempt++) { + if (fs.existsSync(filePath)) { + return await fs.promises.readFile(filePath, "utf-8"); + } + await new Promise(resolve => setTimeout(resolve, attempt * delay)); + } + throw new Error(`${filePath} could not be found after ${retries} attempts`); +} + +export function execCommandExitOnError( + command: string, + args: string[] = [], +): void { + exec(`${command} ${args.join(" ")}`, + (error, stdout, stderr) => { + if (error) { + console.error(`${command} error: ${error.message}`) + process.exit(1) + } + if (stderr) { + console.error(`${command} stderr: ${stderr}`) + process.exit(1) + } + }, + ) +} + export function runCommand( fullCommand: string, cwd: string,