create-codeforge-app 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/index.js +76 -0
- package/lib/cleanup.js +116 -0
- package/lib/clone.js +15 -0
- package/lib/configure.js +80 -0
- package/lib/database.js +37 -0
- package/lib/detect.js +64 -0
- package/lib/install.js +40 -0
- package/lib/prompts.js +17 -0
- package/package.json +22 -0
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# create-codeforge
|
|
2
|
+
|
|
3
|
+
一行命令创建全栈项目(NestJS + Vue3 + PostgreSQL)。
|
|
4
|
+
|
|
5
|
+
## 使用
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx create-codeforge my-project
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 前置条件
|
|
12
|
+
|
|
13
|
+
- Node.js 20+
|
|
14
|
+
- PostgreSQL 或 Docker(二选一)
|
|
15
|
+
|
|
16
|
+
## 功能
|
|
17
|
+
|
|
18
|
+
- 交互式配置(项目名/标题/数据库名)
|
|
19
|
+
- 自动检测 PostgreSQL / Docker 环境
|
|
20
|
+
- 可选移动端 H5 骨架
|
|
21
|
+
- 可选删除示例模块
|
|
22
|
+
- 自动安装依赖、初始化数据库、写入种子数据
|
|
23
|
+
- 生成随机 JWT_SECRET
|
package/index.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const { runPrompts } = require('./lib/prompts')
|
|
5
|
+
const { detect } = require('./lib/detect')
|
|
6
|
+
const { cloneTemplate } = require('./lib/clone')
|
|
7
|
+
const { configure } = require('./lib/configure')
|
|
8
|
+
const { cleanup } = require('./lib/cleanup')
|
|
9
|
+
const { setupDatabase } = require('./lib/database')
|
|
10
|
+
const { install } = require('./lib/install')
|
|
11
|
+
const chalk = require('chalk')
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
const projectName = process.argv[2]
|
|
15
|
+
if (!projectName) {
|
|
16
|
+
console.log(chalk.red('请指定项目名称:npx create-codeforge <project-name>'))
|
|
17
|
+
process.exit(1)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const targetDir = path.resolve(process.cwd(), projectName)
|
|
21
|
+
|
|
22
|
+
// 检查目录是否存在
|
|
23
|
+
const fs = require('fs')
|
|
24
|
+
if (fs.existsSync(targetDir)) {
|
|
25
|
+
const prompts = require('prompts')
|
|
26
|
+
const { overwrite } = await prompts({ type: 'confirm', name: 'overwrite', message: `目录 ${projectName} 已存在,是否覆盖?`, initial: false })
|
|
27
|
+
if (!overwrite) { console.log('已取消'); process.exit(0) }
|
|
28
|
+
fs.rmSync(targetDir, { recursive: true, force: true })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log(chalk.cyan(`\n🚀 create-codeforge v1.0.0\n`))
|
|
32
|
+
|
|
33
|
+
// 1. 交互问答
|
|
34
|
+
const answers = await runPrompts(projectName)
|
|
35
|
+
if (!answers) process.exit(0)
|
|
36
|
+
|
|
37
|
+
// 2. 检测环境
|
|
38
|
+
console.log(chalk.cyan('\n🔍 检测环境...'))
|
|
39
|
+
const env = await detect()
|
|
40
|
+
|
|
41
|
+
// 3. 克隆模板
|
|
42
|
+
console.log(chalk.cyan('\n⏳ 正在创建项目...\n'))
|
|
43
|
+
await cloneTemplate(targetDir)
|
|
44
|
+
|
|
45
|
+
// 4. 替换配置
|
|
46
|
+
await configure(targetDir, answers)
|
|
47
|
+
|
|
48
|
+
// 5. 清理示例模块
|
|
49
|
+
if (!answers.keepExample) {
|
|
50
|
+
await cleanup(targetDir)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 6. 数据库
|
|
54
|
+
await setupDatabase(targetDir, answers, env)
|
|
55
|
+
|
|
56
|
+
// 7. 安装依赖 + git init
|
|
57
|
+
await install(targetDir)
|
|
58
|
+
|
|
59
|
+
// 完成
|
|
60
|
+
console.log(chalk.green('\n✅ 项目创建成功!\n'))
|
|
61
|
+
console.log(` cd ${projectName}`)
|
|
62
|
+
console.log(` cd server && npm run start:dev ${chalk.gray('# 后端 http://localhost:3000')}`)
|
|
63
|
+
console.log(` cd client && npm run dev ${chalk.gray('# 前端 http://localhost:5173')}`)
|
|
64
|
+
console.log('')
|
|
65
|
+
console.log(` 管理员账号: ${chalk.yellow('admin@example.com / admin123')}`)
|
|
66
|
+
console.log(` Swagger 文档: ${chalk.yellow('http://localhost:3000/api-docs')}`)
|
|
67
|
+
console.log('')
|
|
68
|
+
console.log(` 📖 开发教程: TUTORIAL.md`)
|
|
69
|
+
console.log(` 🚀 部署指南: DEPLOY.md`)
|
|
70
|
+
console.log('')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
main().catch((err) => {
|
|
74
|
+
console.error(chalk.red('❌ 创建失败:'), err.message)
|
|
75
|
+
process.exit(1)
|
|
76
|
+
})
|
package/lib/cleanup.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const chalk = require('chalk')
|
|
4
|
+
|
|
5
|
+
function removeDir(dir) {
|
|
6
|
+
if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true, force: true })
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function replaceInFile(filePath, replacements) {
|
|
10
|
+
if (!fs.existsSync(filePath)) return
|
|
11
|
+
let content = fs.readFileSync(filePath, 'utf8')
|
|
12
|
+
for (const [search, replace] of replacements) {
|
|
13
|
+
if (search instanceof RegExp) content = content.replace(search, replace)
|
|
14
|
+
else content = content.split(search).join(replace)
|
|
15
|
+
}
|
|
16
|
+
fs.writeFileSync(filePath, content)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function cleanup(targetDir) {
|
|
20
|
+
console.log(` [4/8] 🗑️ 删除示例模块...`)
|
|
21
|
+
|
|
22
|
+
// 删除后端 example 模块
|
|
23
|
+
removeDir(path.join(targetDir, 'server/src/example'))
|
|
24
|
+
|
|
25
|
+
// 删除前端 example 页面
|
|
26
|
+
removeDir(path.join(targetDir, 'client/src/views/example'))
|
|
27
|
+
|
|
28
|
+
// app.module.ts — 删除 ExampleModule
|
|
29
|
+
replaceInFile(path.join(targetDir, 'server/src/app.module.ts'), [
|
|
30
|
+
[/import \{ ExampleModule \}.*?\n/, ''],
|
|
31
|
+
[/\s*ExampleModule,?\n?/, '\n'],
|
|
32
|
+
])
|
|
33
|
+
|
|
34
|
+
// schema.prisma — 删除 Article 和 Tag 模型
|
|
35
|
+
const schemaPath = path.join(targetDir, 'server/prisma/schema.prisma')
|
|
36
|
+
if (fs.existsSync(schemaPath)) {
|
|
37
|
+
let schema = fs.readFileSync(schemaPath, 'utf8')
|
|
38
|
+
// 删除 Article 模型
|
|
39
|
+
schema = schema.replace(/model Article \{[\s\S]*?\}\n*/g, '')
|
|
40
|
+
// 删除 Tag 模型
|
|
41
|
+
schema = schema.replace(/model Tag \{[\s\S]*?\}\n*/g, '')
|
|
42
|
+
// 删除 User 中的 articles 字段
|
|
43
|
+
schema = schema.replace(/\s*articles\s+Article\[\]\n?/g, '\n')
|
|
44
|
+
fs.writeFileSync(schemaPath, schema)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// seed.ts — 简化为只创建 admin
|
|
48
|
+
fs.writeFileSync(path.join(targetDir, 'server/prisma/seed.ts'), `import { PrismaClient } from '@prisma/client';
|
|
49
|
+
import * as bcrypt from 'bcrypt';
|
|
50
|
+
|
|
51
|
+
const prisma = new PrismaClient();
|
|
52
|
+
|
|
53
|
+
async function main() {
|
|
54
|
+
const password = await bcrypt.hash('admin123', 10);
|
|
55
|
+
await prisma.user.upsert({
|
|
56
|
+
where: { email: 'admin@example.com' },
|
|
57
|
+
update: {},
|
|
58
|
+
create: { email: 'admin@example.com', password, name: '管理员', role: 'ADMIN' },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
main()
|
|
63
|
+
.catch((e) => { throw e; })
|
|
64
|
+
.finally(() => prisma.$disconnect());
|
|
65
|
+
`)
|
|
66
|
+
|
|
67
|
+
// router — 删除 examples 路由
|
|
68
|
+
const routerPath = path.join(targetDir, 'client/src/router/index.ts')
|
|
69
|
+
if (fs.existsSync(routerPath)) {
|
|
70
|
+
let router = fs.readFileSync(routerPath, 'utf8')
|
|
71
|
+
// 删除 examples 路由行
|
|
72
|
+
router = router.replace(/.*path: 'examples'.*\n/g, '')
|
|
73
|
+
// 修复可能出现的 }, }, 问题
|
|
74
|
+
router = router.replace(/\},\s*\},/g, '},')
|
|
75
|
+
fs.writeFileSync(routerPath, router)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// AdminLayout — 删除示例管理菜单项
|
|
79
|
+
replaceInFile(path.join(targetDir, 'client/src/layout/AdminLayout.vue'), [
|
|
80
|
+
[/\s*\{ path: '\/examples'.*?\},?\n?/g, ''],
|
|
81
|
+
])
|
|
82
|
+
|
|
83
|
+
// api/index.ts — 删除 exampleApi
|
|
84
|
+
replaceInFile(path.join(targetDir, 'client/src/api/index.ts'), [
|
|
85
|
+
[/\nexport const exampleApi[\s\S]*?\}\n/g, '\n'],
|
|
86
|
+
])
|
|
87
|
+
|
|
88
|
+
// Dashboard — 简化
|
|
89
|
+
const dashPath = path.join(targetDir, 'client/src/views/dashboard/index.vue')
|
|
90
|
+
if (fs.existsSync(dashPath)) {
|
|
91
|
+
let dash = fs.readFileSync(dashPath, 'utf8')
|
|
92
|
+
// 替换整个 script 部分
|
|
93
|
+
dash = dash.replace(/<script setup lang="ts">[\s\S]*?<\/script>/, `<script setup lang="ts">
|
|
94
|
+
import { ref, onMounted } from 'vue'
|
|
95
|
+
import { User, Document, EditPen, Files } from '@element-plus/icons-vue'
|
|
96
|
+
import { userApi } from '@/api'
|
|
97
|
+
|
|
98
|
+
const cards = ref([
|
|
99
|
+
{ title: '用户总数', value: '-', icon: User, color: '#409eff' },
|
|
100
|
+
{ title: '待开发', value: '-', icon: Document, color: '#67c23a' },
|
|
101
|
+
{ title: '待开发', value: '-', icon: Files, color: '#e6a23c' },
|
|
102
|
+
{ title: '待开发', value: '-', icon: EditPen, color: '#f56c6c' },
|
|
103
|
+
])
|
|
104
|
+
|
|
105
|
+
onMounted(async () => {
|
|
106
|
+
try {
|
|
107
|
+
const users = await userApi.list({ page: 1, pageSize: 1 })
|
|
108
|
+
cards.value[0].value = String(users.total)
|
|
109
|
+
} catch {}
|
|
110
|
+
})
|
|
111
|
+
</script>`)
|
|
112
|
+
fs.writeFileSync(dashPath, dash)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = { cleanup }
|
package/lib/clone.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const { execSync } = require('child_process')
|
|
2
|
+
const chalk = require('chalk')
|
|
3
|
+
|
|
4
|
+
const TEMPLATE_REPO = 'https://github.com/codeforge-arch/project-template.git'
|
|
5
|
+
|
|
6
|
+
async function cloneTemplate(targetDir) {
|
|
7
|
+
console.log(` [1/8] 📥 克隆模板...`)
|
|
8
|
+
try {
|
|
9
|
+
execSync(`git clone --depth 1 ${TEMPLATE_REPO} "${targetDir}"`, { stdio: 'ignore' })
|
|
10
|
+
} catch {
|
|
11
|
+
throw new Error('克隆模板失败,请检查网络连接')
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = { cloneTemplate }
|
package/lib/configure.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const crypto = require('crypto')
|
|
4
|
+
const chalk = require('chalk')
|
|
5
|
+
|
|
6
|
+
function replaceInFile(filePath, replacements) {
|
|
7
|
+
if (!fs.existsSync(filePath)) return
|
|
8
|
+
let content = fs.readFileSync(filePath, 'utf8')
|
|
9
|
+
for (const [search, replace] of replacements) {
|
|
10
|
+
content = content.replace(search, replace)
|
|
11
|
+
}
|
|
12
|
+
fs.writeFileSync(filePath, content)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function configure(targetDir, answers) {
|
|
16
|
+
console.log(` [2/8] ✏️ 替换项目配置...`)
|
|
17
|
+
|
|
18
|
+
const { projectName, title, dbName } = answers
|
|
19
|
+
const jwtSecret = crypto.randomBytes(32).toString('hex')
|
|
20
|
+
const logoFull = title.length > 6 ? title.slice(0, 6) : title
|
|
21
|
+
const logoShort = title.charAt(0)
|
|
22
|
+
|
|
23
|
+
// server/package.json
|
|
24
|
+
replaceInFile(path.join(targetDir, 'server/package.json'), [
|
|
25
|
+
['"name": "server"', `"name": "${projectName}-server"`],
|
|
26
|
+
])
|
|
27
|
+
|
|
28
|
+
// client/package.json
|
|
29
|
+
replaceInFile(path.join(targetDir, 'client/package.json'), [
|
|
30
|
+
['"name": "client"', `"name": "${projectName}-client"`],
|
|
31
|
+
])
|
|
32
|
+
|
|
33
|
+
// index.html 标题
|
|
34
|
+
replaceInFile(path.join(targetDir, 'client/index.html'), [
|
|
35
|
+
[/<title>.*?<\/title>/, `<title>${title}</title>`],
|
|
36
|
+
])
|
|
37
|
+
|
|
38
|
+
// AdminLayout Logo
|
|
39
|
+
replaceInFile(path.join(targetDir, 'client/src/layout/AdminLayout.vue'), [
|
|
40
|
+
[/>Admin</, `>${logoFull}<`],
|
|
41
|
+
[/>A</, `>${logoShort}<`],
|
|
42
|
+
])
|
|
43
|
+
|
|
44
|
+
// server/.env
|
|
45
|
+
console.log(` [3/8] 🔑 生成 .env...`)
|
|
46
|
+
// 检测本地 PostgreSQL 用户(macOS brew 默认用系统用户名,无密码)
|
|
47
|
+
const os = require('os')
|
|
48
|
+
let pgUser = 'postgres', pgPassword = 'postgres'
|
|
49
|
+
try {
|
|
50
|
+
const { execSync } = require('child_process')
|
|
51
|
+
execSync(`psql -U ${os.userInfo().username} -d postgres -c "SELECT 1" 2>/dev/null`, { stdio: 'ignore' })
|
|
52
|
+
pgUser = os.userInfo().username
|
|
53
|
+
pgPassword = ''
|
|
54
|
+
} catch {}
|
|
55
|
+
|
|
56
|
+
const dbUrl = pgPassword
|
|
57
|
+
? `postgresql://${pgUser}:${pgPassword}@localhost:5432/${dbName}?schema=public`
|
|
58
|
+
: `postgresql://${pgUser}@localhost:5432/${dbName}?schema=public`
|
|
59
|
+
|
|
60
|
+
fs.writeFileSync(path.join(targetDir, 'server/.env'), [
|
|
61
|
+
`DATABASE_URL=${dbUrl}`,
|
|
62
|
+
`JWT_SECRET=${jwtSecret}`,
|
|
63
|
+
`PORT=3000`,
|
|
64
|
+
`NODE_ENV=development`,
|
|
65
|
+
`CORS_ORIGIN=*`,
|
|
66
|
+
].join('\n') + '\n')
|
|
67
|
+
|
|
68
|
+
// 根目录 .env(Docker 用)
|
|
69
|
+
fs.writeFileSync(path.join(targetDir, '.env'), [
|
|
70
|
+
`DB_USER=postgres`,
|
|
71
|
+
`DB_PASSWORD=postgres`,
|
|
72
|
+
`DB_NAME=${dbName}`,
|
|
73
|
+
`JWT_SECRET=${jwtSecret}`,
|
|
74
|
+
`PORT=3000`,
|
|
75
|
+
`NODE_ENV=production`,
|
|
76
|
+
`CORS_ORIGIN=http://localhost`,
|
|
77
|
+
].join('\n') + '\n')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { configure }
|
package/lib/database.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const { execSync } = require('child_process')
|
|
2
|
+
const chalk = require('chalk')
|
|
3
|
+
const prompts = require('prompts')
|
|
4
|
+
|
|
5
|
+
async function setupDatabase(targetDir, answers, env) {
|
|
6
|
+
const { dbName } = answers
|
|
7
|
+
const serverDir = `${targetDir}/server`
|
|
8
|
+
|
|
9
|
+
if (env.hasPg) {
|
|
10
|
+
console.log(` [5/8] 🗄️ 配置 PostgreSQL...`)
|
|
11
|
+
// 创建数据库(如果不存在)
|
|
12
|
+
try {
|
|
13
|
+
execSync(`createdb ${dbName} 2>/dev/null`, { stdio: 'ignore' })
|
|
14
|
+
console.log(chalk.gray(` 数据库 ${dbName} 已创建`))
|
|
15
|
+
} catch {
|
|
16
|
+
console.log(chalk.gray(` 数据库 ${dbName} 已存在`))
|
|
17
|
+
}
|
|
18
|
+
} else if (env.hasDocker) {
|
|
19
|
+
console.log(` [5/8] 🐳 启动 PostgreSQL(Docker)...`)
|
|
20
|
+
const containerName = `${answers.projectName}-db`
|
|
21
|
+
try {
|
|
22
|
+
// 停掉同名容器
|
|
23
|
+
execSync(`docker rm -f ${containerName} 2>/dev/null`, { stdio: 'ignore' })
|
|
24
|
+
execSync(`docker run -d --name ${containerName} -p 5432:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=${dbName} postgres:16-alpine`, { stdio: 'ignore' })
|
|
25
|
+
// 等待就绪
|
|
26
|
+
for (let i = 0; i < 20; i++) {
|
|
27
|
+
await new Promise(r => setTimeout(r, 500))
|
|
28
|
+
try { execSync(`docker exec ${containerName} pg_isready`, { stdio: 'ignore' }); break } catch {}
|
|
29
|
+
}
|
|
30
|
+
console.log(chalk.gray(` 容器 ${containerName} 已启动`))
|
|
31
|
+
} catch (e) {
|
|
32
|
+
throw new Error('Docker 启动 PostgreSQL 失败:' + e.message)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { setupDatabase }
|
package/lib/detect.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const { execSync } = require('child_process')
|
|
2
|
+
const chalk = require('chalk')
|
|
3
|
+
|
|
4
|
+
function commandExists(cmd) {
|
|
5
|
+
try { execSync(`which ${cmd}`, { stdio: 'ignore' }); return true } catch { return false }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function pgReady() {
|
|
9
|
+
try { const r = execSync('pg_isready 2>&1', { encoding: 'utf8' }); return r.includes('accepting') } catch { return false }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function dockerRunning() {
|
|
13
|
+
try { execSync('docker info', { stdio: 'ignore' }); return true } catch { return false }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function detect() {
|
|
17
|
+
const env = { hasPg: false, hasDocker: false }
|
|
18
|
+
|
|
19
|
+
// Node.js
|
|
20
|
+
const nodeVer = process.version
|
|
21
|
+
console.log(` ✅ Node.js ${nodeVer}`)
|
|
22
|
+
|
|
23
|
+
// PostgreSQL
|
|
24
|
+
if (commandExists('psql')) {
|
|
25
|
+
if (pgReady()) {
|
|
26
|
+
env.hasPg = true
|
|
27
|
+
console.log(` ✅ PostgreSQL 已运行`)
|
|
28
|
+
} else {
|
|
29
|
+
console.log(chalk.yellow(` ⚠️ PostgreSQL 已安装但未启动`))
|
|
30
|
+
// 尝试启动
|
|
31
|
+
try {
|
|
32
|
+
execSync('brew services start postgresql@16 2>/dev/null || brew services start postgresql 2>/dev/null', { stdio: 'ignore' })
|
|
33
|
+
// 等待启动
|
|
34
|
+
for (let i = 0; i < 10; i++) {
|
|
35
|
+
await new Promise(r => setTimeout(r, 500))
|
|
36
|
+
if (pgReady()) { env.hasPg = true; break }
|
|
37
|
+
}
|
|
38
|
+
if (env.hasPg) console.log(` ✅ PostgreSQL 已自动启动`)
|
|
39
|
+
else console.log(chalk.yellow(` ⚠️ PostgreSQL 启动失败`))
|
|
40
|
+
} catch {}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Docker
|
|
45
|
+
if (commandExists('docker')) {
|
|
46
|
+
if (dockerRunning()) {
|
|
47
|
+
env.hasDocker = true
|
|
48
|
+
console.log(` ✅ Docker 已运行`)
|
|
49
|
+
} else {
|
|
50
|
+
console.log(chalk.yellow(` ⚠️ Docker 已安装但未运行`))
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!env.hasPg && !env.hasDocker) {
|
|
55
|
+
console.log(chalk.red('\n❌ 需要 PostgreSQL 或 Docker(二选一)'))
|
|
56
|
+
console.log(` 安装 PostgreSQL: ${chalk.cyan('brew install postgresql@16')}`)
|
|
57
|
+
console.log(` 安装 Docker: ${chalk.cyan('https://docker.com')}`)
|
|
58
|
+
process.exit(1)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return env
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = { detect }
|
package/lib/install.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const { execSync } = require('child_process')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const chalk = require('chalk')
|
|
5
|
+
|
|
6
|
+
async function install(targetDir) {
|
|
7
|
+
const serverDir = path.join(targetDir, 'server')
|
|
8
|
+
const clientDir = path.join(targetDir, 'client')
|
|
9
|
+
|
|
10
|
+
// 删除模板的旧迁移文件
|
|
11
|
+
const migrationsDir = path.join(serverDir, 'prisma/migrations')
|
|
12
|
+
if (fs.existsSync(migrationsDir)) {
|
|
13
|
+
fs.rmSync(migrationsDir, { recursive: true, force: true })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// 安装依赖
|
|
17
|
+
console.log(` [6/8] 📦 安装依赖...`)
|
|
18
|
+
execSync('npm install', { cwd: serverDir, stdio: 'ignore' })
|
|
19
|
+
console.log(chalk.gray(' server 依赖已安装'))
|
|
20
|
+
execSync('npm install', { cwd: clientDir, stdio: 'ignore' })
|
|
21
|
+
console.log(chalk.gray(' client 依赖已安装'))
|
|
22
|
+
|
|
23
|
+
// 数据库迁移 + 种子
|
|
24
|
+
console.log(` [7/8] 🗄️ 初始化数据库...`)
|
|
25
|
+
try {
|
|
26
|
+
execSync('npx prisma migrate dev --name init', { cwd: serverDir, stdio: 'ignore' })
|
|
27
|
+
execSync('npx prisma db seed', { cwd: serverDir, stdio: 'ignore' })
|
|
28
|
+
console.log(chalk.gray(' 数据库迁移 + 种子数据完成'))
|
|
29
|
+
} catch {
|
|
30
|
+
console.log(chalk.yellow(' ⚠️ 数据库初始化失败,请手动执行:'))
|
|
31
|
+
console.log(chalk.gray(' cd server && npx prisma migrate dev && npx prisma db seed'))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 清理 git 历史,重新 init
|
|
35
|
+
console.log(` [8/8] 🧹 初始化 Git...`)
|
|
36
|
+
fs.rmSync(path.join(targetDir, '.git'), { recursive: true, force: true })
|
|
37
|
+
execSync('git init && git add -A && git commit -m "init: 项目初始化"', { cwd: targetDir, stdio: 'ignore' })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { install }
|
package/lib/prompts.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const prompts = require('prompts')
|
|
2
|
+
|
|
3
|
+
async function runPrompts(projectName) {
|
|
4
|
+
const dbName = projectName.replace(/-/g, '_')
|
|
5
|
+
|
|
6
|
+
const answers = await prompts([
|
|
7
|
+
{ type: 'text', name: 'title', message: '📄 页面标题', initial: projectName },
|
|
8
|
+
{ type: 'text', name: 'dbName', message: '🗄️ 数据库名', initial: dbName },
|
|
9
|
+
{ type: 'confirm', name: 'mobile', message: '📱 需要移动端 H5?', initial: true },
|
|
10
|
+
{ type: 'confirm', name: 'keepExample', message: '📦 保留示例模块?', initial: false },
|
|
11
|
+
], { onCancel: () => { process.exit(0) } })
|
|
12
|
+
|
|
13
|
+
answers.projectName = projectName
|
|
14
|
+
return answers
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = { runPrompts }
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-codeforge-app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "一行命令创建全栈项目(NestJS + Vue3 + PostgreSQL)",
|
|
5
|
+
"bin": {
|
|
6
|
+
"create-codeforge-app": "./index.js"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"scaffold",
|
|
10
|
+
"fullstack",
|
|
11
|
+
"nestjs",
|
|
12
|
+
"vue3",
|
|
13
|
+
"prisma",
|
|
14
|
+
"postgresql"
|
|
15
|
+
],
|
|
16
|
+
"author": "codeforge-arch",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"chalk": "^4.1.2",
|
|
20
|
+
"prompts": "^2.4.2"
|
|
21
|
+
}
|
|
22
|
+
}
|