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 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 }
@@ -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 }
@@ -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
+ }