Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
385 changes: 331 additions & 54 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"example:router-v2": "vite dev examples/router-v2",
"example:tic-tac-toe": "vite dev --config examples/tic-tac-toe/vite.config.ts",
"example:todo": "vite dev examples/todo",
"test:e2e": "npx playwright test --config=tests/e2e/playwright.config.ts"
"test:e2e": "npx playwright test --config=tests/e2e/playwright.config.ts",
"gea": "node packages/gea-cli/bin/gea.js"
},
"devDependencies": {
"@changesets/changelog-github": "^0.6.0",
Expand Down
107 changes: 58 additions & 49 deletions packages/create-gea/bin/create-gea.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
#!/usr/bin/env node

import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
import { cpSync, existsSync, readdirSync, readFileSync, renameSync, writeFileSync, mkdirSync } from 'node:fs'
import { basename, dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { stdin as input, stdout as output } from 'node:process'
import { createInterface } from 'node:readline/promises'
import { intro, outro, text, select, confirm, isCancel, cancel, spinner } from '@clack/prompts'
import { cyan, gray, green, reset } from 'kolorist'

const __dirname = dirname(fileURLToPath(import.meta.url))
const templateDir = resolve(__dirname, '../template')

function printHelp() {
console.log(`create-gea
const templatesDir = resolve(__dirname, '../templates')

Usage:
npm create gea@latest [project-name]

Examples:
npm create gea@latest my-gea-app
npm create gea@latest .
`)
}
const TEMPLATES = [
{ value: 'default', label: 'Standard', hint: 'Simple Gea app with Vite' },
{ value: 'mobile', label: 'Mobile', hint: 'Gea mobile app with touch-friendly UI' },
{ value: 'dashboard', label: 'Dashboard', hint: 'Gea dashboard starter with layout' },
]

function isValidPackageName(value) {
return /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(value)
Expand All @@ -37,19 +29,19 @@ function toValidPackageName(value) {

function getPackageManager() {
const userAgent = process.env.npm_config_user_agent ?? ''

if (userAgent.startsWith('pnpm/')) return 'pnpm'
if (userAgent.startsWith('yarn/')) return 'yarn'
if (userAgent.startsWith('bun/')) return 'bun'
return 'npm'
}

function isEmptyDir(dir) {
return readdirSync(dir).length === 0
return !existsSync(dir) || readdirSync(dir).length === 0
}

function writeTemplateFile(projectRoot, fileName, replacements) {
const filePath = resolve(projectRoot, fileName)
if (!existsSync(filePath)) return
const source = readFileSync(filePath, 'utf8')

let next = source
Expand All @@ -60,68 +52,85 @@ function writeTemplateFile(projectRoot, fileName, replacements) {
writeFileSync(filePath, next)
}

async function getTargetDir(args) {
const firstArg = args.find((arg) => !arg.startsWith('-'))
if (firstArg) return firstArg

const rl = createInterface({ input, output })
try {
const answer = await rl.question('Project name: ')
return answer.trim() || 'gea-app'
} finally {
rl.close()
}
}

async function main() {
intro(cyan('create-gea'))

const args = process.argv.slice(2)
let targetDir = args[0]

if (!targetDir) {
targetDir = await text({
message: 'Project name:',
placeholder: 'my-gea-app',
defaultValue: 'my-gea-app',
})
if (isCancel(targetDir)) {
cancel('Operation cancelled')
process.exit(0)
}
}

if (args.includes('--help') || args.includes('-h')) {
printHelp()
process.exit(0)
if (!targetDir || targetDir.trim() === '') {
cancel('Project name cannot be empty')
process.exit(1)
}

const targetDir = await getTargetDir(args)
const projectRoot = resolve(process.cwd(), targetDir)

if (existsSync(projectRoot) && !isEmptyDir(projectRoot)) {
console.error(`Target directory is not empty: ${projectRoot}`)
cancel('Target directory is not empty')
process.exit(1)
}

mkdirSync(projectRoot, { recursive: true })
const template = await select({
message: 'Select a template:',
options: TEMPLATES,
})

const rawName = targetDir === '.' ? basename(process.cwd()) : basename(projectRoot)
const packageName = isValidPackageName(rawName) ? rawName : toValidPackageName(rawName)
if (isCancel(template)) {
cancel('Operation cancelled')
process.exit(0)
}

const s = spinner()
s.start(`Scaffolding project in ${gray(projectRoot)}...`)

if (!existsSync(projectRoot)) {
mkdirSync(projectRoot, { recursive: true })
}

cpSync(templateDir, projectRoot, { recursive: true })
const selectedTemplateDir = resolve(templatesDir, template)

const templateToUse = existsSync(selectedTemplateDir) ? template : 'default'
cpSync(resolve(templatesDir, templateToUse), projectRoot, { recursive: true })

const gitignoreSource = resolve(projectRoot, '_gitignore')
if (existsSync(gitignoreSource)) {
renameSync(gitignoreSource, resolve(projectRoot, '.gitignore'))
}

const rawName = targetDir === '.' ? basename(process.cwd()) : basename(projectRoot)
const packageName = isValidPackageName(rawName) ? rawName : toValidPackageName(rawName)

writeTemplateFile(projectRoot, 'package.json', {
__PACKAGE_NAME__: packageName,
})
writeTemplateFile(projectRoot, 'index.html', {
__PROJECT_TITLE__: packageName,
})

s.stop(green(`Success! Project scaffolded in ${targetDir}`))

const packageManager = getPackageManager()
const installCommand = packageManager === 'yarn' ? 'yarn' : `${packageManager} install`
const devCommand = packageManager === 'yarn' ? 'yarn dev' : `${packageManager} run dev`

console.log(`\nScaffolded an Gea app in ${projectRoot}\n`)
console.log('Next steps:')
if (targetDir !== '.') {
console.log(` cd ${targetDir}`)
}
console.log(` ${installCommand}`)
console.log(` ${devCommand}`)
outro(`Next steps:
${targetDir !== '.' ? `cd "${targetDir}"\n ` : ''}${installCommand}\n ${devCommand}
`)
}

main().catch((error) => {
console.error(error instanceof Error ? error.message : error)
cancel(error instanceof Error ? error.message : String(error))
process.exit(1)
})
22 changes: 9 additions & 13 deletions packages/create-gea/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"name": "create-gea",
"version": "1.0.1",
"description": "Scaffold a new Gea app with Vite in seconds",
"type": "module",
"bin": {
"create-gea": "bin/create-gea.js"
Expand All @@ -11,17 +10,10 @@
},
"files": [
"bin",
"template"
"templates"
],
"keywords": [
"gea",
"create-gea",
"vite",
"scaffold",
"starter",
"boilerplate"
],
"author": "Armagan Amcalar <armagan@amcalar.com>",
"keywords": [],
"author": "Hacı Mert Gökhan <hacimertgokhan@gmail.com>",
"license": "MIT",
"repository": {
"type": "git",
Expand All @@ -30,5 +22,9 @@
"bugs": {
"url": "https://github.com/dashersw/gea/issues"
},
"homepage": "https://github.com/dashersw/gea#readme"
}
"homepage": "https://github.com/dashersw/gea#readme",
"dependencies": {
"@clack/prompts": "^1.1.0",
"kolorist": "^1.8.0"
}
}
13 changes: 13 additions & 0 deletions packages/create-gea/templates/dashboard/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Dashboard — gea-ui Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" /> -->
</head>
<body>
<div id="app"></div>
<script type="module" src="./main.ts"></script>
</body>
</html>
20 changes: 20 additions & 0 deletions packages/create-gea/templates/dashboard/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "__PACKAGE_NAME__",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@geajs/core": "^1.0.0",
"@geajs/ui": "^0.1.0"
},
"devDependencies": {
"typescript": "^5.9.3",
"vite": "^8.0.0",
"@geajs/vite-plugin": "^1.0.0"
}
}
Loading