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,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoltJS CLI — `volt generate <type> <name>`
|
|
3
|
+
* Generate pages, API routes, components, models, middleware, migrations.
|
|
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', cyan: '\x1b[36m',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const generators = {
|
|
17
|
+
page(name, cwd) {
|
|
18
|
+
const filePath = path.join(cwd, 'pages', `${name}.js`);
|
|
19
|
+
const content = `/**
|
|
20
|
+
* Page: /${name}
|
|
21
|
+
* Auto-discovered by VoltJS file-based routing.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
// GET /${name}
|
|
26
|
+
async get(req, res) {
|
|
27
|
+
res.render('${name}', {
|
|
28
|
+
title: '${capitalize(name)}',
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// POST /${name}
|
|
33
|
+
// async post(req, res) {
|
|
34
|
+
// res.json({ message: '${name} created' });
|
|
35
|
+
// },
|
|
36
|
+
};
|
|
37
|
+
`;
|
|
38
|
+
writeFile(filePath, content);
|
|
39
|
+
|
|
40
|
+
// Also create the view
|
|
41
|
+
const viewPath = path.join(cwd, 'views', `${name}.volt`);
|
|
42
|
+
if (!fs.existsSync(viewPath)) {
|
|
43
|
+
const viewContent = `{#layout layouts/main}
|
|
44
|
+
|
|
45
|
+
{#block content}
|
|
46
|
+
<div style="padding: 2rem; max-width: 800px; margin: 0 auto;">
|
|
47
|
+
<h1>{{ title }}</h1>
|
|
48
|
+
<p>This is the ${name} page.</p>
|
|
49
|
+
</div>
|
|
50
|
+
{/block}
|
|
51
|
+
`;
|
|
52
|
+
writeFile(viewPath, viewContent);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
api(name, cwd) {
|
|
57
|
+
const filePath = path.join(cwd, 'api', `${name}.js`);
|
|
58
|
+
const modelName = capitalize(singularize(name));
|
|
59
|
+
const content = `/**
|
|
60
|
+
* API: /api/${name}
|
|
61
|
+
* Auto-discovered by VoltJS.
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
module.exports = {
|
|
65
|
+
// GET /api/${name}
|
|
66
|
+
async get(req, res) {
|
|
67
|
+
// const items = await ${modelName}.all();
|
|
68
|
+
res.json({ data: [], message: 'List ${name}' });
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// POST /api/${name}
|
|
72
|
+
async post(req, res) {
|
|
73
|
+
// const item = await ${modelName}.create(req.body);
|
|
74
|
+
res.json({ data: req.body, message: '${modelName} created' }, 201);
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// GET /api/${name}/:id
|
|
78
|
+
async show(req, res) {
|
|
79
|
+
const { id } = req.params;
|
|
80
|
+
// const item = await ${modelName}.find(id);
|
|
81
|
+
res.json({ data: { id }, message: '${modelName} details' });
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
// PUT /api/${name}/:id
|
|
85
|
+
async update(req, res) {
|
|
86
|
+
const { id } = req.params;
|
|
87
|
+
// const item = await ${modelName}.updateById(id, req.body);
|
|
88
|
+
res.json({ data: { id, ...req.body }, message: '${modelName} updated' });
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
// DELETE /api/${name}/:id
|
|
92
|
+
async destroy(req, res) {
|
|
93
|
+
const { id } = req.params;
|
|
94
|
+
// await ${modelName}.deleteById(id);
|
|
95
|
+
res.json({ message: '${modelName} deleted' });
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
`;
|
|
99
|
+
writeFile(filePath, content);
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
component(name, cwd) {
|
|
103
|
+
const filePath = path.join(cwd, 'components', `${name}.js`);
|
|
104
|
+
const className = capitalize(name);
|
|
105
|
+
const content = `const { Component } = require('voltjs');
|
|
106
|
+
|
|
107
|
+
class ${className} extends Component {
|
|
108
|
+
setup() {
|
|
109
|
+
this.state = {};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
render() {
|
|
113
|
+
return \`
|
|
114
|
+
<div class="${name.toLowerCase()}" data-component="${className}">
|
|
115
|
+
<h2>\${this.props.title || '${className}'}</h2>
|
|
116
|
+
\${this.children}
|
|
117
|
+
</div>
|
|
118
|
+
\`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = ${className};
|
|
123
|
+
`;
|
|
124
|
+
writeFile(filePath, content);
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
model(name, cwd) {
|
|
128
|
+
const filePath = path.join(cwd, 'models', `${capitalize(name)}.js`);
|
|
129
|
+
const className = capitalize(name);
|
|
130
|
+
const tableName = name.toLowerCase() + 's';
|
|
131
|
+
const content = `const { Model } = require('voltjs');
|
|
132
|
+
|
|
133
|
+
class ${className} extends Model {
|
|
134
|
+
static table = '${tableName}';
|
|
135
|
+
|
|
136
|
+
static schema = {
|
|
137
|
+
name: { type: 'string', required: true, maxLength: 255 },
|
|
138
|
+
email: { type: 'string', required: true },
|
|
139
|
+
// Add more fields here
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Custom methods
|
|
143
|
+
fullName() {
|
|
144
|
+
return this.name;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = ${className};
|
|
149
|
+
`;
|
|
150
|
+
writeFile(filePath, content);
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
middleware(name, cwd) {
|
|
154
|
+
const filePath = path.join(cwd, 'middleware', `${name}.js`);
|
|
155
|
+
const content = `/**
|
|
156
|
+
* Middleware: ${name}
|
|
157
|
+
*
|
|
158
|
+
* Usage in app.js:
|
|
159
|
+
* const ${name} = require('./middleware/${name}');
|
|
160
|
+
* app.use(${name});
|
|
161
|
+
* // or on specific routes:
|
|
162
|
+
* app.get('/protected', ${name}, handler);
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
module.exports = function ${name}(req, res) {
|
|
166
|
+
// Your middleware logic here
|
|
167
|
+
// Return false to stop the request
|
|
168
|
+
// Return nothing (undefined) to continue to next middleware
|
|
169
|
+
|
|
170
|
+
console.log('[${name}]', req.method, req.url);
|
|
171
|
+
};
|
|
172
|
+
`;
|
|
173
|
+
writeFile(filePath, content);
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
migration(name, cwd) {
|
|
177
|
+
const timestamp = Date.now();
|
|
178
|
+
const filePath = path.join(cwd, 'database', 'migrations', `${timestamp}_${name}.js`);
|
|
179
|
+
const tableName = name.replace(/^create_/, '').replace(/_table$/, '');
|
|
180
|
+
const content = `/**
|
|
181
|
+
* Migration: ${name}
|
|
182
|
+
* Created: ${new Date().toISOString()}
|
|
183
|
+
*/
|
|
184
|
+
|
|
185
|
+
module.exports = {
|
|
186
|
+
async up(db) {
|
|
187
|
+
await db.query(\`
|
|
188
|
+
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
189
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
190
|
+
name VARCHAR(255) NOT NULL,
|
|
191
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
192
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
193
|
+
)
|
|
194
|
+
\`);
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
async down(db) {
|
|
198
|
+
await db.query('DROP TABLE IF EXISTS ${tableName}');
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
`;
|
|
202
|
+
writeFile(filePath, content);
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
seeder(name, cwd) {
|
|
206
|
+
const filePath = path.join(cwd, 'database', 'seeders', `${name}.js`);
|
|
207
|
+
const content = `/**
|
|
208
|
+
* Seeder: ${name}
|
|
209
|
+
* Run: volt db:seed
|
|
210
|
+
*/
|
|
211
|
+
|
|
212
|
+
module.exports = {
|
|
213
|
+
async run(db) {
|
|
214
|
+
// await db.query("INSERT INTO table_name (col) VALUES (?)", ['value']);
|
|
215
|
+
console.log('Seeder ${name} executed');
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
`;
|
|
219
|
+
writeFile(filePath, content);
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
module.exports = function generate(args) {
|
|
224
|
+
const type = args[0];
|
|
225
|
+
const name = args[1];
|
|
226
|
+
|
|
227
|
+
if (!type) {
|
|
228
|
+
console.log(`${c.bold}Usage:${c.reset} volt generate <type> <name>\n`);
|
|
229
|
+
console.log(`${c.bold}Types:${c.reset}`);
|
|
230
|
+
console.log(` ${c.cyan}page${c.reset} Generate a page with view`);
|
|
231
|
+
console.log(` ${c.cyan}api${c.reset} Generate an API route`);
|
|
232
|
+
console.log(` ${c.cyan}component${c.reset} Generate a component`);
|
|
233
|
+
console.log(` ${c.cyan}model${c.reset} Generate a database model`);
|
|
234
|
+
console.log(` ${c.cyan}middleware${c.reset} Generate middleware`);
|
|
235
|
+
console.log(` ${c.cyan}migration${c.reset} Generate a database migration`);
|
|
236
|
+
console.log(` ${c.cyan}seeder${c.reset} Generate a database seeder`);
|
|
237
|
+
process.exit(0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!name) {
|
|
241
|
+
console.error(`${c.red}Error: Please specify a name.${c.reset}`);
|
|
242
|
+
console.log(` ${c.cyan}volt generate ${type} <name>${c.reset}`);
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const generator = generators[type];
|
|
247
|
+
if (!generator) {
|
|
248
|
+
console.error(`${c.red}Error: Unknown generator type "${type}".${c.reset}`);
|
|
249
|
+
console.log(`Available: ${Object.keys(generators).join(', ')}`);
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
generator(name, process.cwd());
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
function writeFile(filePath, content) {
|
|
257
|
+
const dir = path.dirname(filePath);
|
|
258
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
259
|
+
|
|
260
|
+
if (fs.existsSync(filePath)) {
|
|
261
|
+
console.log(` ${c.yellow}⚠ Already exists:${c.reset} ${path.relative(process.cwd(), filePath)}`);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
fs.writeFileSync(filePath, content);
|
|
266
|
+
console.log(` ${c.green}✓ Created:${c.reset} ${path.relative(process.cwd(), filePath)}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function capitalize(str) {
|
|
270
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function singularize(str) {
|
|
274
|
+
if (str.endsWith('ies')) return str.slice(0, -3) + 'y';
|
|
275
|
+
if (str.endsWith('ses') || str.endsWith('xes')) return str.slice(0, -2);
|
|
276
|
+
if (str.endsWith('s') && !str.endsWith('ss')) return str.slice(0, -1);
|
|
277
|
+
return str;
|
|
278
|
+
}
|
package/src/cli/lint.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoltJS CLI — `volt lint`
|
|
3
|
+
* Basic linting for VoltJS projects.
|
|
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 lint(args) {
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
console.log(`${c.cyan}${c.bold}⚡ VoltJS Lint${c.reset}\n`);
|
|
19
|
+
|
|
20
|
+
const issues = [];
|
|
21
|
+
const scannedFiles = [];
|
|
22
|
+
const dirs = ['pages', 'api', 'models', 'middleware', 'components'];
|
|
23
|
+
|
|
24
|
+
// Scan root JS files
|
|
25
|
+
for (const file of ['app.js', 'index.js', 'server.js']) {
|
|
26
|
+
const filePath = path.join(cwd, file);
|
|
27
|
+
if (fs.existsSync(filePath)) {
|
|
28
|
+
lintFile(filePath, issues, scannedFiles);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Scan directories
|
|
33
|
+
for (const dir of dirs) {
|
|
34
|
+
const dirPath = path.join(cwd, dir);
|
|
35
|
+
if (fs.existsSync(dirPath)) {
|
|
36
|
+
scanAndLint(dirPath, issues, scannedFiles);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check config
|
|
41
|
+
checkConfig(cwd, issues);
|
|
42
|
+
|
|
43
|
+
// Report
|
|
44
|
+
if (issues.length === 0) {
|
|
45
|
+
console.log(` ${c.green}✓ No issues found!${c.reset} (${scannedFiles.length} files scanned)`);
|
|
46
|
+
} else {
|
|
47
|
+
const warnings = issues.filter(i => i.severity === 'warning');
|
|
48
|
+
const errors = issues.filter(i => i.severity === 'error');
|
|
49
|
+
|
|
50
|
+
for (const issue of issues) {
|
|
51
|
+
const icon = issue.severity === 'error' ? `${c.red}✗` : `${c.yellow}⚠`;
|
|
52
|
+
const relative = path.relative(cwd, issue.file);
|
|
53
|
+
console.log(` ${icon} ${relative}${issue.line ? `:${issue.line}` : ''}${c.reset} — ${issue.message}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(`\n ${c.dim}${scannedFiles.length} files scanned${c.reset}`);
|
|
57
|
+
if (errors.length > 0) console.log(` ${c.red}${errors.length} error(s)${c.reset}`);
|
|
58
|
+
if (warnings.length > 0) console.log(` ${c.yellow}${warnings.length} warning(s)${c.reset}`);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function scanAndLint(dir, issues, scannedFiles) {
|
|
63
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
64
|
+
for (const entry of entries) {
|
|
65
|
+
const fullPath = path.join(dir, entry.name);
|
|
66
|
+
if (entry.isDirectory()) {
|
|
67
|
+
scanAndLint(fullPath, issues, scannedFiles);
|
|
68
|
+
} else if (entry.name.endsWith('.js')) {
|
|
69
|
+
lintFile(fullPath, issues, scannedFiles);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function lintFile(filePath, issues, scannedFiles) {
|
|
75
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
76
|
+
const lines = content.split('\n');
|
|
77
|
+
scannedFiles.push(filePath);
|
|
78
|
+
|
|
79
|
+
// Check for syntax errors
|
|
80
|
+
try {
|
|
81
|
+
new Function(content);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
if (err instanceof SyntaxError) {
|
|
84
|
+
issues.push({
|
|
85
|
+
file: filePath,
|
|
86
|
+
line: null,
|
|
87
|
+
severity: 'error',
|
|
88
|
+
message: `Syntax error: ${err.message}`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < lines.length; i++) {
|
|
94
|
+
const line = lines[i];
|
|
95
|
+
const lineNum = i + 1;
|
|
96
|
+
|
|
97
|
+
// console.log in production code
|
|
98
|
+
if (line.match(/console\.(log|dir|table)\s*\(/) && !filePath.includes('test')) {
|
|
99
|
+
issues.push({
|
|
100
|
+
file: filePath,
|
|
101
|
+
line: lineNum,
|
|
102
|
+
severity: 'warning',
|
|
103
|
+
message: 'Consider removing console.log in production code',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// eval usage
|
|
108
|
+
if (line.match(/\beval\s*\(/)) {
|
|
109
|
+
issues.push({
|
|
110
|
+
file: filePath,
|
|
111
|
+
line: lineNum,
|
|
112
|
+
severity: 'error',
|
|
113
|
+
message: 'Avoid using eval() — security risk',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Hardcoded secrets
|
|
118
|
+
if (line.match(/(password|secret|api_key|apikey|token)\s*[:=]\s*['"][^'"]{8,}['"]/i)) {
|
|
119
|
+
if (!line.includes('process.env') && !filePath.includes('.env')) {
|
|
120
|
+
issues.push({
|
|
121
|
+
file: filePath,
|
|
122
|
+
line: lineNum,
|
|
123
|
+
severity: 'error',
|
|
124
|
+
message: 'Possible hardcoded secret detected — use environment variables',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Very long lines
|
|
130
|
+
if (line.length > 200) {
|
|
131
|
+
issues.push({
|
|
132
|
+
file: filePath,
|
|
133
|
+
line: lineNum,
|
|
134
|
+
severity: 'warning',
|
|
135
|
+
message: `Line too long (${line.length} chars)`,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function checkConfig(cwd, issues) {
|
|
142
|
+
// Missing .env
|
|
143
|
+
if (!fs.existsSync(path.join(cwd, '.env'))) {
|
|
144
|
+
issues.push({
|
|
145
|
+
file: path.join(cwd, '.env'),
|
|
146
|
+
severity: 'warning',
|
|
147
|
+
message: 'No .env file found — create one for environment variables',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// .env in .gitignore
|
|
152
|
+
const gitignore = path.join(cwd, '.gitignore');
|
|
153
|
+
if (fs.existsSync(gitignore)) {
|
|
154
|
+
const content = fs.readFileSync(gitignore, 'utf-8');
|
|
155
|
+
if (!content.includes('.env')) {
|
|
156
|
+
issues.push({
|
|
157
|
+
file: gitignore,
|
|
158
|
+
severity: 'error',
|
|
159
|
+
message: '.env is not in .gitignore — secrets may be committed',
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Missing volt.config.js
|
|
165
|
+
if (!fs.existsSync(path.join(cwd, 'volt.config.js'))) {
|
|
166
|
+
issues.push({
|
|
167
|
+
file: path.join(cwd, 'volt.config.js'),
|
|
168
|
+
severity: 'warning',
|
|
169
|
+
message: 'No volt.config.js — using defaults',
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoltJS CLI — `volt routes`
|
|
3
|
+
* Lists all registered routes in the application.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
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
|
+
magenta: '\x1b[35m',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const METHOD_COLORS = {
|
|
18
|
+
GET: c.green,
|
|
19
|
+
POST: c.cyan,
|
|
20
|
+
PUT: c.yellow,
|
|
21
|
+
PATCH: c.yellow,
|
|
22
|
+
DELETE: c.red,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
module.exports = function routes(args) {
|
|
26
|
+
const cwd = process.cwd();
|
|
27
|
+
|
|
28
|
+
console.log(`${c.cyan}${c.bold}⚡ VoltJS Routes${c.reset}\n`);
|
|
29
|
+
|
|
30
|
+
const allRoutes = [];
|
|
31
|
+
|
|
32
|
+
// Scan pages directory
|
|
33
|
+
const pagesDir = path.join(cwd, 'pages');
|
|
34
|
+
if (fs.existsSync(pagesDir)) {
|
|
35
|
+
scanDir(pagesDir, pagesDir, '', allRoutes, 'page');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Scan api directory
|
|
39
|
+
const apiDir = path.join(cwd, 'api');
|
|
40
|
+
if (fs.existsSync(apiDir)) {
|
|
41
|
+
scanDir(apiDir, apiDir, '/api', allRoutes, 'api');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Try to load app and discover programmatic routes
|
|
45
|
+
try {
|
|
46
|
+
// We'll just display file-based routes since loading the app might start the server
|
|
47
|
+
} catch { }
|
|
48
|
+
|
|
49
|
+
if (allRoutes.length === 0) {
|
|
50
|
+
console.log(` ${c.dim}No routes found. Create files in pages/ or api/ directories.${c.reset}`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Print table header
|
|
55
|
+
const methodWidth = 8;
|
|
56
|
+
const pathWidth = 40;
|
|
57
|
+
const sourceWidth = 30;
|
|
58
|
+
|
|
59
|
+
console.log(
|
|
60
|
+
` ${c.bold}${'METHOD'.padEnd(methodWidth)}${'PATH'.padEnd(pathWidth)}${'SOURCE'.padEnd(sourceWidth)}${c.reset}`
|
|
61
|
+
);
|
|
62
|
+
console.log(` ${'─'.repeat(methodWidth + pathWidth + sourceWidth)}`);
|
|
63
|
+
|
|
64
|
+
for (const route of allRoutes) {
|
|
65
|
+
const methodColor = METHOD_COLORS[route.method] || c.dim;
|
|
66
|
+
console.log(
|
|
67
|
+
` ${methodColor}${route.method.padEnd(methodWidth)}${c.reset}${route.path.padEnd(pathWidth)}${c.dim}${route.source.padEnd(sourceWidth)}${c.reset}`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log(`\n ${c.dim}Total: ${allRoutes.length} routes${c.reset}\n`);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function scanDir(baseDir, currentDir, prefix, routes, type) {
|
|
75
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
76
|
+
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
if (entry.isDirectory()) {
|
|
79
|
+
scanDir(baseDir, path.join(currentDir, entry.name), `${prefix}/${entry.name}`, routes, type);
|
|
80
|
+
} else if (entry.name.endsWith('.js')) {
|
|
81
|
+
const routeName = entry.name.replace('.js', '');
|
|
82
|
+
const routePath = routeName === 'index'
|
|
83
|
+
? (prefix || '/')
|
|
84
|
+
: `${prefix}/${routeName}`;
|
|
85
|
+
|
|
86
|
+
const relativePath = path.relative(process.cwd(), path.join(currentDir, entry.name));
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const mod = require(path.join(currentDir, entry.name));
|
|
90
|
+
const methods = ['get', 'post', 'put', 'patch', 'delete'];
|
|
91
|
+
|
|
92
|
+
for (const method of methods) {
|
|
93
|
+
if (mod[method] || mod[method.toUpperCase()]) {
|
|
94
|
+
routes.push({
|
|
95
|
+
method: method.toUpperCase(),
|
|
96
|
+
path: routePath,
|
|
97
|
+
source: relativePath,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// If it exports show/update/destroy, add /:id routes
|
|
103
|
+
if (mod.show) {
|
|
104
|
+
routes.push({ method: 'GET', path: `${routePath}/:id`, source: relativePath });
|
|
105
|
+
}
|
|
106
|
+
if (mod.update) {
|
|
107
|
+
routes.push({ method: 'PUT', path: `${routePath}/:id`, source: relativePath });
|
|
108
|
+
}
|
|
109
|
+
if (mod.destroy) {
|
|
110
|
+
routes.push({ method: 'DELETE', path: `${routePath}/:id`, source: relativePath });
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// If we can't load the module, assume GET
|
|
114
|
+
routes.push({ method: 'GET', path: routePath, source: relativePath });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
package/src/cli/start.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoltJS CLI — `volt start`
|
|
3
|
+
* Starts the production server.
|
|
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', cyan: '\x1b[36m',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
module.exports = function start(args) {
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
const entry = findEntry(cwd);
|
|
19
|
+
|
|
20
|
+
if (!entry) {
|
|
21
|
+
console.error(`${c.red}Error: No app entry point found (app.js, index.js, or server.js).${c.reset}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log(`${c.cyan}${c.bold}⚡ VoltJS Production Server${c.reset}`);
|
|
26
|
+
console.log(`${c.dim}Starting ${path.basename(entry)}...${c.reset}\n`);
|
|
27
|
+
|
|
28
|
+
// Set production environment
|
|
29
|
+
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
|
|
30
|
+
|
|
31
|
+
// Load the app
|
|
32
|
+
require(entry);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function findEntry(cwd) {
|
|
36
|
+
const candidates = ['app.js', 'index.js', 'server.js', 'src/app.js', 'src/index.js'];
|
|
37
|
+
for (const file of candidates) {
|
|
38
|
+
const fullPath = path.join(cwd, file);
|
|
39
|
+
if (fs.existsSync(fullPath)) return fullPath;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|