millas 0.2.27 → 0.2.29

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 (48) hide show
  1. package/bin/millas.js +12 -2
  2. package/package.json +2 -1
  3. package/src/cli.js +117 -20
  4. package/src/commands/call.js +1 -1
  5. package/src/commands/createsuperuser.js +137 -182
  6. package/src/commands/key.js +61 -83
  7. package/src/commands/lang.js +423 -515
  8. package/src/commands/make.js +88 -62
  9. package/src/commands/migrate.js +200 -279
  10. package/src/commands/new.js +55 -50
  11. package/src/commands/route.js +78 -80
  12. package/src/commands/schedule.js +52 -150
  13. package/src/commands/serve.js +158 -191
  14. package/src/console/AppCommand.js +106 -0
  15. package/src/console/BaseCommand.js +726 -0
  16. package/src/console/CommandContext.js +66 -0
  17. package/src/console/CommandRegistry.js +88 -0
  18. package/src/console/Style.js +123 -0
  19. package/src/console/index.js +12 -3
  20. package/src/container/AppInitializer.js +10 -0
  21. package/src/container/Application.js +2 -0
  22. package/src/facades/DB.js +195 -0
  23. package/src/index.js +2 -1
  24. package/src/scaffold/maker.js +102 -42
  25. package/src/schematics/Collection.js +28 -0
  26. package/src/schematics/SchematicEngine.js +122 -0
  27. package/src/schematics/Template.js +99 -0
  28. package/src/schematics/index.js +7 -0
  29. package/src/templates/command/default.template.js +14 -0
  30. package/src/templates/command/schema.json +19 -0
  31. package/src/templates/controller/default.template.js +10 -0
  32. package/src/templates/controller/resource.template.js +59 -0
  33. package/src/templates/controller/schema.json +30 -0
  34. package/src/templates/job/default.template.js +11 -0
  35. package/src/templates/job/schema.json +19 -0
  36. package/src/templates/middleware/default.template.js +11 -0
  37. package/src/templates/middleware/schema.json +19 -0
  38. package/src/templates/migration/default.template.js +14 -0
  39. package/src/templates/migration/schema.json +19 -0
  40. package/src/templates/model/default.template.js +14 -0
  41. package/src/templates/model/migration.template.js +17 -0
  42. package/src/templates/model/schema.json +30 -0
  43. package/src/templates/service/default.template.js +12 -0
  44. package/src/templates/service/schema.json +19 -0
  45. package/src/templates/shape/default.template.js +11 -0
  46. package/src/templates/shape/schema.json +19 -0
  47. package/src/validation/BaseValidator.js +3 -0
  48. package/src/validation/types.js +3 -3
package/bin/millas.js CHANGED
@@ -2,5 +2,15 @@
2
2
 
3
3
  'use strict';
4
4
 
