webspresso 0.0.10 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -73,6 +73,8 @@ webspresso new my-app --no-tailwind
73
73
  - Asks if you want to install in the current directory
74
74
  - If current directory is not empty, shows a warning
75
75
  - Prompts for project name (defaults to current folder name)
76
+ - Asks if you will use a database (SQLite, PostgreSQL, or MySQL)
77
+ - If yes, adds the appropriate driver to `package.json` and creates `webspresso.db.js` config
76
78
  - After project creation, asks if you want to install dependencies
77
79
  - If yes, runs `npm install` and `npm run build:css`
78
80
  - Then asks if you want to start the development server
@@ -96,6 +98,43 @@ webspresso new my-app
96
98
 
97
99
  **Note:** When dev server starts with Tailwind CSS, it automatically runs `watch:css` in the background to watch for CSS changes.
98
100
 
101
+ **Database Selection:**
102
+ During project creation, you'll be asked if you want to use a database:
103
+ - **SQLite** (better-sqlite3) - Recommended for development and small projects
104
+ - **PostgreSQL** (pg) - For production applications
105
+ - **MySQL** (mysql2) - Alternative SQL database
106
+
107
+ If you select a database:
108
+ - The appropriate driver is added to `package.json` dependencies
109
+ - `webspresso.db.js` config file is created with proper settings
110
+ - `migrations/` directory is created
111
+ - `models/` directory is created
112
+ - `DATABASE_URL` is added to `.env.example` with a template
113
+
114
+ **Seed Data Generation:**
115
+ After selecting a database, you'll be asked if you want to generate seed data:
116
+ - If yes, `@faker-js/faker` is added to dependencies
117
+ - `seeds/` directory is created with `seeds/index.js`
118
+ - `npm run seed` script is added to `package.json`
119
+ - The seed script automatically detects models in `models/` directory and generates fake data based on their schemas
120
+
121
+ To run seeds after creating models:
122
+ ```bash
123
+ npm run seed
124
+ ```
125
+
126
+ The seed script will:
127
+ - Load all models from `models/` directory
128
+ - Generate 10 fake records per model (by default)
129
+ - Use smart field detection based on column names (email, name, title, etc.)
130
+
131
+ You can always add database support later by:
132
+ 1. Installing the driver: `npm install better-sqlite3` (or `pg`, `mysql2`)
133
+ 2. Creating `webspresso.db.js` config file
134
+ 3. Adding `DATABASE_URL` to your `.env` file
135
+ 4. Creating `models/` directory and defining your models
136
+ 5. Optionally adding seed support: `npm install @faker-js/faker` and creating `seeds/index.js`
137
+
99
138
  Options:
100
139
  - `-i, --install` - Auto run `npm install` and `npm run build:css` (non-interactive)
101
140
  - `--no-tailwind` - Skip Tailwind CSS setup
@@ -1040,6 +1079,32 @@ npm install better-sqlite3
1040
1079
 
1041
1080
  ### Database Seeding
1042
1081
 
1082
+ **CLI Command:**
1083
+
1084
+ The easiest way to seed your database is using the CLI command:
1085
+
1086
+ ```bash
1087
+ # Run seeds (requires seeds/index.js)
1088
+ webspresso seed
1089
+
1090
+ # Setup seed files if they don't exist
1091
+ webspresso seed --setup
1092
+
1093
+ # Use custom database config
1094
+ webspresso seed --config ./custom-db-config.js
1095
+
1096
+ # Use different environment
1097
+ webspresso seed --env production
1098
+ ```
1099
+
1100
+ The `webspresso seed` command:
1101
+ - Automatically loads all models from `models/` directory
1102
+ - Generates fake data based on model schemas
1103
+ - Creates 10 records per model by default
1104
+ - Uses smart field detection for appropriate fake data
1105
+
1106
+ **Manual Setup:**
1107
+
1043
1108
  Generate fake data for testing and development using `@faker-js/faker`:
1044
1109
 
