millas 0.2.28 → 0.2.30
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/bin/millas.js +12 -2
- package/package.json +2 -1
- package/src/cli.js +117 -20
- package/src/commands/call.js +1 -1
- package/src/commands/createsuperuser.js +137 -182
- package/src/commands/key.js +61 -83
- package/src/commands/lang.js +423 -515
- package/src/commands/make.js +88 -62
- package/src/commands/migrate.js +200 -279
- package/src/commands/new.js +55 -50
- package/src/commands/route.js +78 -80
- package/src/commands/schedule.js +52 -150
- package/src/commands/serve.js +158 -191
- package/src/console/AppCommand.js +106 -0
- package/src/console/BaseCommand.js +726 -0
- package/src/console/CommandContext.js +66 -0
- package/src/console/CommandRegistry.js +88 -0
- package/src/console/Style.js +123 -0
- package/src/console/index.js +12 -3
- package/src/container/AppInitializer.js +10 -0
- package/src/facades/DB.js +195 -0
- package/src/index.js +2 -1
- package/src/scaffold/maker.js +102 -42
- package/src/schematics/Collection.js +28 -0
- package/src/schematics/SchematicEngine.js +122 -0
- package/src/schematics/Template.js +99 -0
- package/src/schematics/index.js +7 -0
- package/src/templates/command/default.template.js +14 -0
- package/src/templates/command/schema.json +19 -0
- package/src/templates/controller/default.template.js +10 -0
- package/src/templates/controller/resource.template.js +59 -0
- package/src/templates/controller/schema.json +30 -0
- package/src/templates/job/default.template.js +11 -0
- package/src/templates/job/schema.json +19 -0
- package/src/templates/middleware/default.template.js +11 -0
- package/src/templates/middleware/schema.json +19 -0
- package/src/templates/migration/default.template.js +14 -0
- package/src/templates/migration/schema.json +19 -0
- package/src/templates/model/default.template.js +14 -0
- package/src/templates/model/migration.template.js +17 -0
- package/src/templates/model/schema.json +30 -0
- package/src/templates/service/default.template.js +12 -0
- package/src/templates/service/schema.json +19 -0
- package/src/templates/shape/default.template.js +11 -0
- package/src/templates/shape/schema.json +19 -0
- package/src/validation/BaseValidator.js +3 -0
- 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
|
-
|
|
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.
|
|
3
|
+
"version": "0.2.30",
|
|
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('
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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 };
|
package/src/commands/call.js
CHANGED
|
@@ -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
|
|
4
|
-
const path
|
|
5
|
-
const fs
|
|
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
|
|
7
|
+
const Hasher = require('../auth/Hasher');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
class CreateSuperUserCommand extends BaseCommand {
|
|
10
|
+
static description = 'Manage superusers and admin accounts';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
async onInit(register) {
|
|
13
|
+
register
|
|
14
|
+
.command(async (email, name, noinput) => {
|
|
15
|
+
const { User } = await this.#resolveUserModel();
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
this.logger.log(this.style.info('\n Create Millas Superuser\n'));
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
//
|
|
41
|
+
// Password
|
|
53
42
|
let plainPassword;
|
|
54
|
-
if (
|
|
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:
|
|
75
|
-
is_active:
|
|
76
|
-
is_staff:
|
|
61
|
+
password: hash,
|
|
62
|
+
is_active: true,
|
|
63
|
+
is_staff: true,
|
|
77
64
|
is_superuser: true,
|
|
78
65
|
});
|
|
79
66
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
+
this.logger.log(this.style.info(`\n Changing password for: ${user.email}\n`));
|
|
106
87
|
|
|
107
|
-
const plain
|
|
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:
|
|
96
|
+
password: hash,
|
|
116
97
|
updated_at: new Date().toISOString(),
|
|
117
98
|
});
|
|
118
99
|
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
113
|
+
this.warn('No staff users found.');
|
|
114
|
+
this.info('Run: millas createsuperuser');
|
|
139
115
|
return;
|
|
140
116
|
}
|
|
141
117
|
|
|
142
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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
|
|
148
|
-
const sup
|
|
149
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
`
|
|
209
|
-
`
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
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;
|