5
- const { program } = require('../src/cli');
6
- program.parse(process.argv);
5
+ const { program, bootstrap } = require('../src/cli');
6
+
7
+ // Bootstrap async then parse
8
+ (async () => {
9
+ try {
10
+ await bootstrap();
11
+ await program.parseAsync(process.argv);
12
+ } catch (err) {
13
+ console.error(err);
14
+ process.exitCode = 1;
15
+ }
16
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "millas",
3
- "version": "0.2.27",
3
+ "version": "0.2.29",
4
4
  "description": "A modern batteries-included backend framework for Node.js — built on Express, inspired by Laravel, Django, and FastAPI",
5
5
  "main": "src/index.js",
6
6
  "exports": {
@@ -56,6 +56,7 @@
56
56
  },
57
57
  "peerDependencies": {
58
58
  "express": "^4.18.0",
59
+ "pg": "^8.20.0",
59
60
  "multer": "^1.4.5 || ^2.0.0",
60
61
  "@aws-sdk/client-s3": "^3.0.0",
61
62
  "@aws-sdk/s3-request-presigner": "^3.0.0"
package/src/cli.js CHANGED
@@ -2,34 +2,131 @@
2
2
  // Set CLI mode globally for all commands
3
3
  process.env.MILLAS_CLI_MODE = 'true';
4
4
 
5
+ // Load .env file early (before any commands are loaded)
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+ const envPath = path.resolve(process.cwd(), '.env');
9
+ if (fs.existsSync(envPath)) {
10
+ require('dotenv').config({ path: envPath, override: false });
11
+ }
12
+
5
13
  const { Command } = require('commander');
6
14
  const chalk = require('chalk');
15
+ const CommandContext = require('./console/CommandContext');
16
+ const CommandRegistry = require('./console/CommandRegistry');
17
+
18
+ // Initialize Commander program
7
19
  const program = new Command();
8
20
 
9
21
  program
10
22
  .enablePositionalOptions()
11
23
  .name('millas')
12
24
  .description(chalk.cyan('⚡ Millas — A modern batteries-included Node.js framework'))
13
- .version('0.2.12-beta-1');
14
-
15
- // Load all command modules
16
- require('./commands/new')(program);
17
- require('./commands/serve')(program);
18
- require('./commands/make')(program);
19
- require('./commands/migrate')(program);
20
- require('./commands/route')(program);
21
- require('./commands/queue')(program);
22
- require('./commands/schedule')(program);
23
- require('./commands/createsuperuser')(program);
24
- require('./commands/lang')(program);
25
- require('./commands/key')(program);
26
- require('./commands/call')(program);
27
-
28
- // Unknown command handler
25
+ .version(require('../package.json').version)
26
+ .option('--debug', 'Enable debug mode with verbose output');
27
+
28
+ // Configure help
29
+ program.configureHelp({
30
+ sortSubcommands: true,
31
+ sortOptions: true,
32
+ });
33
+
34
+ // Handle --debug flag globally
35
+ program.hook('preAction', (thisCommand) => {
36
+ if (thisCommand.opts().debug) {
37
+ process.env.DEBUG = 'true';
38
+ }
39
+ });
40
+
41
+ // Create command context
42
+ const context = new CommandContext({
43
+ program,
44
+ cwd: process.cwd(),
45
+ logger: console,
46
+ });
47
+
48
+ // Initialize command registry
49
+ const registry = new CommandRegistry(context);
50
+
51
+ // Bootstrap function (async)
52
+ async function bootstrap() {
53
+ // Auto-discover and register built-in commands
54
+ const commandsDir = path.join(__dirname, 'commands');
55
+ await registry.discoverCommands(commandsDir);
56
+
57
+ // Auto-discover user-defined commands (if inside a Millas project)
58
+ if (context.isMillasProject()) {
59
+ await registry.discoverUserCommands();
60
+ }
61
+ }
62
+
63
+ // Unknown command handler with smart suggestions
29
64
  program.on('command:*', ([cmd]) => {
30
- console.error(chalk.red(`\n Unknown command: ${chalk.bold(cmd)}\n`));
31
- console.log(` Run ${chalk.cyan('millas --help')} to see available commands.\n`);
32
- process.exit(1);
65
+ const Style = require('./console/Style');
66
+ const style = new Style();
67
+
68
+ // Check if this looks like a namespace (e.g., 'lang', 'user', 'migrate')
69
+ const allCommands = program.commands.map(c => c.name());
70
+ const namespaceCommands = allCommands.filter(c => c.startsWith(cmd + ':'));
71
+
72
+ if (namespaceCommands.length > 0) {
73
+ // User typed a namespace without subcommand
74
+ console.error(style.danger(`\n ✖ Command '${cmd}' requires a subcommand.\n`));
75
+ console.log(style.info(` Available ${cmd} commands:\n`));
76
+
77
+ namespaceCommands.forEach(fullCmd => {
78
+ const subCmd = fullCmd.substring(cmd.length + 1);
79
+ const cmdObj = program.commands.find(c => c.name() === fullCmd);
80
+ const desc = cmdObj ? cmdObj.description() : '';
81
+ console.log(` ${style.primary(fullCmd.padEnd(25))} ${style.secondary(desc)}`);
82
+ });
83
+ console.log('');
84
+ } else {
85
+ // Truly unknown command - suggest similar ones
86
+ const similar = allCommands.filter(c => {
87
+ const base = c.split(':')[0];
88
+ return base.includes(cmd) || cmd.includes(base) || levenshtein(cmd, base) <= 2;
89
+ });
90
+
91
+ console.error(style.danger(`\n ✖ Unknown command: ${style.bold(cmd)}\n`));
92
+
93
+ if (similar.length > 0) {
94
+ console.log(style.info(' Did you mean one of these?\n'));
95
+ similar.slice(0, 5).forEach(s => {
96
+ console.log(` ${style.primary(s)}`);
97
+ });
98
+ console.log('');
99
+ }
100
+
101
+ console.log(` Run ${style.primary('millas --help')} to see all available commands.\n`);
102
+ }
103
+
104
+ process.exitCode = 1;
33
105
  });
34
106
 
35
- module.exports = { program };
107
+ // Simple Levenshtein distance for command suggestions
108
+ function levenshtein(a, b) {
109
+ const matrix = [];
110
+ for (let i = 0; i <= b.length; i++) {
111
+ matrix[i] = [i];
112
+ }
113
+ for (let j = 0; j <= a.length; j++) {
114
+ matrix[0][j] = j;
115
+ }
116
+ for (let i = 1; i <= b.length; i++) {
117
+ for (let j = 1; j <= a.length; j++) {
118
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
119
+ matrix[i][j] = matrix[i - 1][j - 1];
120
+ } else {
121
+ matrix[i][j] = Math.min(
122
+ matrix[i - 1][j - 1] + 1,
123
+ matrix[i][j - 1] + 1,
124
+ matrix[i - 1][j] + 1
125
+ );
126
+ }
127
+ }
128
+ }
129
+ return matrix[b.length][a.length];
130
+ }
131
+
132
+ module.exports = { program, context, registry, bootstrap };
@@ -29,7 +29,7 @@ module.exports = function (program) {
29
29
  const bootstrapPath = path.join(process.cwd(), 'bootstrap/app.js');
30
30
  if (!fs.existsSync(bootstrapPath)) return;
31
31
 
32
- require('dotenv').config({ path: path.resolve(process.cwd(), '.env') });
32
+ // require('dotenv').config({ path: path.resolve(process.cwd(), '.env') });
33
33
 
34
34
  const basePath = process.cwd();
35
35
  const AppServiceProvider = require(path.join(basePath, 'providers/AppServiceProvider'));
@@ -1,33 +1,23 @@
1
1
  'use strict';
2
2
 
3
- const chalk = require('chalk');
4
- const path = require('path');
5
- const fs = require('fs-extra');
3
+ const BaseCommand = require('../console/BaseCommand');
4
+ const path = require('path');
5
+ const fs = require('fs-extra');
6
6
  const readline = require('readline');
7
- const Hasher = require('../auth/Hasher');
7
+ const Hasher = require('../auth/Hasher');
8
8
 
9
- const Log = require("../logger/internal")
9
+ class CreateSuperUserCommand extends BaseCommand {
10
+ static description = 'Manage superusers and admin accounts';
10
11
 
11
- const TAG = 'Create SuperUser';
12
+ async onInit(register) {
13
+ register
14
+ .command(async (email, name, noinput) => {
15
+ const { User } = await this.#resolveUserModel();
12
16
 
13
- module.exports = function (program) {
17
+ this.logger.log(this.style.info('\n Create Millas Superuser\n'));
14
18
 
15
- // ── createsuperuser ────────────────────────────────────────────────────────
16
- program
17
- .command('createsuperuser')
18
- .description('Create a superuser in the users table (interactive)')
19
- .option('--email <email>', 'Email address (skip prompt)')
20
- .option('--name <n>', 'Display name (skip prompt)')
21
- .option('--noinput', 'Read password from ADMIN_PASSWORD env var, skip all prompts')
22
- .action(async (options) => {
23
- try {
24
- const { User, Auth } = await resolveUserModel();
25
-
26
- Log.i(TAG,chalk.cyan('Create Millas Superuser'));
27
-
28
- // ── Email ────────────────────────────────────────────────
29
- let email = options.email;
30
- if (!email) email = await prompt(' Email address: ');
19
+ // Email
20
+ if (!email) email = await this.#prompt(' Email address: ');
31
21
  email = (email || '').trim().toLowerCase();
32
22
  if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
33
23
  throw new Error('Enter a valid email address.');
@@ -38,229 +28,194 @@ module.exports = function (program) {
38
28
  throw new Error(
39
29
  `A user with email "${email}" already exists.\n` +
40
30
  ` To grant staff access: update the record and set is_staff=true, is_superuser=true.\n` +
41
- ` To change their password: millas changepassword --email ${email}`
31
+ ` To change their password: millas createsuperuser:changepassword --email ${email}`
42
32
  );
43
33
  }
44
34
 
45
- // ── Name ─────────────────────────────────────────────────
46
- let name = options.name;
47
- if (!name && !options.noinput) {
48
- name = await prompt(' Display name (optional, press Enter to skip): ');
35
+ // Name
36
+ if (!name && !noinput) {
37
+ name = await this.#prompt(' Display name (optional, press Enter to skip): ');
49
38
  }
50
39
  name = (name || '').trim() || email.split('@')[0];
51
40
 
52
- // ── Password ─────────────────────────────────────────────
41
+ // Password
53
42
  let plainPassword;
54
- if (options.noinput) {
43
+ if (noinput) {
55
44
  plainPassword = process.env.ADMIN_PASSWORD;
56
45
  if (!plainPassword) {
57
46
  throw new Error('--noinput requires ADMIN_PASSWORD to be set in the environment.');
58
47
  }
59
48
  } else {
60
- plainPassword = await promptPassword(' Password: ');
61
- const confirm = await promptPassword(' Password (again): ');
49
+ plainPassword = await this.#promptPassword(' Password: ');
50
+ const confirm = await this.#promptPassword(' Password (again): ');
62
51
  if (plainPassword !== confirm) throw new Error('Passwords do not match.');
63
52
  }
64
53
 
65
- validatePassword(plainPassword);
54
+ this.#validatePassword(plainPassword);
66
55
 
67
- // ── Create via Auth.register path but with staff flags ───
68
- // Hash manually so we can pass the flags in the same create() call.
69
56
  const hash = await Hasher.make(plainPassword);
70
57
 
71
58
  await User.create({
72
59
  email,
73
60
  name,
74
- password: hash,
75
- is_active: true,
76
- is_staff: true,
61
+ password: hash,
62
+ is_active: true,
63
+ is_staff: true,
77
64
  is_superuser: true,
78
65
  });
79
66
 
80
- Log.i(TAG,chalk.green(`✔ Superuser "${email}" created successfully.`));
81
- Log.i(TAG,chalk.gray(` Run: millas serve then visit /admin`));
82
- } catch (err) {
83
- Log.e(TAG,chalk.red(`✖ ${err.message}`));
84
- Log.e(TAG,err.stack);
85
- process.exit(1);
86
- }
87
- });
67
+ this.success(`Superuser "${email}" created successfully.`);
68
+ this.info('Run: millas serve then visit /admin');
69
+ })
70
+ .name('createsuperuser')
71
+ .str('[email]', v => v.email().optional(), 'Email address (skip prompt)')
72
+ .str('[name]', v => v.optional(), 'Display name (skip prompt)')
73
+ .bool('noinput', 'Read password from ADMIN_PASSWORD env var, skip all prompts')
74
+ .description('Create a superuser in the users table (interactive)');
88
75
 
89
- // ── changepassword ─────────────────────────────────────────────────────────
90
- program
91
- .command('changepassword')
92
- .description("Change a user's password in the users table")
93
- .option('--email <email>', 'Email address of the user')
94
- .action(async (options) => {
95
- try {
96
- const { User } = await resolveUserModel();
76
+ register
77
+ .command(async (email) => {
78
+ const { User } = await this.#resolveUserModel();
97
79
 
98
- let email = options.email;
99
- if (!email) email = await prompt('\n Email address: ');
80
+ if (!email) email = await this.#prompt('\n Email address: ');
100
81
  email = (email || '').trim().toLowerCase();
101
82
 
102
83
  const user = await User.findBy('email', email);
103
84
  if (!user) throw new Error(`No user found with email "${email}".`);
104
85
 
105
- Log.i(TAG,chalk.cyan(`Changing password for: ${user.email}`));
86
+ this.logger.log(this.style.info(`\n Changing password for: ${user.email}\n`));
106
87
 
107
- const plain = await promptPassword(' New password: ');
108
- const confirm = await promptPassword(' New password (again): ');
88
+ const plain = await this.#promptPassword(' New password: ');
89
+ const confirm = await this.#promptPassword(' New password (again): ');
109
90
  if (plain !== confirm) throw new Error('Passwords do not match.');
110
- validatePassword(plain);
91
+ this.#validatePassword(plain);
111
92
 
112
93
  const hash = await Hasher.make(plain);
113
94
 
114
95
  await User.where('id', user.id).update({
115
- password: hash,
96
+ password: hash,
116
97
  updated_at: new Date().toISOString(),
117
98
  });
118
99
 
119
- Log.i(TAG, chalk.green(`✔ Password updated for "${email}".`));
120
- } catch (err) {
121
- Log.e(TAG, chalk.red(`✖ ${err.message}`));
122
- process.exit(1);
123
- }
124
- });
100
+ this.success(`Password updated for "${email}".`);
101
+ })
102
+ .name('changepassword')
103
+ .str('[email]', v => v.email().optional(), 'Email address of the user')
104
+ .description("Change a user's password in the users table");
125
105
 
126
- // ── listadmins ─────────────────────────────────────────────────────────────
127
- program
128
- .command('listadmins')
129
- .description('List all staff/superusers from the users table')
130
- .action(async () => {
131
- try {
132
- const { User } = await resolveUserModel();
106
+ register
107
+ .command(async () => {
108
+ const { User } = await this.#resolveUserModel();
133
109
 
134
- // Query for all staff users — is_staff=true
135
110
  const users = await User.where('is_staff', true).orderBy('id').get();
136
111
 
137
112
  if (!users.length) {
138
- Log.i(TAG, chalk.yellow('No staff users found.\n Run: millas createsuperuser'));
113
+ this.warn('No staff users found.');
114
+ this.info('Run: millas createsuperuser');
139
115
  return;
140
116
  }
141
117
 
142
- Log.i(TAG, chalk.cyan('Staff / Superusers'));
118
+ this.logger.log(this.style.info('\n Staff / Superusers\n'));
143
119
  const colW = Math.max(...users.map(u => u.email.length)) + 2;
144
- Log.i(TAG, chalk.gray(` ${'ID'.padEnd(5)} ${'Email'.padEnd(colW)} ${'Name'.padEnd(20)} Active Super`));
145
- Log.i(TAG, chalk.gray(' ' + '─'.repeat(colW + 42)));
120
+ this.logger.log(this.style.muted(` ${'ID'.padEnd(5)} ${'Email'.padEnd(colW)} ${'Name'.padEnd(20)} Active Super`));
121
+ this.logger.log(this.style.muted(' ' + '─'.repeat(colW + 42)));
122
+
146
123
  for (const u of users) {
147
- const active = u.is_active ? chalk.green('Yes ') : chalk.red('No ');
148
- const sup = u.is_superuser ? chalk.green('Yes') : chalk.gray('No');
149
- Log.i(TAG, ` ${String(u.id).padEnd(5)} ${chalk.cyan(u.email.padEnd(colW))} ${(u.name || '—').padEnd(20)} ${active} ${sup}`);
124
+ const active = u.is_active ? this.style.success('Yes ') : this.style.danger('No ');
125
+ const sup = u.is_superuser ? this.style.success('Yes') : this.style.muted('No');
126
+ this.logger.log(` ${String(u.id).padEnd(5)} ${this.style.info(u.email.padEnd(colW))} ${(u.name || '—').padEnd(20)} ${active} ${sup}`);
150
127
  }
151
- } catch (err) {
152
- Log.e(TAG, chalk.red(`✖ ${err.message}`));
153
- process.exit(1);
154
- }
155
- });
156
- };
157
-
158
- // ─── Helpers ──────────────────────────────────────────────────────────────────
159
-
160
- /**
161
- * Resolve the User model using the same three-step priority as AuthServiceProvider:
162
- *
163
- * 1. config/app.js -> auth_user: 'ModelName'
164
- * Looked up by name in app/models/index.js exports.
165
- *
166
- * 2. app/models/User.js (conventional default path)
167
- *
168
- * 3. Built-in AuthUser (abstract fallback)
169
- *
170
- * Also boots the DB connection and verifies the resolved model's table exists,
171
- * giving a clear error if migrations haven't been run yet.
172
- */
173
-
174
- async function resolveUserModel() {
175
- const cwd = process.cwd();
176
- const configPath = path.join(cwd, 'config/database.js');
177
- if (!fs.existsSync(configPath)) {
178
- throw new Error('config/database.js not found. Are you inside a Millas project?');
128
+ this.logger.log('');
129
+ })
130
+ .name('listadmins')
131
+ .description('List all staff/superusers from the users table');
179
132
  }
180
133
 
181
- // Always require DatabaseManager from the project-local node_modules.
182
- // This ensures the same singleton is shared with the project's models,
183
- // avoiding the "not configured" error when millas is installed globally.
184
- const dbConfig = require(configPath);
185
- let DatabaseManager;
186
- try {
187
- DatabaseManager = require(path.join(cwd, 'node_modules/millas/src/orm/drivers/DatabaseManager'));
188
- } catch {
189
- DatabaseManager = require('../orm/drivers/DatabaseManager');
190
- }
191
- DatabaseManager.configure(dbConfig);
192
- const db = DatabaseManager.connection();
193
-
194
- // -- Step 1: auth_user from config/app.js --
195
- let User;
196
- let authUserName = null;
197
- try {
198
- const appConfig = require(path.join(cwd, 'config/app'));
199
- authUserName = appConfig.auth_user || null;
200
- } catch { /* config/app.js missing or no auth_user key */ }
201
-
202
- if (authUserName) {
134
+ async #resolveUserModel() {
135
+ const configPath = path.join(this.cwd, 'config/database.js');
136
+ if (!fs.existsSync(configPath)) {
137
+ throw new Error('config/database.js not found. Are you inside a Millas project?');
138
+ }
139
+
140
+ const dbConfig = require(configPath);
141
+ let DatabaseManager;
203
142
  try {
204
- const modelsIndex = require(path.join(cwd, 'app/models/index'));
205
- const resolved = modelsIndex[authUserName];
206
- if (!resolved) {
143
+ DatabaseManager = require(path.join(this.cwd, 'node_modules/millas/src/orm/drivers/DatabaseManager'));
144
+ } catch {
145
+ DatabaseManager = require('../orm/drivers/DatabaseManager');
146
+ }
147
+ DatabaseManager.configure(dbConfig);
148
+ const db = DatabaseManager.connection();
149
+
150
+ let User;
151
+ let authUserName = null;
152
+ try {
153
+ const appConfig = require(path.join(this.cwd, 'config/app'));
154
+ authUserName = appConfig.auth_user || null;
155
+ } catch { /* config/app.js missing or no auth_user key */ }
156
+
157
+ if (authUserName) {
158
+ try {
159
+ const modelsIndex = require(path.join(this.cwd, 'app/models/index'));
160
+ const resolved = modelsIndex[authUserName];
161
+ if (!resolved) {
162
+ throw new Error(
163
+ `auth_user: '${authUserName}' not found in app/models/index.js.\n` +
164
+ ` Available exports: ${Object.keys(modelsIndex).join(', ')}`
165
+ );
166
+ }
167
+ User = resolved;
168
+ } catch (err) {
169
+ if (err.message.includes('auth_user:')) throw err;
170
+ throw new Error(`Could not load app/models/index.js: ${err.message}`);
171
+ }
172
+ } else {
173
+ try {
174
+ User = require(path.join(this.cwd, 'app/models/User'));
175
+ } catch {
176
+ User = require('../auth/AuthUser');
177
+ }
178
+ }
179
+
180
+ const table = User.table;
181
+ if (table) {
182
+ const tableExists = await db.schema.hasTable(table);
183
+ if (!tableExists) {
207
184
  throw new Error(
208
- `auth_user: '${authUserName}' not found in app/models/index.js.\n` +
209
- ` Available exports: ${Object.keys(modelsIndex).join(', ')}`
185
+ `Table "${table}" does not exist.\n\n` +
186
+ ` Did you run migrations?\n` +
187
+ ` Run: millas migrate\n`
210
188
  );
211
189
  }
212
- User = resolved;
213
- } catch (err) {
214
- if (err.message.includes('auth_user:')) throw err;
215
- throw new Error(`Could not load app/models/index.js: ${err.message}`);
216
- }
217
- } else {
218
- // -- Step 2: try app/models/User.js --
219
- try {
220
- User = require(path.join(cwd, 'app/models/User'));
221
- } catch {
222
- // -- Step 3: abstract AuthUser fallback --
223
- User = require('../auth/AuthUser');
224
190
  }
191
+
192
+ return { User, db };
225
193
  }
226
194
 
227
- // -- Verify the model's table exists (uses the model's own table name) --
228
- const table = User.table;
229
- if (table) {
230
- const tableExists = await db.schema.hasTable(table);
231
- if (!tableExists) {
232
- throw new Error(
233
- `Table "${table}" does not exist.\n\n` +
234
- ` Did you run migrations?\n` +
235
- ` Run: millas migrate\n`
236
- );
195
+ #validatePassword(pw) {
196
+ if (!pw || pw.length < 8) {
197
+ throw new Error('This password is too short. It must contain at least 8 characters.');
237
198
  }
238
199
  }
239
200
 
240
- return { User, db };
241
- }
242
-
243
- function validatePassword(pw) {
244
- if (!pw || pw.length < 8) {
245
- throw new Error('This password is too short. It must contain at least 8 characters.');
201
+ #prompt(question) {
202
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
203
+ return new Promise(resolve => rl.question(question, ans => { rl.close(); resolve(ans); }));
246
204
  }
247
- }
248
205
 
249
- function prompt(question) {
250
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
251
- return new Promise(resolve => rl.question(question, ans => { rl.close(); resolve(ans); }));
206
+ #promptPassword(question) {
207
+ return new Promise(resolve => {
208
+ if (!process.stdin.isTTY) return this.#prompt(question).then(resolve);
209
+
210
+ process.stdout.write(question);
211
+ const rl = readline.createInterface({
212
+ input: process.stdin,
213
+ output: new (require('stream').Writable)({ write(c, e, cb) { cb(); } }),
214
+ terminal: true,
215
+ });
216
+ rl.question('', ans => { rl.close(); process.stdout.write('\n'); resolve(ans); });
217
+ });
218
+ }
252
219
  }
253
220
 
254
- function promptPassword(question) {
255
- return new Promise(resolve => {
256
- if (!process.stdin.isTTY) return prompt(question).then(resolve);
257
-
258
- process.stdout.write(question);
259
- const rl = readline.createInterface({
260
- input: process.stdin,
261
- output: new (require('stream').Writable)({ write(c, e, cb) { cb(); } }),
262
- terminal: true,
263
- });
264
- rl.question('', ans => { rl.close(); process.stdout.write('\n'); resolve(ans); });
265
- });
266
- }
221
+ module.exports = CreateSuperUserCommand;