1045
1110
  ```bash
package/bin/webspresso.js CHANGED
@@ -121,6 +121,43 @@ program
121
121
 
122
122
  console.log(`\nšŸš€ Creating new Webspresso project: ${projectName}\n`);
123
123
 
124
+ // Ask about database
125
+ const { useDatabase, databaseType } = await inquirer.prompt([
126
+ {
127
+ type: 'confirm',
128
+ name: 'useDatabase',
129
+ message: 'Will you use a database?',
130
+ default: false
131
+ },
132
+ {
133
+ type: 'list',
134
+ name: 'databaseType',
135
+ message: 'Which database?',
136
+ choices: [
137
+ { name: 'SQLite (better-sqlite3)', value: 'better-sqlite3' },
138
+ { name: 'PostgreSQL (pg)', value: 'pg' },
139
+ { name: 'MySQL (mysql2)', value: 'mysql2' },
140
+ { name: 'None', value: null }
141
+ ],
142
+ default: 'better-sqlite3',
143
+ when: (answers) => answers.useDatabase
144
+ }
145
+ ]);
146
+
147
+ // Ask about seed data if database is selected
148
+ let useSeed = false;
149
+ if (useDatabase && databaseType) {
150
+ const { generateSeed } = await inquirer.prompt([
151
+ {
152
+ type: 'confirm',
153
+ name: 'generateSeed',
154
+ message: 'Generate seed data based on existing models?',
155
+ default: false
156
+ }
157
+ ]);
158
+ useSeed = generateSeed;
159
+ }
160
+
124
161
  // Create directory structure (skip root if using current dir)
125
162
  if (!useCurrentDir) {
126
163
  fs.mkdirSync(projectPath, { recursive: true });
@@ -130,6 +167,11 @@ program
130
167
  fs.mkdirSync(path.join(projectPath, 'views'), { recursive: true });
131
168
  fs.mkdirSync(path.join(projectPath, 'public'), { recursive: true });
132
169
 
170
+ // Create models directory if database is selected
171
+ if (useDatabase && databaseType) {
172
+ fs.mkdirSync(path.join(projectPath, 'models'), { recursive: true });
173
+ }
174
+
133
175
  // Create package.json
134
176
  const packageJson = {
135
177
  name: projectName,
@@ -146,6 +188,23 @@ program
146
188
  }
147
189
  };
148
190
 
191
+ // Add database driver if selected
192
+ if (useDatabase && databaseType) {
193
+ const dbDrivers = {
194
+ 'better-sqlite3': '^9.0.0',
195
+ 'pg': '^8.0.0',
196
+ 'mysql2': '^3.0.0'
197
+ };
198
+
199
+ packageJson.dependencies[databaseType] = dbDrivers[databaseType];
200
+ }
201
+
202
+ // Add faker if seed is selected
203
+ if (useSeed) {
204
+ packageJson.dependencies['@faker-js/faker'] = '^8.0.0';
205
+ packageJson.scripts.seed = 'node seeds/index.js';
206
+ }
207
+
149
208
  fs.writeFileSync(
150
209
  path.join(projectPath, 'package.json'),
151
210
  JSON.stringify(packageJson, null, 2) + '\n'
@@ -172,15 +231,186 @@ app.listen(PORT, () => {
172
231
  fs.writeFileSync(path.join(projectPath, 'server.js'), serverJs);
173
232
 
174
233
  // Create .env.example
175
- const envExample = `PORT=3000
234
+ let envExample = `PORT=3000
176
235
  NODE_ENV=development
177
236
  DEFAULT_LOCALE=en
178
237
  SUPPORTED_LOCALES=en,tr
179
238
  BASE_URL=http://localhost:3000
180
239
  `;
181
240
 
241
+ if (useDatabase && databaseType) {
242
+ if (databaseType === 'better-sqlite3') {
243
+ envExample += `DATABASE_URL=sqlite:./database.sqlite
244
+ `;
245
+ } else if (databaseType === 'pg') {
246
+ envExample += `DATABASE_URL=postgresql://user:password@localhost:5432/dbname
247
+ `;
248
+ } else if (databaseType === 'mysql2') {
249
+ envExample += `DATABASE_URL=mysql://user:password@localhost:3306/dbname
250
+ `;
251
+ }
252
+ }
253
+
182
254
  fs.writeFileSync(path.join(projectPath, '.env.example'), envExample);
183
255
 
