voltjs-framework 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/LICENSE +21 -0
- package/README.md +1265 -0
- package/bin/volt.js +139 -0
- package/package.json +56 -0
- package/src/api/graphql.js +399 -0
- package/src/api/rest.js +204 -0
- package/src/api/websocket.js +285 -0
- package/src/cli/build.js +111 -0
- package/src/cli/create.js +371 -0
- package/src/cli/db.js +106 -0
- package/src/cli/dev.js +114 -0
- package/src/cli/generate.js +278 -0
- package/src/cli/lint.js +172 -0
- package/src/cli/routes.js +118 -0
- package/src/cli/start.js +42 -0
- package/src/cli/test.js +138 -0
- package/src/core/app.js +701 -0
- package/src/core/config.js +232 -0
- package/src/core/middleware.js +133 -0
- package/src/core/plugins.js +88 -0
- package/src/core/react-renderer.js +244 -0
- package/src/core/renderer.js +337 -0
- package/src/core/router.js +183 -0
- package/src/database/index.js +461 -0
- package/src/database/migration.js +192 -0
- package/src/database/model.js +285 -0
- package/src/database/query.js +394 -0
- package/src/database/seeder.js +89 -0
- package/src/index.js +156 -0
- package/src/security/auth.js +425 -0
- package/src/security/cors.js +80 -0
- package/src/security/csrf.js +125 -0
- package/src/security/encryption.js +110 -0
- package/src/security/helmet.js +103 -0
- package/src/security/index.js +75 -0
- package/src/security/rateLimit.js +119 -0
- package/src/security/sanitizer.js +113 -0
- package/src/security/xss.js +110 -0
- package/src/ui/component.js +224 -0
- package/src/ui/reactive.js +503 -0
- package/src/ui/template.js +448 -0
- package/src/utils/cache.js +216 -0
- package/src/utils/collection.js +772 -0
- package/src/utils/cron.js +213 -0
- package/src/utils/date.js +223 -0
- package/src/utils/events.js +181 -0
- package/src/utils/excel.js +482 -0
- package/src/utils/form.js +547 -0
- package/src/utils/hash.js +121 -0
- package/src/utils/http.js +461 -0
- package/src/utils/logger.js +186 -0
- package/src/utils/mail.js +347 -0
- package/src/utils/paginator.js +179 -0
- package/src/utils/pdf.js +417 -0
- package/src/utils/queue.js +199 -0
- package/src/utils/schema.js +985 -0
- package/src/utils/sms.js +243 -0
- package/src/utils/storage.js +348 -0
- package/src/utils/string.js +236 -0
- package/src/utils/validation.js +318 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoltJS CLI — `volt create <project-name>`
|
|
3
|
+
* Scaffolds a new VoltJS project.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const c = {
|
|
12
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
13
|
+
red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', cyan: '\x1b[36m',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
module.exports = function create(args) {
|
|
17
|
+
const projectName = args[0];
|
|
18
|
+
if (!projectName) {
|
|
19
|
+
console.error(`${c.red}Error: Please specify a project name.${c.reset}`);
|
|
20
|
+
console.log(` ${c.cyan}volt create my-app${c.reset}`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const projectDir = path.resolve(process.cwd(), projectName);
|
|
25
|
+
|
|
26
|
+
if (fs.existsSync(projectDir)) {
|
|
27
|
+
console.error(`${c.red}Error: Directory "${projectName}" already exists.${c.reset}`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log(`\n${c.cyan}${c.bold}⚡ Creating VoltJS project: ${projectName}${c.reset}\n`);
|
|
32
|
+
|
|
33
|
+
// Create directory structure
|
|
34
|
+
const dirs = [
|
|
35
|
+
'',
|
|
36
|
+
'pages',
|
|
37
|
+
'api',
|
|
38
|
+
'components',
|
|
39
|
+
'public',
|
|
40
|
+
'public/css',
|
|
41
|
+
'public/js',
|
|
42
|
+
'public/images',
|
|
43
|
+
'views',
|
|
44
|
+
'views/layouts',
|
|
45
|
+
'views/partials',
|
|
46
|
+
'models',
|
|
47
|
+
'middleware',
|
|
48
|
+
'config',
|
|
49
|
+
'database',
|
|
50
|
+
'database/migrations',
|
|
51
|
+
'database/seeders',
|
|
52
|
+
'storage',
|
|
53
|
+
'tests',
|
|
54
|
+
'logs',
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
for (const dir of dirs) {
|
|
58
|
+
const fullPath = path.join(projectDir, dir);
|
|
59
|
+
fs.mkdirSync(fullPath, { recursive: true });
|
|
60
|
+
console.log(` ${c.green}✓${c.reset} ${c.dim}${dir || projectName}/${c.reset}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Create files
|
|
64
|
+
const files = {
|
|
65
|
+
'package.json': JSON.stringify({
|
|
66
|
+
name: projectName,
|
|
67
|
+
version: '1.0.0',
|
|
68
|
+
private: true,
|
|
69
|
+
scripts: {
|
|
70
|
+
dev: 'volt dev',
|
|
71
|
+
start: 'volt start',
|
|
72
|
+
build: 'volt build',
|
|
73
|
+
test: 'volt test',
|
|
74
|
+
'db:migrate': 'volt db:migrate',
|
|
75
|
+
'db:seed': 'volt db:seed',
|
|
76
|
+
},
|
|
77
|
+
dependencies: {
|
|
78
|
+
voltjs: '^1.0.0',
|
|
79
|
+
},
|
|
80
|
+
}, null, 2),
|
|
81
|
+
|
|
82
|
+
'app.js': `const { Volt } = require('voltjs');
|
|
83
|
+
|
|
84
|
+
const app = new Volt();
|
|
85
|
+
|
|
86
|
+
// Your routes
|
|
87
|
+
app.get('/', (req, res) => {
|
|
88
|
+
res.render('index', {
|
|
89
|
+
title: '${projectName}',
|
|
90
|
+
message: 'Welcome to VoltJS! ⚡',
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// API example
|
|
95
|
+
app.get('/api/hello', (req, res) => {
|
|
96
|
+
res.json({ message: 'Hello from VoltJS API!', time: new Date().toISOString() });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Start the server
|
|
100
|
+
app.listen(3000, () => {
|
|
101
|
+
console.log('⚡ Server running at http://localhost:3000');
|
|
102
|
+
});
|
|
103
|
+
`,
|
|
104
|
+
|
|
105
|
+
'volt.config.js': `module.exports = {
|
|
106
|
+
// Server
|
|
107
|
+
port: process.env.PORT || 3000,
|
|
108
|
+
|
|
109
|
+
// Security (all enabled by default)
|
|
110
|
+
security: {
|
|
111
|
+
csrf: true,
|
|
112
|
+
cors: { origin: '*' },
|
|
113
|
+
rateLimit: { windowMs: 15 * 60 * 1000, max: 100 },
|
|
114
|
+
helmet: true,
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// Views
|
|
118
|
+
views: {
|
|
119
|
+
dir: './views',
|
|
120
|
+
engine: 'volt',
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
// Database
|
|
124
|
+
database: {
|
|
125
|
+
driver: 'memory', // 'memory', 'sqlite', 'mysql', 'postgres'
|
|
126
|
+
// host: 'localhost',
|
|
127
|
+
// port: 3306,
|
|
128
|
+
// database: '${projectName}',
|
|
129
|
+
// user: 'root',
|
|
130
|
+
// password: '',
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
// Mail
|
|
134
|
+
mail: {
|
|
135
|
+
host: process.env.MAIL_HOST || 'smtp.example.com',
|
|
136
|
+
port: process.env.MAIL_PORT || 587,
|
|
137
|
+
secure: false,
|
|
138
|
+
user: process.env.MAIL_USER || '',
|
|
139
|
+
pass: process.env.MAIL_PASS || '',
|
|
140
|
+
from: '${projectName} <noreply@example.com>',
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// Logging
|
|
144
|
+
logging: {
|
|
145
|
+
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
|
|
146
|
+
file: './logs/app.log',
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
`,
|
|
150
|
+
|
|
151
|
+
'.env': `# Environment
|
|
152
|
+
NODE_ENV=development
|
|
153
|
+
PORT=3000
|
|
154
|
+
|
|
155
|
+
# Database
|
|
156
|
+
DB_DRIVER=memory
|
|
157
|
+
# DB_HOST=localhost
|
|
158
|
+
# DB_PORT=3306
|
|
159
|
+
# DB_DATABASE=${projectName}
|
|
160
|
+
# DB_USER=root
|
|
161
|
+
# DB_PASSWORD=
|
|
162
|
+
|
|
163
|
+
# Mail
|
|
164
|
+
# MAIL_HOST=smtp.example.com
|
|
165
|
+
# MAIL_PORT=587
|
|
166
|
+
# MAIL_USER=
|
|
167
|
+
# MAIL_PASS=
|
|
168
|
+
|
|
169
|
+
# Security
|
|
170
|
+
APP_SECRET=${require('crypto').randomBytes(32).toString('hex')}
|
|
171
|
+
`,
|
|
172
|
+
|
|
173
|
+
'.env.example': `# Environment
|
|
174
|
+
NODE_ENV=development
|
|
175
|
+
PORT=3000
|
|
176
|
+
|
|
177
|
+
# Database
|
|
178
|
+
DB_DRIVER=memory
|
|
179
|
+
DB_HOST=localhost
|
|
180
|
+
DB_PORT=3306
|
|
181
|
+
DB_DATABASE=myapp
|
|
182
|
+
DB_USER=root
|
|
183
|
+
DB_PASSWORD=
|
|
184
|
+
|
|
185
|
+
# Mail
|
|
186
|
+
MAIL_HOST=smtp.example.com
|
|
187
|
+
MAIL_PORT=587
|
|
188
|
+
MAIL_USER=
|
|
189
|
+
MAIL_PASS=
|
|
190
|
+
|
|
191
|
+
# Security
|
|
192
|
+
APP_SECRET=your-secret-key-here
|
|
193
|
+
`,
|
|
194
|
+
|
|
195
|
+
'.gitignore': `node_modules/
|
|
196
|
+
.env
|
|
197
|
+
logs/
|
|
198
|
+
storage/
|
|
199
|
+
dist/
|
|
200
|
+
*.log
|
|
201
|
+
.DS_Store
|
|
202
|
+
Thumbs.db
|
|
203
|
+
`,
|
|
204
|
+
|
|
205
|
+
'views/layouts/main.volt': `<!DOCTYPE html>
|
|
206
|
+
<html lang="en">
|
|
207
|
+
<head>
|
|
208
|
+
<meta charset="UTF-8">
|
|
209
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
210
|
+
<title>{{ title }} | ${projectName}</title>
|
|
211
|
+
<link rel="stylesheet" href="/css/app.css">
|
|
212
|
+
</head>
|
|
213
|
+
<body>
|
|
214
|
+
{> header}
|
|
215
|
+
<main>
|
|
216
|
+
{#slot content}
|
|
217
|
+
</main>
|
|
218
|
+
{> footer}
|
|
219
|
+
<script src="/js/app.js"></script>
|
|
220
|
+
</body>
|
|
221
|
+
</html>
|
|
222
|
+
`,
|
|
223
|
+
|
|
224
|
+
'views/partials/header.volt': `<header style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 1rem 2rem;">
|
|
225
|
+
<nav style="display: flex; justify-content: space-between; align-items: center; max-width: 1200px; margin: 0 auto;">
|
|
226
|
+
<a href="/" style="color: white; text-decoration: none; font-size: 1.5rem; font-weight: bold;">⚡ ${projectName}</a>
|
|
227
|
+
<div>
|
|
228
|
+
<a href="/" style="color: white; text-decoration: none; margin-left: 1.5rem;">Home</a>
|
|
229
|
+
<a href="/api/hello" style="color: white; text-decoration: none; margin-left: 1.5rem;">API</a>
|
|
230
|
+
</div>
|
|
231
|
+
</nav>
|
|
232
|
+
</header>
|
|
233
|
+
`,
|
|
234
|
+
|
|
235
|
+
'views/partials/footer.volt': `<footer style="text-align: center; padding: 2rem; color: #666; border-top: 1px solid #eee; margin-top: 2rem;">
|
|
236
|
+
<p>Built with ⚡ <a href="https://github.com/voltjs" style="color: #667eea;">VoltJS</a></p>
|
|
237
|
+
</footer>
|
|
238
|
+
`,
|
|
239
|
+
|
|
240
|
+
'views/index.volt': `{#layout layouts/main}
|
|
241
|
+
|
|
242
|
+
{#block content}
|
|
243
|
+
<div style="text-align: center; padding: 4rem 2rem; max-width: 800px; margin: 0 auto;">
|
|
244
|
+
<h1 style="font-size: 3rem; margin-bottom: 0.5rem;">⚡ {{ title }}</h1>
|
|
245
|
+
<p style="font-size: 1.3rem; color: #666;">{{ message }}</p>
|
|
246
|
+
|
|
247
|
+
<div style="margin-top: 3rem; display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1.5rem; text-align: left;">
|
|
248
|
+
<div style="padding: 1.5rem; border-radius: 8px; border: 1px solid #e2e8f0;">
|
|
249
|
+
<h3>🔒 Security First</h3>
|
|
250
|
+
<p style="color: #666;">CSRF, XSS, CORS, rate limiting, and helmet — all enabled by default.</p>
|
|
251
|
+
</div>
|
|
252
|
+
<div style="padding: 1.5rem; border-radius: 8px; border: 1px solid #e2e8f0;">
|
|
253
|
+
<h3>📦 Batteries Included</h3>
|
|
254
|
+
<p style="color: #666;">Email, SMS, PDF, Excel, caching, queues, cron — ready to use.</p>
|
|
255
|
+
</div>
|
|
256
|
+
<div style="padding: 1.5rem; border-radius: 8px; border: 1px solid #e2e8f0;">
|
|
257
|
+
<h3>🚀 Zero Config</h3>
|
|
258
|
+
<p style="color: #666;">Just 2 files to start. Everything works out of the box.</p>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
{/block}
|
|
263
|
+
`,
|
|
264
|
+
|
|
265
|
+
'public/css/app.css': `/* VoltJS Default Styles */
|
|
266
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
267
|
+
|
|
268
|
+
body {
|
|
269
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
270
|
+
line-height: 1.6;
|
|
271
|
+
color: #333;
|
|
272
|
+
min-height: 100vh;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
a { color: #667eea; text-decoration: none; }
|
|
276
|
+
a:hover { text-decoration: underline; }
|
|
277
|
+
|
|
278
|
+
code {
|
|
279
|
+
background: #f4f4f5;
|
|
280
|
+
padding: 0.2em 0.4em;
|
|
281
|
+
border-radius: 4px;
|
|
282
|
+
font-size: 0.9em;
|
|
283
|
+
}
|
|
284
|
+
`,
|
|
285
|
+
|
|
286
|
+
'public/js/app.js': `// VoltJS Client-Side JavaScript
|
|
287
|
+
console.log('⚡ VoltJS loaded');
|
|
288
|
+
`,
|
|
289
|
+
|
|
290
|
+
'tests/app.test.js': `/**
|
|
291
|
+
* Basic tests for your VoltJS app
|
|
292
|
+
* Run: volt test
|
|
293
|
+
*/
|
|
294
|
+
|
|
295
|
+
const assert = require('assert');
|
|
296
|
+
|
|
297
|
+
describe('App', () => {
|
|
298
|
+
it('should load without errors', () => {
|
|
299
|
+
assert.ok(true);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
`,
|
|
303
|
+
|
|
304
|
+
'README.md': `# ${projectName}
|
|
305
|
+
|
|
306
|
+
Built with ⚡ [VoltJS](https://github.com/voltjs) — the batteries-included, security-first JavaScript framework.
|
|
307
|
+
|
|
308
|
+
## Quick Start
|
|
309
|
+
|
|
310
|
+
\`\`\`bash
|
|
311
|
+
npm install
|
|
312
|
+
npm run dev
|
|
313
|
+
\`\`\`
|
|
314
|
+
|
|
315
|
+
Open [http://localhost:3000](http://localhost:3000) in your browser.
|
|
316
|
+
|
|
317
|
+
## Available Commands
|
|
318
|
+
|
|
319
|
+
| Command | Description |
|
|
320
|
+
|---------|-------------|
|
|
321
|
+
| \`npm run dev\` | Start development server with hot reload |
|
|
322
|
+
| \`npm start\` | Start production server |
|
|
323
|
+
| \`npm run build\` | Build for production |
|
|
324
|
+
| \`npm test\` | Run tests |
|
|
325
|
+
| \`npm run db:migrate\` | Run database migrations |
|
|
326
|
+
| \`npm run db:seed\` | Seed the database |
|
|
327
|
+
|
|
328
|
+
## Project Structure
|
|
329
|
+
|
|
330
|
+
\`\`\`
|
|
331
|
+
${projectName}/
|
|
332
|
+
├── app.js # Main application entry
|
|
333
|
+
├── volt.config.js # Framework configuration
|
|
334
|
+
├── pages/ # File-based routes (auto-discovered)
|
|
335
|
+
├── api/ # API routes (auto-discovered)
|
|
336
|
+
├── views/ # Templates
|
|
337
|
+
│ ├── layouts/ # Layout files
|
|
338
|
+
│ └── partials/ # Partial templates
|
|
339
|
+
├── components/ # Reusable components
|
|
340
|
+
├── models/ # Database models
|
|
341
|
+
├── middleware/ # Custom middleware
|
|
342
|
+
├── public/ # Static assets
|
|
343
|
+
├── database/ # Migrations & seeders
|
|
344
|
+
├── storage/ # File uploads
|
|
345
|
+
├── tests/ # Test files
|
|
346
|
+
└── logs/ # Log files
|
|
347
|
+
\`\`\`
|
|
348
|
+
`,
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// Write files
|
|
352
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
353
|
+
const fullPath = path.join(projectDir, filePath);
|
|
354
|
+
const dir = path.dirname(fullPath);
|
|
355
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
356
|
+
fs.writeFileSync(fullPath, content);
|
|
357
|
+
console.log(` ${c.green}✓${c.reset} ${filePath}`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
console.log(`
|
|
361
|
+
${c.green}${c.bold}✓ Project created successfully!${c.reset}
|
|
362
|
+
|
|
363
|
+
${c.bold}Next steps:${c.reset}
|
|
364
|
+
|
|
365
|
+
${c.cyan}cd ${projectName}${c.reset}
|
|
366
|
+
${c.cyan}npm install${c.reset}
|
|
367
|
+
${c.cyan}npm run dev${c.reset}
|
|
368
|
+
|
|
369
|
+
${c.dim}Your app will be running at ${c.cyan}http://localhost:3000${c.reset}
|
|
370
|
+
`);
|
|
371
|
+
};
|
package/src/cli/db.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoltJS CLI — `volt db:migrate`, `volt db:seed`, `volt db:rollback`
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const c = {
|
|
10
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
11
|
+
red: '\x1b[31m', green: '\x1b[32m', cyan: '\x1b[36m',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
module.exports = async function db(args) {
|
|
15
|
+
const action = args[0];
|
|
16
|
+
const cwd = process.cwd();
|
|
17
|
+
|
|
18
|
+
// Load config
|
|
19
|
+
let config = {};
|
|
20
|
+
try {
|
|
21
|
+
config = require(path.join(cwd, 'volt.config.js'));
|
|
22
|
+
} catch { }
|
|
23
|
+
|
|
24
|
+
// Import database and migration/seeder modules
|
|
25
|
+
let Database, Migration, Seeder;
|
|
26
|
+
try {
|
|
27
|
+
({ Database } = require('../database'));
|
|
28
|
+
({ Migration } = require('../database/migration'));
|
|
29
|
+
({ Seeder } = require('../database/seeder'));
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error(`${c.red}Error loading database modules: ${err.message}${c.reset}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const db = new Database(config.database || {});
|
|
36
|
+
await db.connect();
|
|
37
|
+
|
|
38
|
+
switch (action) {
|
|
39
|
+
case 'migrate': {
|
|
40
|
+
console.log(`${c.cyan}${c.bold}⚡ Running migrations...${c.reset}\n`);
|
|
41
|
+
const migrationsDir = path.join(cwd, 'database', 'migrations');
|
|
42
|
+
try {
|
|
43
|
+
const result = await Migration.run(db, migrationsDir);
|
|
44
|
+
if (result && result.length > 0) {
|
|
45
|
+
for (const m of result) {
|
|
46
|
+
console.log(` ${c.green}✓${c.reset} ${m}`);
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
console.log(` ${c.dim}Nothing to migrate.${c.reset}`);
|
|
50
|
+
}
|
|
51
|
+
console.log(`\n${c.green}Migrations complete.${c.reset}`);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
console.error(`${c.red}Migration failed: ${err.message}${c.reset}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
case 'rollback': {
|
|
60
|
+
console.log(`${c.cyan}${c.bold}⚡ Rolling back...${c.reset}\n`);
|
|
61
|
+
const migrationsDir = path.join(cwd, 'database', 'migrations');
|
|
62
|
+
try {
|
|
63
|
+
const result = await Migration.rollback(db, migrationsDir);
|
|
64
|
+
if (result && result.length > 0) {
|
|
65
|
+
for (const m of result) {
|
|
66
|
+
console.log(` ${c.green}✓${c.reset} Rolled back: ${m}`);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
console.log(` ${c.dim}Nothing to rollback.${c.reset}`);
|
|
70
|
+
}
|
|
71
|
+
console.log(`\n${c.green}Rollback complete.${c.reset}`);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error(`${c.red}Rollback failed: ${err.message}${c.reset}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
case 'seed': {
|
|
80
|
+
console.log(`${c.cyan}${c.bold}⚡ Running seeders...${c.reset}\n`);
|
|
81
|
+
const seedersDir = path.join(cwd, 'database', 'seeders');
|
|
82
|
+
try {
|
|
83
|
+
const result = await Seeder.run(db, seedersDir);
|
|
84
|
+
if (result && result.length > 0) {
|
|
85
|
+
for (const s of result) {
|
|
86
|
+
console.log(` ${c.green}✓${c.reset} ${s}`);
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
console.log(` ${c.dim}No seeders found.${c.reset}`);
|
|
90
|
+
}
|
|
91
|
+
console.log(`\n${c.green}Seeding complete.${c.reset}`);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
console.error(`${c.red}Seeding failed: ${err.message}${c.reset}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
default:
|
|
100
|
+
console.error(`${c.red}Unknown db command: ${action}${c.reset}`);
|
|
101
|
+
console.log(` Available: ${c.cyan}migrate${c.reset}, ${c.cyan}rollback${c.reset}, ${c.cyan}seed${c.reset}`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
process.exit(0);
|
|
106
|
+
};
|
package/src/cli/dev.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoltJS CLI — `volt dev`
|
|
3
|
+
* Starts a development server with hot reload (file watching).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { spawn } = require('child_process');
|
|
11
|
+
|
|
12
|
+
const c = {
|
|
13
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
14
|
+
red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', cyan: '\x1b[36m',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
module.exports = function dev(args) {
|
|
18
|
+
const cwd = process.cwd();
|
|
19
|
+
const entry = findEntry(cwd);
|
|
20
|
+
|
|
21
|
+
if (!entry) {
|
|
22
|
+
console.error(`${c.red}Error: No app entry point found (app.js, index.js, or server.js).${c.reset}`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(`${c.cyan}${c.bold}⚡ VoltJS Dev Server${c.reset}`);
|
|
27
|
+
console.log(`${c.dim}Watching for file changes...${c.reset}\n`);
|
|
28
|
+
|
|
29
|
+
let child = null;
|
|
30
|
+
let restarting = false;
|
|
31
|
+
let debounceTimer = null;
|
|
32
|
+
|
|
33
|
+
function startServer() {
|
|
34
|
+
if (child) {
|
|
35
|
+
child.kill('SIGTERM');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
child = spawn(process.execPath, [entry], {
|
|
39
|
+
cwd,
|
|
40
|
+
stdio: 'inherit',
|
|
41
|
+
env: { ...process.env, NODE_ENV: 'development', VOLT_DEV: '1' },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
child.on('exit', (code, signal) => {
|
|
45
|
+
if (!restarting && signal !== 'SIGTERM') {
|
|
46
|
+
if (code !== 0) {
|
|
47
|
+
console.log(`\n${c.yellow}Process exited with code ${code}. Waiting for changes...${c.reset}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
child.on('error', (err) => {
|
|
53
|
+
console.error(`${c.red}Failed to start: ${err.message}${c.reset}`);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function restart() {
|
|
58
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
59
|
+
debounceTimer = setTimeout(() => {
|
|
60
|
+
restarting = true;
|
|
61
|
+
console.log(`\n${c.cyan}↻ Restarting...${c.reset}`);
|
|
62
|
+
startServer();
|
|
63
|
+
restarting = false;
|
|
64
|
+
}, 200);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Watch for file changes
|
|
68
|
+
const watchDirs = ['pages', 'api', 'views', 'components', 'models', 'middleware', 'config', 'src']
|
|
69
|
+
.map(d => path.join(cwd, d))
|
|
70
|
+
.filter(d => fs.existsSync(d));
|
|
71
|
+
|
|
72
|
+
// Also watch root JS files
|
|
73
|
+
watchDirs.push(cwd);
|
|
74
|
+
|
|
75
|
+
const watchedExtensions = ['.js', '.json', '.volt', '.html', '.css'];
|
|
76
|
+
|
|
77
|
+
for (const dir of watchDirs) {
|
|
78
|
+
try {
|
|
79
|
+
fs.watch(dir, { recursive: true }, (eventType, filename) => {
|
|
80
|
+
if (!filename) return;
|
|
81
|
+
const ext = path.extname(filename);
|
|
82
|
+
if (watchedExtensions.includes(ext)) {
|
|
83
|
+
console.log(`${c.dim} Changed: ${filename}${c.reset}`);
|
|
84
|
+
restart();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
} catch {
|
|
88
|
+
// Directory may not support recursive watching
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Graceful shutdown
|
|
93
|
+
process.on('SIGINT', () => {
|
|
94
|
+
console.log(`\n${c.dim}Shutting down dev server...${c.reset}`);
|
|
95
|
+
if (child) child.kill('SIGTERM');
|
|
96
|
+
process.exit(0);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
process.on('SIGTERM', () => {
|
|
100
|
+
if (child) child.kill('SIGTERM');
|
|
101
|
+
process.exit(0);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
startServer();
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
function findEntry(cwd) {
|
|
108
|
+
const candidates = ['app.js', 'index.js', 'server.js', 'src/app.js', 'src/index.js'];
|
|
109
|
+
for (const file of candidates) {
|
|
110
|
+
const fullPath = path.join(cwd, file);
|
|
111
|
+
if (fs.existsSync(fullPath)) return fullPath;
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|