webspresso 0.0.14 → 0.0.16

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
@@ -1007,6 +1007,9 @@ webspresso db:make create_posts_table
1007
1007
 
1008
1008
  # Create migration from model (scaffolding)
1009
1009
  webspresso db:make create_users_table --model User
1010
+
1011
+ # Admin Panel Setup
1012
+ webspresso admin:setup # Create admin_users migration
1010
1013
  ```
1011
1014
 
1012
1015
  **Database Config File (`webspresso.db.js`):**
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Admin Setup Command
3
+ * Create admin_users table migration for admin panel
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { loadDbConfig } = require('../utils/db');
9
+ const { generateAdminUsersMigration } = require('../../plugins/admin-panel/migration-template');
10
+
11
+ function registerCommand(program) {
12
+ program
13
+ .command('admin:setup')
14
+ .description('Create admin_users table migration for admin panel')
15
+ .option('-c, --config <path>', 'Path to database config file')
16
+ .action(async (options) => {
17
+ try {
18
+ const { config, path: configPath } = loadDbConfig(options.config);
19
+ console.log(`\nšŸ“¦ Using config: ${configPath}\n`);
20
+
21
+ const migrationDir = config.migrations?.directory || './migrations';
22
+
23
+ // Ensure migrations directory exists
24
+ if (!fs.existsSync(migrationDir)) {
25
+ fs.mkdirSync(migrationDir, { recursive: true });
26
+ console.log(`Created directory: ${migrationDir}`);
27
+ }
28
+
29
+ // Check if migration already exists
30
+ const existingMigrations = fs.readdirSync(migrationDir)
31
+ .filter(f => f.includes('admin_users') || f.includes('admin-users'));
32
+
33
+ if (existingMigrations.length > 0) {
34
+ console.log(`āš ļø Admin users migration already exists:`);
35
+ existingMigrations.forEach(m => console.log(` - ${m}`));
36
+ console.log(`\n If you want to recreate it, delete the existing migration first.\n`);
37
+ return;
38
+ }
39
+
40
+ // Generate filename with timestamp
41
+ const now = new Date();
42
+ const timestamp = [
43
+ now.getFullYear(),
44
+ String(now.getMonth() + 1).padStart(2, '0'),
45
+ String(now.getDate()).padStart(2, '0'),
46
+ '_',
47
+ String(now.getHours()).padStart(2, '0'),
48
+ String(now.getMinutes()).padStart(2, '0'),
49
+ String(now.getSeconds()).padStart(2, '0'),
50
+ ].join('');
51
+
52
+ const filename = `${timestamp}_create_admin_users_table.js`;
53
+ const filepath = path.join(migrationDir, filename);
54
+
55
+ // Generate migration content
56
+ const content = generateAdminUsersMigration();
57
+
58
+ // Write migration file
59
+ fs.writeFileSync(filepath, content);
60
+
61
+ console.log(`āœ… Created admin users migration: ${filepath}\n`);
62
+ console.log(`šŸ“ Next steps:`);
63
+ console.log(` 1. Run migration: webspresso db:migrate`);
64
+ console.log(` 2. Create first admin user via admin panel setup page\n`);
65
+ } catch (err) {
66
+ console.error('āŒ Error:', err.message);
67
+ process.exit(1);
68
+ }
69
+ });
70
+ }
71
+
72
+ module.exports = { registerCommand };
package/bin/webspresso.js CHANGED
@@ -24,6 +24,7 @@ const { registerCommand: registerDbRollback } = require('./commands/db-rollback'
24
24
  const { registerCommand: registerDbStatus } = require('./commands/db-status');
25
25
  const { registerCommand: registerDbMake } = require('./commands/db-make');
26
26
  const { registerCommand: registerSeed } = require('./commands/seed');
27
+ const { registerCommand: registerAdminSetup } = require('./commands/admin-setup');
27
28
 
28
29
  registerNew(program);
29
30
  registerPage(program);
@@ -36,6 +37,7 @@ registerDbRollback(program);
36
37
  registerDbStatus(program);
37
38
  registerDbMake(program);
38
39
  registerSeed(program);
40
+ registerAdminSetup(program);
39
41
 
40
42
  // Parse arguments
41
43
  program.parse();
package/core/orm/index.js CHANGED
@@ -4,6 +4,7 @@
4
4
  * @module core/orm
5
5
  */
6
6
 
7
+ const path = require('path');
7
8
  const { createSchemaHelpers, extractColumnsFromSchema, getColumnMeta } = require('./schema-helpers');
8
9
  const { defineModel, getModel, getAllModels, hasModel, clearRegistry } = require('./model');
9
10
 
@@ -40,8 +41,105 @@ function createDatabase(config) {
40
41
  throw new Error('Knex is required for ORM. Install it with: npm install knex');
41
42
  }
42
43
 
44
+ // Check if database driver is available in project's node_modules
45
+ const client = config.client;
46
+ if (client) {
47
+ const driverMap = {
48
+ 'better-sqlite3': 'better-sqlite3',
49
+ 'pg': 'pg',
50
+ 'mysql2': 'mysql2',
51
+ 'mysql': 'mysql2',
52
+ };
53
+
54
+ const driverName = driverMap[client] || client;
55
+
56
+ // Try to resolve and pre-load the driver from project's node_modules
57
+ // This ensures Knex can find it when it tries to load it
58
+ let driverPath = null;
59
+ const resolvePaths = [
60
+ path.join(process.cwd(), 'node_modules'),
61
+ process.cwd(),
62
+ ];
63
+
64
+ for (const resolvePath of resolvePaths) {
65
+ try {
66
+ driverPath = require.resolve(driverName, { paths: [resolvePath] });
67
+ // Pre-load the driver so Knex can find it in Module._cache
68
+ require(driverPath);
69
+ break;
70
+ } catch (e) {
71
+ // Continue to next path
72
+ }
73
+ }
74
+
75
+ // If still not found, provide helpful error
76
+ if (!driverPath) {
77
+ const installCmd = driverName === 'better-sqlite3'
78
+ ? 'npm install better-sqlite3 --save'
79
+ : driverName === 'pg'
80
+ ? 'npm install pg --save'
81
+ : driverName === 'mysql2'
82
+ ? 'npm install mysql2 --save'
83
+ : `npm install ${driverName} --save`;
84
+
85
+ throw new Error(
86
+ `Database driver "${driverName}" is not installed in your project. ` +
87
+ `Please install it with: ${installCmd}\n` +
88
+ `Note: Database drivers are peer dependencies and must be installed in your project's node_modules, not globally.\n` +
89
+ `Current working directory: ${process.cwd()}`
90
+ );
91
+ }
92
+ }
93
+
43
94
  // Create Knex instance