256
+ // Create database config if database is selected
257
+ if (useDatabase && databaseType) {
258
+ let dbConfig = '';
259
+
260
+ if (databaseType === 'better-sqlite3') {
261
+ dbConfig = `module.exports = {
262
+ client: 'better-sqlite3',
263
+ connection: {
264
+ filename: process.env.DATABASE_URL?.replace('sqlite:', '') || './database.sqlite'
265
+ },
266
+ migrations: {
267
+ directory: './migrations',
268
+ tableName: 'knex_migrations',
269
+ },
270
+ useNullAsDefault: true
271
+ };
272
+ `;
273
+ } else if (databaseType === 'pg') {
274
+ dbConfig = `module.exports = {
275
+ client: 'pg',
276
+ connection: process.env.DATABASE_URL,
277
+ migrations: {
278
+ directory: './migrations',
279
+ tableName: 'knex_migrations',
280
+ },
281
+ pool: {
282
+ min: 2,
283
+ max: 10
284
+ }
285
+ };
286
+ `;
287
+ } else if (databaseType === 'mysql2') {
288
+ dbConfig = `module.exports = {
289
+ client: 'mysql2',
290
+ connection: process.env.DATABASE_URL,
291
+ migrations: {
292
+ directory: './migrations',
293
+ tableName: 'knex_migrations',
294
+ },
295
+ pool: {
296
+ min: 2,
297
+ max: 10
298
+ }
299
+ };
300
+ `;
301
+ }
302
+
303
+ fs.writeFileSync(path.join(projectPath, 'webspresso.db.js'), dbConfig);
304
+
305
+ // Create migrations directory
306
+ fs.mkdirSync(path.join(projectPath, 'migrations'), { recursive: true });
307
+ }
308
+
309
+ // Create seed files if seed is selected
310
+ if (useSeed) {
311
+ fs.mkdirSync(path.join(projectPath, 'seeds'), { recursive: true });
312
+
313
+ const seedIndex = `require('dotenv').config();
314
+ const { faker } = require('@faker-js/faker');
315
+ const path = require('path');
316
+ const fs = require('fs');
317
+ const { createDatabase, getAllModels } = require('webspresso/orm');
318
+ const dbConfig = require('./webspresso.db.js');
319
+
320
+ const db = createDatabase(dbConfig);
321
+ const seeder = db.seeder(faker);
322
+
323
+ /**
324
+ * Load all models from models/ directory
325
+ */
326
+ function loadModels() {
327
+ const modelsDir = path.join(__dirname, 'models');
328
+
329
+ if (!fs.existsSync(modelsDir)) {
330
+ return [];
331
+ }
332
+
333
+ const modelFiles = fs.readdirSync(modelsDir)
334
+ .filter(file => file.endsWith('.js') && !file.startsWith('_'));
335
+
336
+ const loadedModels = [];
337
+ for (const file of modelFiles) {
338
+ try {
339
+ require(path.join(modelsDir, file));
340
+ loadedModels.push(file);
341
+ } catch (error) {
342
+ console.warn(\`āš ļø Failed to load model from \${file}:\`, error.message);
343
+ }
344
+ }
345
+
346
+ return loadedModels;
347
+ }
348
+
349
+ /**
350
+ * Seed database with fake data
351
+ * This script automatically detects models in the models/ directory
352
+ * and generates seed data based on their schemas.
353
+ */
354
+ async function runSeeds() {
355
+ try {
356
+ console.log('🌱 Starting seed process...');
357
+
358
+ // Load all models
359
+ const loadedFiles = loadModels();
360
+
361
+ if (loadedFiles.length === 0) {
362
+ console.log('āš ļø No model files found in models/ directory.');
363
+ console.log(' Create models first, then run: npm run seed');
364
+ await db.knex.destroy();
365
+ return;
366
+ }
367
+
368
+ // Get all registered models
369
+ const models = getAllModels();
370
+
371
+ if (models.size === 0) {
372
+ console.log('āš ļø No models registered. Make sure your model files export models using defineModel().');
373
+ await db.knex.destroy();
374
+ return;
375
+ }
376
+
377
+ console.log(\`šŸ“¦ Found \${models.size} model(s):\`);
378
+ for (const [name] of models) {
379
+ console.log(\` - \${name}\`);
380
+ }
381
+
382
+ // Seed each model
383
+ const results = {};
384
+ for (const [modelName, model] of models) {
385
+ console.log(\`\\n🌱 Seeding \${modelName}...\`);
386
+
387
+ // Default count: 10 records per model
388
+ const count = 10;
389
+ const records = await seeder.seed(modelName, count);
390
+ results[modelName] = records.length;
391
+
392
+ console.log(\`āœ… Created \${records.length} \${modelName} record(s)\`);
393
+ }
394
+
395
+ console.log(\`\\n✨ Seed completed! Created:\`);
396
+ for (const [modelName, count] of Object.entries(results)) {
397
+ console.log(\` - \${count} \${modelName} record(s)\`);
398
+ }
399
+
400
+ await db.knex.destroy();
401
+ } catch (error) {
402
+ console.error('āŒ Seed failed:', error);
403
+ await db.knex.destroy().catch(() => {});
404
+ process.exit(1);
405
+ }
406
+ }
407
+
408
+ runSeeds();
409
+ `;
410
+
411
+ fs.writeFileSync(path.join(projectPath, 'seeds', 'index.js'), seedIndex);
412
+ }
413
+
184
414
  // Create .gitignore
