@basicbenframework/core 0.1.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/.github/workflows/publish.yml +35 -0
- package/README.md +588 -0
- package/bin/cli.js +8 -0
- package/create-basicben-app/index.js +205 -0
- package/create-basicben-app/package.json +30 -0
- package/create-basicben-app/template/.env.example +24 -0
- package/create-basicben-app/template/README.md +59 -0
- package/create-basicben-app/template/basicben.config.js +33 -0
- package/create-basicben-app/template/index.html +54 -0
- package/create-basicben-app/template/migrations/001_create_users.js +15 -0
- package/create-basicben-app/template/migrations/002_create_posts.js +18 -0
- package/create-basicben-app/template/public/.gitkeep +0 -0
- package/create-basicben-app/template/seeds/01_users.js +29 -0
- package/create-basicben-app/template/seeds/02_posts.js +43 -0
- package/create-basicben-app/template/src/client/components/Alert.jsx +11 -0
- package/create-basicben-app/template/src/client/components/Avatar.jsx +11 -0
- package/create-basicben-app/template/src/client/components/BackLink.jsx +10 -0
- package/create-basicben-app/template/src/client/components/Button.jsx +19 -0
- package/create-basicben-app/template/src/client/components/Card.jsx +10 -0
- package/create-basicben-app/template/src/client/components/Empty.jsx +6 -0
- package/create-basicben-app/template/src/client/components/Input.jsx +12 -0
- package/create-basicben-app/template/src/client/components/Loading.jsx +6 -0
- package/create-basicben-app/template/src/client/components/Logo.jsx +40 -0
- package/create-basicben-app/template/src/client/components/Nav/DarkModeToggle.jsx +23 -0
- package/create-basicben-app/template/src/client/components/Nav/DesktopNav.jsx +32 -0
- package/create-basicben-app/template/src/client/components/Nav/MobileNav.jsx +107 -0
- package/create-basicben-app/template/src/client/components/NavLink.jsx +10 -0
- package/create-basicben-app/template/src/client/components/PageHeader.jsx +8 -0
- package/create-basicben-app/template/src/client/components/PostCard.jsx +19 -0
- package/create-basicben-app/template/src/client/components/Textarea.jsx +12 -0
- package/create-basicben-app/template/src/client/components/ThemeContext.jsx +5 -0
- package/create-basicben-app/template/src/client/contexts/ToastContext.jsx +94 -0
- package/create-basicben-app/template/src/client/layouts/AppLayout.jsx +60 -0
- package/create-basicben-app/template/src/client/layouts/AuthLayout.jsx +33 -0
- package/create-basicben-app/template/src/client/layouts/DocsLayout.jsx +60 -0
- package/create-basicben-app/template/src/client/layouts/RootLayout.jsx +25 -0
- package/create-basicben-app/template/src/client/pages/Auth.jsx +55 -0
- package/create-basicben-app/template/src/client/pages/Authentication.jsx +236 -0
- package/create-basicben-app/template/src/client/pages/Database.jsx +426 -0
- package/create-basicben-app/template/src/client/pages/Feed.jsx +34 -0
- package/create-basicben-app/template/src/client/pages/FeedPost.jsx +37 -0
- package/create-basicben-app/template/src/client/pages/GettingStarted.jsx +136 -0
- package/create-basicben-app/template/src/client/pages/Home.jsx +206 -0
- package/create-basicben-app/template/src/client/pages/PostForm.jsx +69 -0
- package/create-basicben-app/template/src/client/pages/Posts.jsx +59 -0
- package/create-basicben-app/template/src/client/pages/Profile.jsx +68 -0
- package/create-basicben-app/template/src/client/pages/Routing.jsx +207 -0
- package/create-basicben-app/template/src/client/pages/Testing.jsx +251 -0
- package/create-basicben-app/template/src/client/pages/Validation.jsx +210 -0
- package/create-basicben-app/template/src/controllers/AuthController.js +81 -0
- package/create-basicben-app/template/src/controllers/HomeController.js +17 -0
- package/create-basicben-app/template/src/controllers/PostController.js +86 -0
- package/create-basicben-app/template/src/controllers/ProfileController.js +66 -0
- package/create-basicben-app/template/src/helpers/api.js +24 -0
- package/create-basicben-app/template/src/main.jsx +9 -0
- package/create-basicben-app/template/src/middleware/auth.js +16 -0
- package/create-basicben-app/template/src/models/Post.js +63 -0
- package/create-basicben-app/template/src/models/User.js +42 -0
- package/create-basicben-app/template/src/routes/App.jsx +38 -0
- package/create-basicben-app/template/src/routes/api/auth.js +7 -0
- package/create-basicben-app/template/src/routes/api/posts.js +15 -0
- package/create-basicben-app/template/src/routes/api/profile.js +8 -0
- package/create-basicben-app/template/src/server/index.js +16 -0
- package/create-basicben-app/template/vite.config.js +18 -0
- package/database.sqlite +0 -0
- package/my-test-app/.env.example +24 -0
- package/my-test-app/README.md +59 -0
- package/my-test-app/basicben.config.js +33 -0
- package/my-test-app/database.sqlite-shm +0 -0
- package/my-test-app/database.sqlite-wal +0 -0
- package/my-test-app/index.html +54 -0
- package/my-test-app/migrations/001_create_users.js +15 -0
- package/my-test-app/migrations/002_create_posts.js +18 -0
- package/my-test-app/package-lock.json +2160 -0
- package/my-test-app/package.json +29 -0
- package/my-test-app/public/.gitkeep +0 -0
- package/my-test-app/seeds/01_users.js +29 -0
- package/my-test-app/seeds/02_posts.js +43 -0
- package/my-test-app/src/client/components/Alert.jsx +11 -0
- package/my-test-app/src/client/components/Avatar.jsx +11 -0
- package/my-test-app/src/client/components/BackLink.jsx +10 -0
- package/my-test-app/src/client/components/Button.jsx +19 -0
- package/my-test-app/src/client/components/Card.jsx +10 -0
- package/my-test-app/src/client/components/Empty.jsx +6 -0
- package/my-test-app/src/client/components/Input.jsx +12 -0
- package/my-test-app/src/client/components/Loading.jsx +6 -0
- package/my-test-app/src/client/components/Logo.jsx +40 -0
- package/my-test-app/src/client/components/Nav/DarkModeToggle.jsx +23 -0
- package/my-test-app/src/client/components/Nav/DesktopNav.jsx +32 -0
- package/my-test-app/src/client/components/Nav/MobileNav.jsx +107 -0
- package/my-test-app/src/client/components/NavLink.jsx +10 -0
- package/my-test-app/src/client/components/PageHeader.jsx +8 -0
- package/my-test-app/src/client/components/PostCard.jsx +19 -0
- package/my-test-app/src/client/components/Textarea.jsx +12 -0
- package/my-test-app/src/client/components/ThemeContext.jsx +5 -0
- package/my-test-app/src/client/contexts/AppContext.jsx +13 -0
- package/my-test-app/src/client/contexts/ToastContext.jsx +94 -0
- package/my-test-app/src/client/layouts/AppLayout.jsx +60 -0
- package/my-test-app/src/client/layouts/AuthLayout.jsx +33 -0
- package/my-test-app/src/client/layouts/DocsLayout.jsx +60 -0
- package/my-test-app/src/client/layouts/RootLayout.jsx +25 -0
- package/my-test-app/src/client/pages/Auth.jsx +55 -0
- package/my-test-app/src/client/pages/Authentication.jsx +236 -0
- package/my-test-app/src/client/pages/Database.jsx +426 -0
- package/my-test-app/src/client/pages/Feed.jsx +34 -0
- package/my-test-app/src/client/pages/FeedPost.jsx +37 -0
- package/my-test-app/src/client/pages/GettingStarted.jsx +136 -0
- package/my-test-app/src/client/pages/Home.jsx +206 -0
- package/my-test-app/src/client/pages/PostForm.jsx +69 -0
- package/my-test-app/src/client/pages/Posts.jsx +59 -0
- package/my-test-app/src/client/pages/Profile.jsx +68 -0
- package/my-test-app/src/client/pages/Routing.jsx +207 -0
- package/my-test-app/src/client/pages/Testing.jsx +251 -0
- package/my-test-app/src/client/pages/Validation.jsx +210 -0
- package/my-test-app/src/controllers/AuthController.js +81 -0
- package/my-test-app/src/controllers/HomeController.js +17 -0
- package/my-test-app/src/controllers/PostController.js +86 -0
- package/my-test-app/src/controllers/ProfileController.js +66 -0
- package/my-test-app/src/helpers/api.js +24 -0
- package/my-test-app/src/main.jsx +9 -0
- package/my-test-app/src/middleware/auth.js +16 -0
- package/my-test-app/src/models/Post.js +63 -0
- package/my-test-app/src/models/User.js +42 -0
- package/my-test-app/src/routes/App.jsx +38 -0
- package/my-test-app/src/routes/api/auth.js +7 -0
- package/my-test-app/src/routes/api/posts.js +15 -0
- package/my-test-app/src/routes/api/profile.js +8 -0
- package/my-test-app/src/server/index.js +16 -0
- package/my-test-app/vite.config.js +18 -0
- package/package.json +61 -0
- package/scripts/test-app.sh +59 -0
- package/src/auth/jwt.js +195 -0
- package/src/auth/password.js +132 -0
- package/src/cli/colors.js +31 -0
- package/src/cli/dispatcher.js +168 -0
- package/src/cli/parser.js +91 -0
- package/src/client/context.js +4 -0
- package/src/client/hooks.js +50 -0
- package/src/client/index.js +3 -0
- package/src/client/router.js +184 -0
- package/src/commands/build.js +155 -0
- package/src/commands/dev.js +206 -0
- package/src/commands/help.js +84 -0
- package/src/commands/make-controller.js +36 -0
- package/src/commands/make-middleware.js +44 -0
- package/src/commands/make-migration.js +51 -0
- package/src/commands/make-model.js +38 -0
- package/src/commands/make-route.js +36 -0
- package/src/commands/make-seed.js +38 -0
- package/src/commands/migrate-fresh.js +32 -0
- package/src/commands/migrate-rollback.js +30 -0
- package/src/commands/migrate-status.js +41 -0
- package/src/commands/migrate.js +30 -0
- package/src/commands/seed.js +47 -0
- package/src/commands/start.js +69 -0
- package/src/commands/test.js +46 -0
- package/src/db/Grammar.js +125 -0
- package/src/db/QueryBuilder.js +476 -0
- package/src/db/adapters/neon.js +170 -0
- package/src/db/adapters/planetscale.js +146 -0
- package/src/db/adapters/postgres.js +166 -0
- package/src/db/adapters/sqlite.js +125 -0
- package/src/db/adapters/turso.js +165 -0
- package/src/db/index.js +156 -0
- package/src/db/migrator.js +250 -0
- package/src/db/seeder.js +124 -0
- package/src/index.js +12 -0
- package/src/scaffolding/index.js +152 -0
- package/src/server/body-parser.js +159 -0
- package/src/server/cors.js +63 -0
- package/src/server/default-entry.js +13 -0
- package/src/server/http.js +221 -0
- package/src/server/index.js +168 -0
- package/src/server/loader.js +128 -0
- package/src/server/router.js +281 -0
- package/src/server/static.js +139 -0
- package/src/validation/index.js +436 -0
- package/src/vite/config.js +49 -0
- package/stubs/controller.stub +48 -0
- package/stubs/middleware-auth.stub +29 -0
- package/stubs/middleware.stub +9 -0
- package/stubs/migration.stub +17 -0
- package/stubs/model.stub +77 -0
- package/stubs/route.stub +13 -0
- package/stubs/seed.stub +16 -0
- package/stubs/vite.config.stub +18 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* build command
|
|
3
|
+
* Builds the client (Vite) and prepares server for production
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawn } from 'node:child_process'
|
|
7
|
+
import { existsSync, mkdirSync, copyFileSync, readdirSync } from 'node:fs'
|
|
8
|
+
import { resolve, join } from 'node:path'
|
|
9
|
+
import { bold, cyan, green, yellow, dim, red } from '../cli/colors.js'
|
|
10
|
+
|
|
11
|
+
export async function run(args, flags) {
|
|
12
|
+
const cwd = process.cwd()
|
|
13
|
+
const staticOnly = flags.static || false
|
|
14
|
+
|
|
15
|
+
console.log(`\n${bold('BasicBen')} ${dim('build')}${staticOnly ? dim(' --static') : ''}\n`)
|
|
16
|
+
|
|
17
|
+
// Build client with Vite
|
|
18
|
+
console.log(`${cyan('Building client...')}\n`)
|
|
19
|
+
|
|
20
|
+
const outDir = staticOnly ? 'dist' : 'dist/client'
|
|
21
|
+
const viteBuild = await runViteBuild(cwd, outDir)
|
|
22
|
+
if (!viteBuild.success) {
|
|
23
|
+
console.error(`\n${red('Client build failed')}\n`)
|
|
24
|
+
process.exit(1)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log(`\n${green('✓')} Client built to ${dim(outDir)}\n`)
|
|
28
|
+
|
|
29
|
+
// Static-only build stops here
|
|
30
|
+
if (staticOnly) {
|
|
31
|
+
console.log(`${green('Static build complete!')}\n`)
|
|
32
|
+
console.log(`Deploy the ${cyan('dist/')} folder to any static host (CF Pages, Netlify, etc.)\n`)
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Copy server files to dist
|
|
37
|
+
console.log(`${cyan('Preparing server...')}\n`)
|
|
38
|
+
|
|
39
|
+
await prepareServer(cwd)
|
|
40
|
+
|
|
41
|
+
console.log(`${green('✓')} Server prepared in ${dim('dist/server')}\n`)
|
|
42
|
+
|
|
43
|
+
// Summary
|
|
44
|
+
console.log(`${green('Build complete!')}\n`)
|
|
45
|
+
console.log(`Run ${cyan('basicben start')} to start the production server.\n`)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Run Vite build
|
|
50
|
+
*/
|
|
51
|
+
function runViteBuild(cwd, outDir = 'dist/client') {
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
const proc = spawn('npx', ['vite', 'build', '--outDir', outDir], {
|
|
54
|
+
cwd,
|
|
55
|
+
stdio: 'inherit',
|
|
56
|
+
env: {
|
|
57
|
+
...process.env,
|
|
58
|
+
NODE_ENV: 'production'
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
proc.on('exit', (code) => {
|
|
63
|
+
resolve({ success: code === 0 })
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
proc.on('error', () => {
|
|
67
|
+
resolve({ success: false })
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Prepare server files for production
|
|
74
|
+
*/
|
|
75
|
+
async function prepareServer(cwd) {
|
|
76
|
+
const distServer = resolve(cwd, 'dist/server')
|
|
77
|
+
|
|
78
|
+
// Create dist/server directory
|
|
79
|
+
if (!existsSync(distServer)) {
|
|
80
|
+
mkdirSync(distServer, { recursive: true })
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Copy server source files
|
|
84
|
+
const serverDirs = ['src/server', 'src/routes', 'src/controllers', 'src/models', 'src/middleware']
|
|
85
|
+
|
|
86
|
+
for (const dir of serverDirs) {
|
|
87
|
+
const srcDir = resolve(cwd, dir)
|
|
88
|
+
if (existsSync(srcDir)) {
|
|
89
|
+
const destDir = resolve(distServer, dir.replace('src/', ''))
|
|
90
|
+
copyDir(srcDir, destDir)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Copy package.json for production dependencies
|
|
95
|
+
const pkgSrc = resolve(cwd, 'package.json')
|
|
96
|
+
if (existsSync(pkgSrc)) {
|
|
97
|
+
copyFileSync(pkgSrc, resolve(distServer, 'package.json'))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Create production server entry
|
|
101
|
+
const hasCustomServer = existsSync(resolve(cwd, 'src/server/index.js'))
|
|
102
|
+
|
|
103
|
+
const serverEntry = hasCustomServer
|
|
104
|
+
? `
|
|
105
|
+
import { createServer } from './server/index.js'
|
|
106
|
+
|
|
107
|
+
const app = await createServer({
|
|
108
|
+
static: { dir: '../client' }
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const port = process.env.PORT || 3000
|
|
112
|
+
|
|
113
|
+
app.listen(port, () => {
|
|
114
|
+
console.log(\`Server running at http://localhost:\${port}\`)
|
|
115
|
+
})
|
|
116
|
+
`.trim()
|
|
117
|
+
: `
|
|
118
|
+
import { createServer } from 'basicben/server'
|
|
119
|
+
|
|
120
|
+
const app = await createServer({
|
|
121
|
+
static: { dir: '../client' }
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const port = process.env.PORT || 3000
|
|
125
|
+
|
|
126
|
+
app.listen(port, () => {
|
|
127
|
+
console.log(\`Server running at http://localhost:\${port}\`)
|
|
128
|
+
})
|
|
129
|
+
`.trim()
|
|
130
|
+
|
|
131
|
+
const { writeFileSync } = await import('node:fs')
|
|
132
|
+
writeFileSync(resolve(distServer, 'index.js'), serverEntry)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Recursively copy directory
|
|
137
|
+
*/
|
|
138
|
+
function copyDir(src, dest) {
|
|
139
|
+
if (!existsSync(dest)) {
|
|
140
|
+
mkdirSync(dest, { recursive: true })
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const entries = readdirSync(src, { withFileTypes: true })
|
|
144
|
+
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
const srcPath = join(src, entry.name)
|
|
147
|
+
const destPath = join(dest, entry.name)
|
|
148
|
+
|
|
149
|
+
if (entry.isDirectory()) {
|
|
150
|
+
copyDir(srcPath, destPath)
|
|
151
|
+
} else if (entry.name.endsWith('.js') && !entry.name.endsWith('.test.js')) {
|
|
152
|
+
copyFileSync(srcPath, destPath)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dev command
|
|
3
|
+
* Starts Vite dev server (port 3000) and Node API server (port 3001)
|
|
4
|
+
* Vite proxies /api/* requests to Node
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { spawn } from 'node:child_process'
|
|
8
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
9
|
+
import { resolve, dirname } from 'node:path'
|
|
10
|
+
import { fileURLToPath } from 'node:url'
|
|
11
|
+
import { bold, cyan, green, yellow, dim, red } from '../cli/colors.js'
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Load .env file into process.env
|
|
17
|
+
*/
|
|
18
|
+
function loadEnv(cwd) {
|
|
19
|
+
const envPath = resolve(cwd, '.env')
|
|
20
|
+
if (!existsSync(envPath)) return
|
|
21
|
+
|
|
22
|
+
const content = readFileSync(envPath, 'utf-8')
|
|
23
|
+
for (const line of content.split('\n')) {
|
|
24
|
+
const trimmed = line.trim()
|
|
25
|
+
if (!trimmed || trimmed.startsWith('#')) continue
|
|
26
|
+
const [key, ...rest] = trimmed.split('=')
|
|
27
|
+
if (key && rest.length) {
|
|
28
|
+
let value = rest.join('=').trim()
|
|
29
|
+
// Strip inline comments (but not if inside quotes)
|
|
30
|
+
if (!value.startsWith('"') && !value.startsWith("'")) {
|
|
31
|
+
const commentIndex = value.indexOf('#')
|
|
32
|
+
if (commentIndex !== -1) {
|
|
33
|
+
value = value.substring(0, commentIndex).trim()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
process.env[key.trim()] = value
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function run(args, flags) {
|
|
42
|
+
const cwd = process.cwd()
|
|
43
|
+
|
|
44
|
+
// Load .env file
|
|
45
|
+
loadEnv(cwd)
|
|
46
|
+
|
|
47
|
+
console.log(`\n${bold('BasicBen')} ${dim('dev')}\n`)
|
|
48
|
+
|
|
49
|
+
// Find server entry (uses framework default if no custom server)
|
|
50
|
+
const serverEntry = findServerEntry(cwd)
|
|
51
|
+
|
|
52
|
+
const viteConfig = findViteConfig(cwd)
|
|
53
|
+
|
|
54
|
+
// Start Node server with --watch
|
|
55
|
+
const nodePort = flags.port || process.env.PORT || 3001
|
|
56
|
+
const nodeProcess = startNodeServer(serverEntry, nodePort, cwd)
|
|
57
|
+
|
|
58
|
+
// Start Vite dev server
|
|
59
|
+
const vitePort = flags.vitePort || process.env.VITE_PORT || 3000
|
|
60
|
+
const viteProcess = startViteServer(vitePort, nodePort, viteConfig, cwd)
|
|
61
|
+
|
|
62
|
+
// Handle process cleanup
|
|
63
|
+
const cleanup = () => {
|
|
64
|
+
console.log(`\n${dim('Shutting down...')}\n`)
|
|
65
|
+
nodeProcess.kill()
|
|
66
|
+
viteProcess.kill()
|
|
67
|
+
process.exit(0)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
process.on('SIGINT', cleanup)
|
|
71
|
+
process.on('SIGTERM', cleanup)
|
|
72
|
+
|
|
73
|
+
// Wait for both processes
|
|
74
|
+
nodeProcess.on('exit', (code) => {
|
|
75
|
+
if (code !== 0 && code !== null) {
|
|
76
|
+
console.error(`${red('Node server exited with code')} ${code}`)
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
viteProcess.on('exit', (code) => {
|
|
81
|
+
if (code !== 0 && code !== null) {
|
|
82
|
+
console.error(`${red('Vite exited with code')} ${code}`)
|
|
83
|
+
}
|
|
84
|
+
cleanup()
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Find server entry point
|
|
90
|
+
* Returns default framework entry if no custom server exists
|
|
91
|
+
*/
|
|
92
|
+
function findServerEntry(cwd) {
|
|
93
|
+
const candidates = [
|
|
94
|
+
'src/server/index.js',
|
|
95
|
+
'src/server.js',
|
|
96
|
+
'src/index.js',
|
|
97
|
+
'server/index.js',
|
|
98
|
+
'server.js'
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
for (const candidate of candidates) {
|
|
102
|
+
const fullPath = resolve(cwd, candidate)
|
|
103
|
+
if (existsSync(fullPath)) {
|
|
104
|
+
return fullPath
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// No custom server found, use framework default
|
|
109
|
+
return resolve(__dirname, '../server/default-entry.js')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Find Vite config
|
|
114
|
+
*/
|
|
115
|
+
function findViteConfig(cwd) {
|
|
116
|
+
const candidates = [
|
|
117
|
+
'vite.config.js',
|
|
118
|
+
'vite.config.ts',
|
|
119
|
+
'vite.config.mjs'
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
for (const candidate of candidates) {
|
|
123
|
+
const fullPath = resolve(cwd, candidate)
|
|
124
|
+
if (existsSync(fullPath)) {
|
|
125
|
+
return fullPath
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Start Node server with --watch flag
|
|
134
|
+
*/
|
|
135
|
+
function startNodeServer(entry, port, cwd) {
|
|
136
|
+
console.log(`${cyan('API')} ${dim('→')} http://localhost:${port}`)
|
|
137
|
+
|
|
138
|
+
const nodeArgs = [
|
|
139
|
+
'--watch',
|
|
140
|
+
'--env-file=.env'
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
// Add .env file if it exists
|
|
144
|
+
if (!existsSync(resolve(cwd, '.env'))) {
|
|
145
|
+
nodeArgs.splice(1, 1) // Remove --env-file if no .env
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
nodeArgs.push(entry)
|
|
149
|
+
|
|
150
|
+
const proc = spawn('node', nodeArgs, {
|
|
151
|
+
cwd,
|
|
152
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
153
|
+
env: {
|
|
154
|
+
...process.env,
|
|
155
|
+
PORT: port,
|
|
156
|
+
NODE_ENV: 'development'
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// Prefix output with [API]
|
|
161
|
+
proc.stdout?.on('data', (data) => {
|
|
162
|
+
const lines = data.toString().trim().split('\n')
|
|
163
|
+
for (const line of lines) {
|
|
164
|
+
if (line) console.log(`${dim('[API]')} ${line}`)
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
proc.stderr?.on('data', (data) => {
|
|
169
|
+
const lines = data.toString().trim().split('\n')
|
|
170
|
+
for (const line of lines) {
|
|
171
|
+
if (line) console.error(`${dim('[API]')} ${line}`)
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
return proc
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Start Vite dev server with proxy config
|
|
180
|
+
*/
|
|
181
|
+
function startViteServer(port, apiPort, configPath, cwd) {
|
|
182
|
+
console.log(`${green('Vite')} ${dim('→')} http://localhost:${port}`)
|
|
183
|
+
console.log()
|
|
184
|
+
|
|
185
|
+
const viteArgs = [
|
|
186
|
+
'vite',
|
|
187
|
+
'--port', String(port),
|
|
188
|
+
'--strictPort'
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
if (configPath) {
|
|
192
|
+
viteArgs.push('--config', configPath)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const proc = spawn('npx', viteArgs, {
|
|
196
|
+
cwd,
|
|
197
|
+
stdio: 'inherit',
|
|
198
|
+
env: {
|
|
199
|
+
...process.env,
|
|
200
|
+
PORT: apiPort,
|
|
201
|
+
NODE_ENV: 'development'
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
return proc
|
|
206
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Help command. Shows available commands and usage.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { bold, cyan, dim, yellow } from '../cli/colors.js'
|
|
6
|
+
import { commandMeta } from '../cli/dispatcher.js'
|
|
7
|
+
|
|
8
|
+
export async function run(args, flags) {
|
|
9
|
+
const specificCommand = args[0]
|
|
10
|
+
|
|
11
|
+
if (specificCommand) {
|
|
12
|
+
showCommandHelp(specificCommand)
|
|
13
|
+
} else {
|
|
14
|
+
showGeneralHelp()
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function showGeneralHelp() {
|
|
19
|
+
console.log(`
|
|
20
|
+
${bold('BasicBen')} — A full-stack framework for React
|
|
21
|
+
|
|
22
|
+
${bold('Usage:')}
|
|
23
|
+
basicben <command> [options]
|
|
24
|
+
|
|
25
|
+
${bold('Development')}
|
|
26
|
+
${cyan('dev')} Start Vite + Node dev server
|
|
27
|
+
${cyan('build')} Build for production
|
|
28
|
+
${cyan('start')} Start production server
|
|
29
|
+
${cyan('test')} Run tests with Vitest
|
|
30
|
+
|
|
31
|
+
${bold('Scaffolding')}
|
|
32
|
+
${cyan('make:controller')} Generate a controller
|
|
33
|
+
${cyan('make:model')} Generate a model
|
|
34
|
+
${cyan('make:route')} Generate a route file
|
|
35
|
+
${cyan('make:migration')} Generate a migration file
|
|
36
|
+
${cyan('make:middleware')} Generate middleware
|
|
37
|
+
|
|
38
|
+
${bold('Database')}
|
|
39
|
+
${cyan('migrate')} Run pending migrations
|
|
40
|
+
${cyan('migrate:rollback')} Roll back last batch
|
|
41
|
+
${cyan('migrate:fresh')} Drop all and re-run
|
|
42
|
+
${cyan('migrate:status')} Show migration status
|
|
43
|
+
|
|
44
|
+
${bold('Options')}
|
|
45
|
+
${cyan('--help, -h')} Show help
|
|
46
|
+
${cyan('--version, -v')} Show version
|
|
47
|
+
|
|
48
|
+
Run ${cyan('basicben help <command>')} for details on a specific command.
|
|
49
|
+
`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function showCommandHelp(command) {
|
|
53
|
+
const meta = commandMeta[command]
|
|
54
|
+
|
|
55
|
+
if (!meta) {
|
|
56
|
+
console.log(`\n${yellow('Unknown command:')} ${command}`)
|
|
57
|
+
console.log(`Run ${cyan('basicben help')} to see available commands.\n`)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log(`
|
|
62
|
+
${bold('Command:')} ${cyan(command)}
|
|
63
|
+
|
|
64
|
+
${meta.description}
|
|
65
|
+
|
|
66
|
+
${bold('Usage:')}
|
|
67
|
+
${meta.usage}
|
|
68
|
+
`)
|
|
69
|
+
|
|
70
|
+
// Add command-specific options if available
|
|
71
|
+
if (meta.options) {
|
|
72
|
+
console.log(`${bold('Options:')}`)
|
|
73
|
+
for (const [flag, desc] of Object.entries(meta.options)) {
|
|
74
|
+
console.log(` ${cyan(flag)} ${desc}`)
|
|
75
|
+
}
|
|
76
|
+
console.log()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Add example if available
|
|
80
|
+
if (meta.example) {
|
|
81
|
+
console.log(`${bold('Example:')}`)
|
|
82
|
+
console.log(` ${dim('$')} ${meta.example}\n`)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* make:controller command
|
|
3
|
+
* Generates a controller file in src/controllers/
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { generate, transformName } from '../scaffolding/index.js'
|
|
7
|
+
import { green, red, cyan, dim } from '../cli/colors.js'
|
|
8
|
+
|
|
9
|
+
export async function run(args, flags) {
|
|
10
|
+
const name = args[0]
|
|
11
|
+
|
|
12
|
+
if (!name) {
|
|
13
|
+
console.error(`\n${red('Error:')} Controller name is required`)
|
|
14
|
+
console.error(`\nUsage: ${cyan('basicben make:controller <Name>')}\n`)
|
|
15
|
+
process.exit(1)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const names = transformName(name)
|
|
19
|
+
const fileName = `${names.pascal}Controller.js`
|
|
20
|
+
const targetPath = `src/controllers/${fileName}`
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const fullPath = generate('controller', targetPath, {
|
|
24
|
+
Name: names.pascal,
|
|
25
|
+
name: names.camel,
|
|
26
|
+
lower: names.lower,
|
|
27
|
+
pluralLower: names.pluralLower
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
console.log(`\n${green('Created:')} ${targetPath}`)
|
|
31
|
+
console.log(`${dim('Controller:')} ${names.pascal}Controller\n`)
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.error(`\n${red('Error:')} ${err.message}\n`)
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* make:middleware command
|
|
3
|
+
* Generates a middleware file in src/middleware/
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { generate, transformName } from '../scaffolding/index.js'
|
|
7
|
+
import { green, red, cyan, dim } from '../cli/colors.js'
|
|
8
|
+
|
|
9
|
+
export async function run(args, flags) {
|
|
10
|
+
const name = args[0]
|
|
11
|
+
|
|
12
|
+
if (!name) {
|
|
13
|
+
console.error(`\n${red('Error:')} Middleware name is required`)
|
|
14
|
+
console.error(`\nUsage: ${cyan('basicben make:middleware <name>')}\n`)
|
|
15
|
+
process.exit(1)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const names = transformName(name)
|
|
19
|
+
const fileName = `${names.lower}.js`
|
|
20
|
+
const targetPath = `src/middleware/${fileName}`
|
|
21
|
+
|
|
22
|
+
// Use auth stub for 'auth' middleware, otherwise use generic stub
|
|
23
|
+
const stubName = names.lower === 'auth' ? 'middleware-auth' : 'middleware'
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const fullPath = generate(stubName, targetPath, {
|
|
27
|
+
Name: names.pascal,
|
|
28
|
+
name: names.camel,
|
|
29
|
+
lower: names.lower
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
console.log(`\n${green('Created:')} ${targetPath}`)
|
|
33
|
+
|
|
34
|
+
if (names.lower === 'auth') {
|
|
35
|
+
console.log(`${dim('JWT auth middleware generated')}`)
|
|
36
|
+
console.log(`${dim('Requires APP_KEY in .env')}\n`)
|
|
37
|
+
} else {
|
|
38
|
+
console.log()
|
|
39
|
+
}
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error(`\n${red('Error:')} ${err.message}\n`)
|
|
42
|
+
process.exit(1)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* make:migration command
|
|
3
|
+
* Generates a migration file in migrations/
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { generate, transformName, timestamp } from '../scaffolding/index.js'
|
|
7
|
+
import { green, red, cyan, dim } from '../cli/colors.js'
|
|
8
|
+
|
|
9
|
+
export async function run(args, flags) {
|
|
10
|
+
const name = args[0]
|
|
11
|
+
|
|
12
|
+
if (!name) {
|
|
13
|
+
console.error(`\n${red('Error:')} Migration name is required`)
|
|
14
|
+
console.error(`\nUsage: ${cyan('basicben make:migration <name>')}\n`)
|
|
15
|
+
console.error(`Examples:`)
|
|
16
|
+
console.error(` ${dim('basicben make:migration create_users')}`)
|
|
17
|
+
console.error(` ${dim('basicben make:migration add_email_to_users')}\n`)
|
|
18
|
+
process.exit(1)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const ts = timestamp()
|
|
22
|
+
const names = transformName(name)
|
|
23
|
+
|
|
24
|
+
// Extract table name from migration name
|
|
25
|
+
// create_users -> users
|
|
26
|
+
// add_email_to_users -> users
|
|
27
|
+
let tableName = names.snake
|
|
28
|
+
if (tableName.startsWith('create_')) {
|
|
29
|
+
tableName = tableName.replace('create_', '')
|
|
30
|
+
} else if (tableName.includes('_to_')) {
|
|
31
|
+
tableName = tableName.split('_to_').pop()
|
|
32
|
+
} else if (tableName.includes('_from_')) {
|
|
33
|
+
tableName = tableName.split('_from_').pop()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const fileName = `${ts}_${names.snake}.js`
|
|
37
|
+
const targetPath = `migrations/${fileName}`
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const fullPath = generate('migration', targetPath, {
|
|
41
|
+
description: name.replace(/_/g, ' '),
|
|
42
|
+
tableName: tableName
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
console.log(`\n${green('Created:')} ${targetPath}`)
|
|
46
|
+
console.log(`${dim('Table:')} ${tableName}\n`)
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error(`\n${red('Error:')} ${err.message}\n`)
|
|
49
|
+
process.exit(1)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* make:model command
|
|
3
|
+
* Generates a model file in src/models/
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { generate, transformName } from '../scaffolding/index.js'
|
|
7
|
+
import { green, red, cyan, dim } from '../cli/colors.js'
|
|
8
|
+
|
|
9
|
+
export async function run(args, flags) {
|
|
10
|
+
const name = args[0]
|
|
11
|
+
|
|
12
|
+
if (!name) {
|
|
13
|
+
console.error(`\n${red('Error:')} Model name is required`)
|
|
14
|
+
console.error(`\nUsage: ${cyan('basicben make:model <Name>')}\n`)
|
|
15
|
+
process.exit(1)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const names = transformName(name)
|
|
19
|
+
const fileName = `${names.pascal}.js`
|
|
20
|
+
const targetPath = `src/models/${fileName}`
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const fullPath = generate('model', targetPath, {
|
|
24
|
+
Name: names.pascal,
|
|
25
|
+
name: names.camel,
|
|
26
|
+
lower: names.lower,
|
|
27
|
+
pluralLower: names.pluralLower,
|
|
28
|
+
tableName: names.pluralSnake
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
console.log(`\n${green('Created:')} ${targetPath}`)
|
|
32
|
+
console.log(`${dim('Model:')} ${names.pascal}`)
|
|
33
|
+
console.log(`${dim('Table:')} ${names.pluralSnake}\n`)
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.error(`\n${red('Error:')} ${err.message}\n`)
|
|
36
|
+
process.exit(1)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* make:route command
|
|
3
|
+
* Generates a route file in src/routes/api/
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { generate, transformName } from '../scaffolding/index.js'
|
|
7
|
+
import { green, red, cyan, dim } from '../cli/colors.js'
|
|
8
|
+
|
|
9
|
+
export async function run(args, flags) {
|
|
10
|
+
const name = args[0]
|
|
11
|
+
|
|
12
|
+
if (!name) {
|
|
13
|
+
console.error(`\n${red('Error:')} Route name is required`)
|
|
14
|
+
console.error(`\nUsage: ${cyan('basicben make:route <name>')}\n`)
|
|
15
|
+
process.exit(1)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const names = transformName(name)
|
|
19
|
+
const fileName = `${names.lower}.js`
|
|
20
|
+
const targetPath = `src/routes/api/${fileName}`
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const fullPath = generate('route', targetPath, {
|
|
24
|
+
Name: names.pascal,
|
|
25
|
+
name: names.camel,
|
|
26
|
+
lower: names.lower,
|
|
27
|
+
pluralLower: names.pluralLower
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
console.log(`\n${green('Created:')} ${targetPath}`)
|
|
31
|
+
console.log(`${dim('Routes:')} /${names.pluralLower}\n`)
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.error(`\n${red('Error:')} ${err.message}\n`)
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* make:seed command
|
|
3
|
+
* Generates a seed file in seeds/
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { generate, transformName } from '../scaffolding/index.js'
|
|
7
|
+
import { green, red, cyan, dim } from '../cli/colors.js'
|
|
8
|
+
|
|
9
|
+
export async function run(args, flags) {
|
|
10
|
+
const name = args[0]
|
|
11
|
+
|
|
12
|
+
if (!name) {
|
|
13
|
+
console.error(`\n${red('Error:')} Seed name is required`)
|
|
14
|
+
console.error(`\nUsage: ${cyan('basicben make:seed <name>')}\n`)
|
|
15
|
+
console.error(`Examples:`)
|
|
16
|
+
console.error(` ${dim('basicben make:seed users')}`)
|
|
17
|
+
console.error(` ${dim('basicben make:seed sample_posts')}\n`)
|
|
18
|
+
process.exit(1)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const names = transformName(name)
|
|
22
|
+
const fileName = `${names.snake}.js`
|
|
23
|
+
const targetPath = `seeds/${fileName}`
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
generate('seed', targetPath, {
|
|
27
|
+
name: names.pascal,
|
|
28
|
+
tableName: names.pluralSnake,
|
|
29
|
+
lower: names.lower
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
console.log(`\n${green('Created:')} ${targetPath}`)
|
|
33
|
+
console.log(`${dim('Run with:')} basicben seed ${names.snake}\n`)
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.error(`\n${red('Error:')} ${err.message}\n`)
|
|
36
|
+
process.exit(1)
|
|
37
|
+
}
|
|
38
|
+
}
|