44
- const knexInstance = knex(config);
95
+ // Knex will try to load the driver from its own node_modules
96
+ // We need to ensure the driver is available in the project's node_modules
97
+ let knexInstance;
98
+ try {
99
+ knexInstance = knex(config);
100
+ } catch (e) {
101
+ // If knex throws an error about missing driver, provide better message
102
+ if (e.message && (e.message.includes('Cannot find module') || e.message.includes('run') || e.message.includes('npm install'))) {
103
+ const driverName = config.client;
104
+ const installCmd = driverName === 'better-sqlite3'
105
+ ? 'npm install better-sqlite3 --save'
106
+ : driverName === 'pg'
107
+ ? 'npm install pg --save'
108
+ : driverName === 'mysql2'
109
+ ? 'npm install mysql2 --save'
110
+ : `npm install ${driverName} --save`;
111
+
112
+ // Check if driver exists in project's node_modules
113
+ let driverExists = false;
114
+ try {
115
+ require.resolve(driverName, { paths: [path.join(process.cwd(), 'node_modules')] });
116
+ driverExists = true;
117
+ } catch (resolveError) {
118
+ // Driver not found in project
119
+ }
120
+
121
+ if (!driverExists) {
122
+ throw new Error(
123
+ `Database driver "${driverName}" is not installed in your project. ` +
124
+ `Please install it with: ${installCmd}\n` +
125
+ `Note: Database drivers are peer dependencies and must be installed in your project's node_modules, not globally.\n` +
126
+ `Current working directory: ${process.cwd()}\n` +
127
+ `Make sure you run "${installCmd}" in your project directory.`
128
+ );
129
+ } else {
130
+ // Driver exists but Knex can't find it - this is a module resolution issue
131
+ throw new Error(
132
+ `Database driver "${driverName}" is installed but Knex cannot find it. ` +
133
+ `This might be a module resolution issue. Try:\n` +
134
+ `1. Delete node_modules and package-lock.json\n` +
135
+ `2. Run "npm install" again\n` +
136
+ `3. Make sure "${driverName}" is in your package.json dependencies\n` +
137
+ `Original error: ${e.message}`
138
+ );
139
+ }
140
+ }
141
+ throw e;
142
+ }
45
143
 
46
144
  // Create migration manager
47
145
  const migrationConfig = config.migrations || {};
package/index.js CHANGED
@@ -63,6 +63,9 @@ module.exports = {
63
63
  // ORM
64
64
  ...orm,
65
65
 
66
+ // Direct zdb export (for convenience)
67
+ zdb: orm.zdb,
68
+
66
69
  // Plugins
67
70
  schemaExplorerPlugin,
68
71
  adminPanelPlugin,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webspresso",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
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": {
@@ -37,9 +37,11 @@
37
37
  "plugins/"
38
38
  ],