185
415
  const gitignore = `node_modules/
186
416
  .env
@@ -1242,6 +1472,174 @@ exports.down = function(knex) {
1242
1472
  `;
1243
1473
  }
1244
1474
 
1475
+ // seed command
1476
+ program
1477
+ .command('seed')
1478
+ .description('Run database seeders to populate database with fake data')
1479
+ .option('-c, --config <path>', 'Path to database config file')
1480
+ .option('-e, --env <environment>', 'Environment (development, production)', 'development')
1481
+ .option('--setup', 'Setup seed files if they don\'t exist')
1482
+ .action(async (options) => {
1483
+ // Check if it's a Webspresso project
1484
+ if (!fs.existsSync(path.join(process.cwd(), 'server.js')) &&
1485
+ !fs.existsSync(path.join(process.cwd(), 'pages'))) {
1486
+ console.error('āŒ Not a Webspresso project! Run this command in your project directory.');
1487
+ process.exit(1);
1488
+ }
1489
+
1490
+ // Check for faker
1491
+ let faker;
1492
+ try {
1493
+ faker = require('@faker-js/faker');
1494
+ } catch {
1495
+ console.error('āŒ @faker-js/faker not installed.');
1496
+ console.log(' Install it with: npm install @faker-js/faker');
1497
+ process.exit(1);
1498
+ }
1499
+
1500
+ // Load database config
1501
+ const { config, path: configPath } = loadDbConfig(options.config);
1502
+ console.log(`\nšŸ“¦ Using config: ${configPath}`);
1503
+ console.log(` Environment: ${options.env}\n`);
1504
+
1505
+ // Check if models directory exists
1506
+ const modelsDir = path.join(process.cwd(), 'models');
1507
+ if (!fs.existsSync(modelsDir)) {
1508
+ console.error('āŒ models/ directory not found.');
1509
+ console.log(' Create models first, then run: webspresso seed');
1510
+ process.exit(1);
1511
+ }
1512
+
1513
+ // Check if seeds directory exists
1514
+ const seedsDir = path.join(process.cwd(), 'seeds');
1515
+ const seedIndexPath = path.join(seedsDir, 'index.js');
1516
+
1517
+ if (!fs.existsSync(seedsDir) || !fs.existsSync(seedIndexPath)) {
1518
+ if (options.setup) {
1519
+ console.log('šŸ“ Setting up seed files...\n');
1520
+
1521
+ // Create seeds directory
1522
+ if (!fs.existsSync(seedsDir)) {
1523
+ fs.mkdirSync(seedsDir, { recursive: true });
1524
+ }
1525
+
1526
+ // Create seed index file
1527
+ const seedIndex = `require('dotenv').config();
1528
+ const { faker } = require('@faker-js/faker');
1529
+ const path = require('path');
1530
+ const fs = require('fs');
1531
+ const { createDatabase, getAllModels } = require('webspresso/orm');
1532
+ const dbConfig = require('../webspresso.db.js');
1533
+
1534
+ const db = createDatabase(dbConfig);
1535
+ const seeder = db.seeder(faker);
1536
+
1537
+ /**
1538
+ * Load all models from models/ directory
1539
+ */
1540
+ function loadModels() {
1541
+ const modelsDir = path.join(__dirname, '..', 'models');
1542
+
1543
+ if (!fs.existsSync(modelsDir)) {
1544
+ return [];
1545
+ }
1546
+
1547
+ const modelFiles = fs.readdirSync(modelsDir)
1548
+ .filter(file => file.endsWith('.js') && !file.startsWith('_'));
1549
+
1550
+ const loadedModels = [];
1551
+ for (const file of modelFiles) {
1552
+ try {
1553
+ require(path.join(modelsDir, file));
1554
+ loadedModels.push(file);
1555
+ } catch (error) {
1556
+ console.warn(\`āš ļø Failed to load model from \${file}:\`, error.message);
1557
+ }
1558
+ }
1559
+
1560
+ return loadedModels;
1561
+ }
1562
+
1563
+ /**
1564
+ * Seed database with fake data
1565
+ * This script automatically detects models in the models/ directory
1566
+ * and generates seed data based on their schemas.
1567
+ */
1568
+ async function runSeeds() {
1569
+ try {
1570
+ console.log('🌱 Starting seed process...');
1571
+
1572
+ // Load all models
1573
+ const loadedFiles = loadModels();
1574
+
1575
+ if (loadedFiles.length === 0) {
1576
+ console.log('āš ļø No model files found in models/ directory.');
1577
+ console.log(' Create models first, then run: webspresso seed');
1578
+ await db.knex.destroy();
1579
+ return;
1580
+ }
1581
+
1582
+ // Get all registered models
1583
+ const models = getAllModels();
1584
+
1585
+ if (models.size === 0) {
1586
+ console.log('āš ļø No models registered. Make sure your model files export models using defineModel().');
1587
+ await db.knex.destroy();
1588
+ return;
1589
+ }
1590
+
1591
+ console.log(\`šŸ“¦ Found \${models.size} model(s):\`);
1592
+ for (const [name] of models) {
1593
+ console.log(\` - \${name}\`);
1594
+ }
1595
+
1596
+ // Seed each model
1597
+ const results = {};
1598
+ for (const [modelName, model] of models) {
1599
+ console.log(\`\\n🌱 Seeding \${modelName}...\`);
1600
+
1601
+ // Default count: 10 records per model
1602
+ const count = 10;
1603
+ const records = await seeder.seed(modelName, count);
1604
+ results[modelName] = records.length;
1605
+
1606
+ console.log(\`āœ… Created \${records.length} \${modelName} record(s)\`);
1607
+ }
1608
+
1609
+ console.log(\`\\n✨ Seed completed! Created:\`);
1610
+ for (const [modelName, count] of Object.entries(results)) {
1611
+ console.log(\` - \${count} \${modelName} record(s)\`);
1612
+ }
1613
+
1614
+ await db.knex.destroy();
1615
+ } catch (error) {
1616
+ console.error('āŒ Seed failed:', error);
1617
+ await db.knex.destroy().catch(() => {});
1618
+ process.exit(1);
1619
+ }
1620
+ }
1621
+
1622
+ runSeeds();
1623
+ `;
1624
+
1625
+ fs.writeFileSync(seedIndexPath, seedIndex);
1626
+ console.log('āœ… Seed files created!\n');
1627
+ } else {
1628
+ console.error('āŒ seeds/index.js not found.');
1629
+ console.log(' Run with --setup flag to create seed files: webspresso seed --setup');
1630
+ process.exit(1);
1631
+ }
1632
+ }
1633
+
1634
+ // Run the seed script
1635
+ try {
1636
+ require(seedIndexPath);
1637
+ } catch (error) {
1638
+ console.error('āŒ Failed to run seed:', error.message);
1639
+ process.exit(1);
1640
+ }
1641
+ });
1642
+
1245
1643
  // Parse arguments
1246
1644
  program.parse();
1247
1645
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webspresso",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "Minimal, production-ready SSR framework for Node.js with file-based routing, Nunjucks templating, built-in i18n, and CLI tooling",
5
5
  "main": "index.js",
6
6
  "bin": {