@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.
Files changed (186) hide show
  1. package/.github/workflows/publish.yml +35 -0
  2. package/README.md +588 -0
  3. package/bin/cli.js +8 -0
  4. package/create-basicben-app/index.js +205 -0
  5. package/create-basicben-app/package.json +30 -0
  6. package/create-basicben-app/template/.env.example +24 -0
  7. package/create-basicben-app/template/README.md +59 -0
  8. package/create-basicben-app/template/basicben.config.js +33 -0
  9. package/create-basicben-app/template/index.html +54 -0
  10. package/create-basicben-app/template/migrations/001_create_users.js +15 -0
  11. package/create-basicben-app/template/migrations/002_create_posts.js +18 -0
  12. package/create-basicben-app/template/public/.gitkeep +0 -0
  13. package/create-basicben-app/template/seeds/01_users.js +29 -0
  14. package/create-basicben-app/template/seeds/02_posts.js +43 -0
  15. package/create-basicben-app/template/src/client/components/Alert.jsx +11 -0
  16. package/create-basicben-app/template/src/client/components/Avatar.jsx +11 -0
  17. package/create-basicben-app/template/src/client/components/BackLink.jsx +10 -0
  18. package/create-basicben-app/template/src/client/components/Button.jsx +19 -0
  19. package/create-basicben-app/template/src/client/components/Card.jsx +10 -0
  20. package/create-basicben-app/template/src/client/components/Empty.jsx +6 -0
  21. package/create-basicben-app/template/src/client/components/Input.jsx +12 -0
  22. package/create-basicben-app/template/src/client/components/Loading.jsx +6 -0
  23. package/create-basicben-app/template/src/client/components/Logo.jsx +40 -0
  24. package/create-basicben-app/template/src/client/components/Nav/DarkModeToggle.jsx +23 -0
  25. package/create-basicben-app/template/src/client/components/Nav/DesktopNav.jsx +32 -0
  26. package/create-basicben-app/template/src/client/components/Nav/MobileNav.jsx +107 -0
  27. package/create-basicben-app/template/src/client/components/NavLink.jsx +10 -0
  28. package/create-basicben-app/template/src/client/components/PageHeader.jsx +8 -0
  29. package/create-basicben-app/template/src/client/components/PostCard.jsx +19 -0
  30. package/create-basicben-app/template/src/client/components/Textarea.jsx +12 -0
  31. package/create-basicben-app/template/src/client/components/ThemeContext.jsx +5 -0
  32. package/create-basicben-app/template/src/client/contexts/ToastContext.jsx +94 -0
  33. package/create-basicben-app/template/src/client/layouts/AppLayout.jsx +60 -0
  34. package/create-basicben-app/template/src/client/layouts/AuthLayout.jsx +33 -0
  35. package/create-basicben-app/template/src/client/layouts/DocsLayout.jsx +60 -0
  36. package/create-basicben-app/template/src/client/layouts/RootLayout.jsx +25 -0
  37. package/create-basicben-app/template/src/client/pages/Auth.jsx +55 -0
  38. package/create-basicben-app/template/src/client/pages/Authentication.jsx +236 -0
  39. package/create-basicben-app/template/src/client/pages/Database.jsx +426 -0
  40. package/create-basicben-app/template/src/client/pages/Feed.jsx +34 -0
  41. package/create-basicben-app/template/src/client/pages/FeedPost.jsx +37 -0
  42. package/create-basicben-app/template/src/client/pages/GettingStarted.jsx +136 -0
  43. package/create-basicben-app/template/src/client/pages/Home.jsx +206 -0
  44. package/create-basicben-app/template/src/client/pages/PostForm.jsx +69 -0
  45. package/create-basicben-app/template/src/client/pages/Posts.jsx +59 -0
  46. package/create-basicben-app/template/src/client/pages/Profile.jsx +68 -0
  47. package/create-basicben-app/template/src/client/pages/Routing.jsx +207 -0
  48. package/create-basicben-app/template/src/client/pages/Testing.jsx +251 -0
  49. package/create-basicben-app/template/src/client/pages/Validation.jsx +210 -0
  50. package/create-basicben-app/template/src/controllers/AuthController.js +81 -0
  51. package/create-basicben-app/template/src/controllers/HomeController.js +17 -0
  52. package/create-basicben-app/template/src/controllers/PostController.js +86 -0
  53. package/create-basicben-app/template/src/controllers/ProfileController.js +66 -0
  54. package/create-basicben-app/template/src/helpers/api.js +24 -0
  55. package/create-basicben-app/template/src/main.jsx +9 -0
  56. package/create-basicben-app/template/src/middleware/auth.js +16 -0
  57. package/create-basicben-app/template/src/models/Post.js +63 -0
  58. package/create-basicben-app/template/src/models/User.js +42 -0
  59. package/create-basicben-app/template/src/routes/App.jsx +38 -0
  60. package/create-basicben-app/template/src/routes/api/auth.js +7 -0
  61. package/create-basicben-app/template/src/routes/api/posts.js +15 -0
  62. package/create-basicben-app/template/src/routes/api/profile.js +8 -0
  63. package/create-basicben-app/template/src/server/index.js +16 -0
  64. package/create-basicben-app/template/vite.config.js +18 -0
  65. package/database.sqlite +0 -0
  66. package/my-test-app/.env.example +24 -0
  67. package/my-test-app/README.md +59 -0
  68. package/my-test-app/basicben.config.js +33 -0
  69. package/my-test-app/database.sqlite-shm +0 -0
  70. package/my-test-app/database.sqlite-wal +0 -0
  71. package/my-test-app/index.html +54 -0
  72. package/my-test-app/migrations/001_create_users.js +15 -0
  73. package/my-test-app/migrations/002_create_posts.js +18 -0
  74. package/my-test-app/package-lock.json +2160 -0
  75. package/my-test-app/package.json +29 -0
  76. package/my-test-app/public/.gitkeep +0 -0
  77. package/my-test-app/seeds/01_users.js +29 -0
  78. package/my-test-app/seeds/02_posts.js +43 -0
  79. package/my-test-app/src/client/components/Alert.jsx +11 -0
  80. package/my-test-app/src/client/components/Avatar.jsx +11 -0
  81. package/my-test-app/src/client/components/BackLink.jsx +10 -0
  82. package/my-test-app/src/client/components/Button.jsx +19 -0
  83. package/my-test-app/src/client/components/Card.jsx +10 -0
  84. package/my-test-app/src/client/components/Empty.jsx +6 -0
  85. package/my-test-app/src/client/components/Input.jsx +12 -0
  86. package/my-test-app/src/client/components/Loading.jsx +6 -0
  87. package/my-test-app/src/client/components/Logo.jsx +40 -0
  88. package/my-test-app/src/client/components/Nav/DarkModeToggle.jsx +23 -0
  89. package/my-test-app/src/client/components/Nav/DesktopNav.jsx +32 -0
  90. package/my-test-app/src/client/components/Nav/MobileNav.jsx +107 -0
  91. package/my-test-app/src/client/components/NavLink.jsx +10 -0
  92. package/my-test-app/src/client/components/PageHeader.jsx +8 -0
  93. package/my-test-app/src/client/components/PostCard.jsx +19 -0
  94. package/my-test-app/src/client/components/Textarea.jsx +12 -0
  95. package/my-test-app/src/client/components/ThemeContext.jsx +5 -0
  96. package/my-test-app/src/client/contexts/AppContext.jsx +13 -0
  97. package/my-test-app/src/client/contexts/ToastContext.jsx +94 -0
  98. package/my-test-app/src/client/layouts/AppLayout.jsx +60 -0
  99. package/my-test-app/src/client/layouts/AuthLayout.jsx +33 -0
  100. package/my-test-app/src/client/layouts/DocsLayout.jsx +60 -0
  101. package/my-test-app/src/client/layouts/RootLayout.jsx +25 -0
  102. package/my-test-app/src/client/pages/Auth.jsx +55 -0
  103. package/my-test-app/src/client/pages/Authentication.jsx +236 -0
  104. package/my-test-app/src/client/pages/Database.jsx +426 -0
  105. package/my-test-app/src/client/pages/Feed.jsx +34 -0
  106. package/my-test-app/src/client/pages/FeedPost.jsx +37 -0
  107. package/my-test-app/src/client/pages/GettingStarted.jsx +136 -0
  108. package/my-test-app/src/client/pages/Home.jsx +206 -0
  109. package/my-test-app/src/client/pages/PostForm.jsx +69 -0
  110. package/my-test-app/src/client/pages/Posts.jsx +59 -0
  111. package/my-test-app/src/client/pages/Profile.jsx +68 -0
  112. package/my-test-app/src/client/pages/Routing.jsx +207 -0
  113. package/my-test-app/src/client/pages/Testing.jsx +251 -0
  114. package/my-test-app/src/client/pages/Validation.jsx +210 -0
  115. package/my-test-app/src/controllers/AuthController.js +81 -0
  116. package/my-test-app/src/controllers/HomeController.js +17 -0
  117. package/my-test-app/src/controllers/PostController.js +86 -0
  118. package/my-test-app/src/controllers/ProfileController.js +66 -0
  119. package/my-test-app/src/helpers/api.js +24 -0
  120. package/my-test-app/src/main.jsx +9 -0
  121. package/my-test-app/src/middleware/auth.js +16 -0
  122. package/my-test-app/src/models/Post.js +63 -0
  123. package/my-test-app/src/models/User.js +42 -0
  124. package/my-test-app/src/routes/App.jsx +38 -0
  125. package/my-test-app/src/routes/api/auth.js +7 -0
  126. package/my-test-app/src/routes/api/posts.js +15 -0
  127. package/my-test-app/src/routes/api/profile.js +8 -0
  128. package/my-test-app/src/server/index.js +16 -0
  129. package/my-test-app/vite.config.js +18 -0
  130. package/package.json +61 -0
  131. package/scripts/test-app.sh +59 -0
  132. package/src/auth/jwt.js +195 -0
  133. package/src/auth/password.js +132 -0
  134. package/src/cli/colors.js +31 -0
  135. package/src/cli/dispatcher.js +168 -0
  136. package/src/cli/parser.js +91 -0
  137. package/src/client/context.js +4 -0
  138. package/src/client/hooks.js +50 -0
  139. package/src/client/index.js +3 -0
  140. package/src/client/router.js +184 -0
  141. package/src/commands/build.js +155 -0
  142. package/src/commands/dev.js +206 -0
  143. package/src/commands/help.js +84 -0
  144. package/src/commands/make-controller.js +36 -0
  145. package/src/commands/make-middleware.js +44 -0
  146. package/src/commands/make-migration.js +51 -0
  147. package/src/commands/make-model.js +38 -0
  148. package/src/commands/make-route.js +36 -0
  149. package/src/commands/make-seed.js +38 -0
  150. package/src/commands/migrate-fresh.js +32 -0
  151. package/src/commands/migrate-rollback.js +30 -0
  152. package/src/commands/migrate-status.js +41 -0
  153. package/src/commands/migrate.js +30 -0
  154. package/src/commands/seed.js +47 -0
  155. package/src/commands/start.js +69 -0
  156. package/src/commands/test.js +46 -0
  157. package/src/db/Grammar.js +125 -0
  158. package/src/db/QueryBuilder.js +476 -0
  159. package/src/db/adapters/neon.js +170 -0
  160. package/src/db/adapters/planetscale.js +146 -0
  161. package/src/db/adapters/postgres.js +166 -0
  162. package/src/db/adapters/sqlite.js +125 -0
  163. package/src/db/adapters/turso.js +165 -0
  164. package/src/db/index.js +156 -0
  165. package/src/db/migrator.js +250 -0
  166. package/src/db/seeder.js +124 -0
  167. package/src/index.js +12 -0
  168. package/src/scaffolding/index.js +152 -0
  169. package/src/server/body-parser.js +159 -0
  170. package/src/server/cors.js +63 -0
  171. package/src/server/default-entry.js +13 -0
  172. package/src/server/http.js +221 -0
  173. package/src/server/index.js +168 -0
  174. package/src/server/loader.js +128 -0
  175. package/src/server/router.js +281 -0
  176. package/src/server/static.js +139 -0
  177. package/src/validation/index.js +436 -0
  178. package/src/vite/config.js +49 -0
  179. package/stubs/controller.stub +48 -0
  180. package/stubs/middleware-auth.stub +29 -0
  181. package/stubs/middleware.stub +9 -0
  182. package/stubs/migration.stub +17 -0
  183. package/stubs/model.stub +77 -0
  184. package/stubs/route.stub +13 -0
  185. package/stubs/seed.stub +16 -0
  186. 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
+ }