39
39
  "dependencies": {
40
+ "bcrypt": "^5.1.1",
40
41
  "commander": "^11.1.0",
41
42
  "dayjs": "^1.11.19",
42
43
  "express": "^4.18.2",
44
+ "express-session": "^1.18.0",
43
45
  "helmet": "^7.2.0",
44
46
  "inquirer": "^8.2.6",
45
47
  "knex": "^3.1.0",
@@ -4,8 +4,8 @@
4
4
  * @module plugins/admin-panel/admin-user-model
5
5
  */
6
6
 
7
- const { defineModel } = require('../../../core/orm/model');
8
- const { zdb } = require('../../../core/orm');
7
+ const { defineModel } = require('../../core/orm/model');
8
+ const { zdb } = require('../../core/orm');
9
9
 
10
10
  /**
11
11
  * AdminUser model schema
@@ -23,7 +23,7 @@ const AdminUserSchema = zdb.schema({
23
23
 
24
24
  /**
25
25
  * Create and register AdminUser model
26
- * @returns {import('../../../core/orm/types').ModelDefinition}
26
+ * @returns {import('../../core/orm/types').ModelDefinition}
27
27
  */
28
28
  function createAdminUserModel() {
29
29
  return defineModel({
@@ -4,7 +4,7 @@
4
4
  * @module plugins/admin-panel/api
5
5
  */
6
6
 
7
- const { getAllModels, getModel } = require('../../../core/orm/model');
7
+ const { getAllModels, getModel } = require('../../core/orm/model');
8
8
  const { checkAdminExists, setupAdmin, login, logout, requireAuth } = require('./auth');
9
9
 
10
10
  /**
@@ -31,25 +31,9 @@ function adminPanelPlugin(options = {}) {
31
31
  throw new Error('Admin panel plugin requires a database instance. Pass `db` in options.');
32
32
  }
33
33
 
34
- // Check for peer dependencies
35
- let session = null;
36
- let bcrypt = null;
37
-
38
- try {
39
- session = require('express-session');
40
- } catch (e) {
41
- throw new Error(
42
- 'Admin panel plugin requires express-session. Install it with: npm install express-session'
43
- );
44
- }
45
-
46
- try {
47
- bcrypt = require('bcrypt');
48
- } catch (e) {
49
- throw new Error(
50
- 'Admin panel plugin requires bcrypt. Install it with: npm install bcrypt'
51
- );
52
- }
34
+ // Dependencies are now in main package
35
+ const session = require('express-session');
36
+ const bcrypt = require('bcrypt');
53
37
 
54
38
  return {
55
39
  name: 'admin-panel',
@@ -61,8 +45,13 @@ function adminPanelPlugin(options = {}) {
61
45
  * Register hook - called when plugin is registered
62
46
  */
63
47
  async register(ctx) {
64
- // Create and register AdminUser model
65
- const AdminUser = createAdminUserModel();
48
+ // Create and register AdminUser model (only if not already registered)
49
+ const { getModel } = require('../../core/orm/model');
50
+ let AdminUser = getModel('AdminUser');
51
+
52
+ if (!AdminUser) {
53
+ AdminUser = createAdminUserModel();
54
+ }
66
55
 
67
56
  // Store in plugin context for later use
68
57
  this._adminUser = AdminUser;
@@ -86,19 +75,24 @@ function adminPanelPlugin(options = {}) {
86
75
  const bcrypt = this._bcrypt;
87
76
  const session = this._session;
88
77
 
89
- // Setup session middleware
90
- const sessionSecret = options.sessionSecret || process.env.SESSION_SECRET || 'webspresso-admin-secret-change-in-production';
91
-
92
- app.use(session({
93
- secret: sessionSecret,
94
- resave: false,
95
- saveUninitialized: false,
96
- cookie: {
97
- secure: process.env.NODE_ENV === 'production',
98
- httpOnly: true,
99
- maxAge: 24 * 60 * 60 * 1000, // 24 hours
100
- },
101
- }));
78
+ // Setup session middleware (only once, even if multiple plugins)
79
+ // Check if session middleware is already registered
80
+ if (!app._webspressoSessionInitialized) {
81
+ const sessionSecret = options.sessionSecret || process.env.SESSION_SECRET || 'webspresso-admin-secret-change-in-production';
82
+
83
+ app.use(session({
84
+ secret: sessionSecret,
85
+ resave: false,
86
+ saveUninitialized: false,
87
+ cookie: {
88
+ secure: process.env.NODE_ENV === 'production',
89
+ httpOnly: true,
90
+ maxAge: 24 * 60 * 60 * 1000, // 24 hours
91
+ },
92
+ }));
93
+
94
+ app._webspressoSessionInitialized = true;
95
+ }
102
96
 
103
97
  // Create API handlers
104
98
  const apiHandlers = createApiHandlers({