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.
Files changed (47) 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/facades/DB.js +195 -0
  22. package/src/index.js +2 -1
  23. package/src/scaffold/maker.js +102 -42
  24. package/src/schematics/Collection.js +28 -0
  25. package/src/schematics/SchematicEngine.js +122 -0
  26. package/src/schematics/Template.js +99 -0
  27. package/src/schematics/index.js +7 -0
  28. package/src/templates/command/default.template.js +14 -0
  29. package/src/templates/command/schema.json +19 -0
  30. package/src/templates/controller/default.template.js +10 -0
  31. package/src/templates/controller/resource.template.js +59 -0
  32. package/src/templates/controller/schema.json +30 -0
  33. package/src/templates/job/default.template.js +11 -0
  34. package/src/templates/job/schema.json +19 -0
  35. package/src/templates/middleware/default.template.js +11 -0
  36. package/src/templates/middleware/schema.json +19 -0
  37. package/src/templates/migration/default.template.js +14 -0
  38. package/src/templates/migration/schema.json +19 -0
  39. package/src/templates/model/default.template.js +14 -0
  40. package/src/templates/model/migration.template.js +17 -0
  41. package/src/templates/model/schema.json +30 -0
  42. package/src/templates/service/default.template.js +12 -0
  43. package/src/templates/service/schema.json +19 -0
  44. package/src/templates/shape/default.template.js +11 -0
  45. package/src/templates/shape/schema.json +19 -0
  46. package/src/validation/BaseValidator.js +3 -0
  47. package/src/validation/types.js +3 -3
@@ -1,65 +1,70 @@
1
1
  'use strict';
2
2
 
3
- const chalk = require('chalk');
3
+ const BaseCommand = require('../console/BaseCommand');
4
4
  const fs = require('fs-extra');
5
5
  const path = require('path');
6
6
  const ora = require('ora');
7
7
  const { generateProject } = require('../scaffold/generator');
8
8
 
9
- module.exports = function (program) {
10
- program
11
- .command('new <project-name>')
12
- .description('Create a new Millas project')
13
- .option('--no-install', 'Skip npm install')
14
- .action(async (projectName, options) => {
15
- console.log();
16
- console.log(chalk.cyan(' ⚡ Millas Framework'));
17
- console.log(chalk.gray(' Creating a new project...\n'));
9
+ class NewCommand extends BaseCommand {
10
+ static description = 'Create a new Millas project';
18
11
 
19
- const targetDir = path.resolve(process.cwd(), projectName);
12
+ async onInit(register) {
13
+ register
14
+ .command(async (projectName, install) => {
15
+ this.logger.log('');
16
+ this.logger.log(this.style.info(' ⚡ Millas Framework'));
17
+ this.logger.log(this.style.muted(' Creating a new project...\n'));
20
18
 
21
- if (fs.existsSync(targetDir)) {
22
- console.error(chalk.red(` ✖ Directory "${projectName}" already exists.\n`));
23
- process.exit(1);
24
- }
19
+ const targetDir = path.resolve(this.cwd, projectName);
25
20
 
26
- const spinner = ora(` Scaffolding project ${chalk.bold(projectName)}`).start();
27
-
28
- try {
29
- await generateProject(projectName, targetDir);
30
- spinner.succeed(chalk.green(` Project "${projectName}" created successfully!`));
31
-
32
- if (options.install !== false) {
33
- const installSpinner = ora(' Installing dependencies...').start();
34
- const { execSync } = require('child_process');
35
- execSync('npm install', { cwd: targetDir, stdio: 'ignore' });
36
- installSpinner.succeed(chalk.green(' Dependencies installed!'));
21
+ if (fs.existsSync(targetDir)) {
22
+ throw new Error(`Directory "${projectName}" already exists.`);
37
23
  }
38
24
 
39
- // Auto-generate APP_KEY into the new project's .env
25
+ const spinner = ora(` Scaffolding project ${this.style.bold(projectName)}`).start();
26
+
40
27
  try {
41
- const { Encrypter } = require('../encryption/Encrypter');
42
- const envPath = path.join(targetDir, '.env');
43
- const key = Encrypter.generateKey('AES-256-CBC');
44
- if (fs.existsSync(envPath)) {
45
- let envContent = fs.readFileSync(envPath, 'utf8');
46
- envContent = /^APP_KEY=/m.test(envContent)
47
- ? envContent.replace(/^APP_KEY=.*$/m, `APP_KEY=${key}`)
48
- : envContent + `\nAPP_KEY=${key}\n`;
49
- fs.writeFileSync(envPath, envContent, 'utf8');
50
- console.log(chalk.green(' ✔ Application key generated.'));
28
+ await generateProject(projectName, targetDir);
29
+ spinner.succeed(this.style.success(` Project "${projectName}" created successfully!`));
30
+
31
+ if (install !== false) {
32
+ const installSpinner = ora(' Installing dependencies...').start();
33
+ const { execSync } = require('child_process');
34
+ execSync('npm install', { cwd: targetDir, stdio: 'ignore' });
35
+ installSpinner.succeed(this.style.success(' Dependencies installed!'));
51
36
  }
52
- } catch { /* non-fatal — developer can run millas key:generate */ }
53
37
 
54
- console.log();
55
- console.log(chalk.bold(' Next steps:'));
56
- console.log(chalk.cyan(` cd ${projectName}`));
57
- console.log(chalk.cyan(' millas serve'));
58
- console.log();
59
- } catch (err) {
60
- spinner.fail(chalk.red(' Failed to create project.'));
61
- console.error(chalk.red(`\n Error: ${err.message}\n`));
62
- process.exit(1);
63
- }
64
- });
65
- };
38
+ // Auto-generate APP_KEY into the new project's .env
39
+ try {
40
+ const { Encrypter } = require('../encryption/Encrypter');
41
+ const envPath = path.join(targetDir, '.env');
42
+ const key = Encrypter.generateKey('AES-256-CBC');
43
+ if (fs.existsSync(envPath)) {
44
+ let envContent = fs.readFileSync(envPath, 'utf8');
45
+ envContent = /^APP_KEY=/m.test(envContent)
46
+ ? envContent.replace(/^APP_KEY=.*$/m, `APP_KEY=${key}`)
47
+ : envContent + `\nAPP_KEY=${key}\n`;
48
+ fs.writeFileSync(envPath, envContent, 'utf8');
49
+ this.logger.log(this.style.success(' ✔ Application key generated.'));
50
+ }
51
+ } catch { /* non-fatal — developer can run millas key:generate */ }
52
+
53
+ this.logger.log('');
54
+ this.logger.log(this.style.bold(' Next steps:'));
55
+ this.logger.log(this.style.info(` cd ${projectName}`));
56
+ this.logger.log(this.style.info(' millas serve'));
57
+ this.logger.log('');
58
+ } catch (err) {
59
+ spinner.fail(this.style.danger(' Failed to create project.'));
60
+ throw err;
61
+ }
62
+ })
63
+ .name('new')
64
+ .str('projectName', 'Project name')
65
+ .bool('install', 'Install dependencies')
66
+ .description('Create a new Millas project');
67
+ }
68
+ }
69
+
70
+ module.exports = NewCommand;
@@ -1,82 +1,83 @@
1
1
  'use strict';
2
2
 
3
- const chalk = require('chalk');
4
- const path = require('path');
5
- const fs = require('fs-extra');
6
-
7
- module.exports = function (program) {
8
- program
9
- .command('route:list')
10
- .description('List all registered routes')
11
- .action(async () => {
12
- const bootstrapPath = path.resolve(process.cwd(), 'bootstrap/app.js');
13
-
14
- if (!fs.existsSync(bootstrapPath)) {
15
- console.error(chalk.red('\n ✖ Not inside a Millas project.\n'));
16
- process.exit(1);
17
- }
18
-
19
- process.env.MILLAS_ROUTE_LIST = 'true';
20
-
21
- let route;
22
- try {
23
- const bootstrap = await require(bootstrapPath);
24
- route = bootstrap.route;
25
- } catch (err) {
26
- console.error(chalk.red(`\n ✖ Failed to load routes: ${err.message}\n`));
27
- process.exit(1);
28
- }
29
-
30
- if (!route) {
31
- console.log(chalk.yellow('\n ⚠ Bootstrap did not export { route }.\n'));
32
- process.exit(0);
33
- }
34
-
35
- const rows = route.list();
36
-
37
- if (rows.length === 0) {
38
- console.log(chalk.yellow('\n No routes registered.\n'));
39
- return;
40
- }
41
-
42
- console.log();
43
- console.log(chalk.bold(' Registered Routes\n'));
44
-
45
- const col = {
46
- verb: 8,
47
- path: Math.max(6, ...rows.map(r => r.path.length)) + 2,
48
- handler: Math.max(8, ...rows.map(r => formatHandler(r).length)) + 2,
49
- mw: Math.max(10, ...rows.map(r => (r.middleware || []).join(', ').length || 1)) + 2,
50
- };
51
-
52
- const header =
3
+ const BaseCommand = require('../console/BaseCommand');
4
+
5
+ class RouteCommand extends BaseCommand {
6
+ static description = 'Manage application routes';
7
+
8
+ async onInit(register) {
9
+ register
10
+ .command(this.list)
11
+ .description('List all registered routes');
12
+ }
13
+
14
+ async list() {
15
+ if (!this.hasAppBootstrap()) {
16
+ this.error('Not inside a Millas project.');
17
+ throw new Error('Not inside a Millas project');
18
+ }
19
+
20
+ process.env.MILLAS_ROUTE_LIST = 'true';
21
+
22
+ let route;
23
+ try {
24
+ const app = await this.getApp();
25
+
26
+ route = app.route;
27
+ } catch (err) {
28
+ this.error(`Failed to load routes: ${err.message}`);
29
+ throw err;
30
+ }
31
+
32
+ if (!route) {
33
+ this.warn('Bootstrap did not export { route }.');
34
+ return;
35
+ }
36
+
37
+ const rows = route.list();
38
+
39
+ if (rows.length === 0) {
40
+ this.warn('No routes registered.');
41
+ return;
42
+ }
43
+
44
+ this.logger.log('');
45
+ this.logger.log(this.style.bold(' Registered Routes\n'));
46
+
47
+ const col = {
48
+ verb: 8,
49
+ path: Math.max(6, ...rows.map(r => r.path.length)) + 2,
50
+ handler: Math.max(8, ...rows.map(r => formatHandler(r).length)) + 2,
51
+ mw: Math.max(10, ...rows.map(r => (r.middleware || []).join(', ').length || 1)) + 2,
52
+ };
53
+
54
+ const header =
55
+ ' ' +
56
+ this.style.bold(pad('METHOD', col.verb)) +
57
+ this.style.bold(pad('PATH', col.path)) +
58
+ this.style.bold(pad('HANDLER', col.handler)) +
59
+ this.style.bold(pad('MIDDLEWARE', col.mw)) +
60
+ this.style.bold('NAME');
61
+
62
+ this.logger.log(header);
63
+ this.logger.log(this.style.line(col.verb + col.path + col.handler + col.mw + 10, '─').padStart(col.verb + col.path + col.handler + col.mw + 12));
64
+
65
+ for (const r of rows) {
66
+ const mw = (r.middleware || []).join(', ') || this.style.secondary('—');
67
+ const name = r.name || this.style.secondary('—');
68
+ this.logger.log(
53
69
  ' ' +
54
- chalk.bold(pad('METHOD', col.verb)) +
55
- chalk.bold(pad('PATH', col.path)) +
56
- chalk.bold(pad('HANDLER', col.handler)) +
57
- chalk.bold(pad('MIDDLEWARE', col.mw)) +
58
- chalk.bold('NAME');
59
-
60
- console.log(header);
61
- console.log(chalk.gray(' ' + '─'.repeat(col.verb + col.path + col.handler + col.mw + 10)));
62
-
63
- for (const r of rows) {
64
- const mw = (r.middleware || []).join(', ') || chalk.gray('—');
65
- const name = r.name || chalk.gray('—');
66
- console.log(
67
- ' ' +
68
- verbChalk(r.verb)(pad(r.verb, col.verb)) +
69
- chalk.cyan(pad(r.path, col.path)) +
70
- chalk.white(pad(formatHandler(r), col.handler)) +
71
- chalk.yellow(pad(mw, col.mw)) +
72
- chalk.gray(name)
73
- );
74
- }
75
-
76
- console.log(chalk.gray(`\n ${rows.length} route(s) total.\n`));
77
- process.exit(0);
78
- });
79
- };
70
+ this.style.method(r.verb)(pad(r.verb, col.verb)) +
71
+ this.style.info(pad(r.path, col.path)) +
72
+ this.style.light(pad(formatHandler(r), col.handler)) +
73
+ this.style.warning(pad(mw, col.mw)) +
74
+ this.style.secondary(name)
75
+ );
76
+ }
77
+
78
+ this.logger.log(this.style.secondary(`\n ${rows.length} route(s) total.\n`));
79
+ }
80
+ }
80
81
 
81
82
  function pad(str, len) { return String(str).padEnd(len); }
82
83
 
@@ -87,7 +88,4 @@ function formatHandler(r) {
87
88
  return r.method ? `${name}@${r.method}` : name;
88
89
  }
89
90
 
90
- function verbChalk(verb) {
91
- return { GET: chalk.green, POST: chalk.blue, PUT: chalk.yellow,
92
- PATCH: chalk.magenta, DELETE: chalk.red }[verb] || chalk.white;
93
- }
91
+ module.exports = RouteCommand;
@@ -1,176 +1,78 @@
1
1
  'use strict';
2
2
 
3
- const chalk = require('chalk');
4
- const path = require('path');
5
- const fs = require('fs-extra');
6
-
7
- module.exports = function (program) {
8
-
9
- program
10
- .command('schedule:list')
11
- .description('Show all scheduled tasks and their next run times')
12
- .action(async () => {
13
- const bootstrapPath = path.resolve(process.cwd(), 'bootstrap/app.js');
14
- if (!fs.existsSync(bootstrapPath)) {
15
- console.error(chalk.red('\n ✖ Not inside a Millas project.\n'));
16
- process.exit(1);
17
- }
18
-
19
- // Boot the app to get scheduler
20
- let app;
21
- try {
22
- app = await require(bootstrapPath);
23
- } catch (e) {
24
- console.error(chalk.red(`\n ✖ Failed to load app: ${e.message}\n`));
25
- process.exit(1);
26
- }
27
-
28
- const scheduler = app.make('scheduler');
29
- const tasks = scheduler.getTasks();
3
+ const BaseCommand = require('../console/BaseCommand');
4
+
5
+ class ScheduleCommand extends BaseCommand {
6
+ static description = 'Manage scheduled tasks';
7
+
8
+ async onInit(register) {
9
+ register
10
+ .command(async () => {
11
+ const app = this.getApp();
12
+ const scheduler = app.make('scheduler');
13
+ const tasks = scheduler.getTasks();
14
+
15
+ if (tasks.length === 0) {
16
+ this.info('No scheduled tasks found.');
17
+ this.info('Create routes/schedule.js to define scheduled tasks.');
18
+ return;
19
+ }
30
20
 
31
- console.log();
32
- if (tasks.length === 0) {
33
- console.log(chalk.gray(' No scheduled tasks found.'));
34
- console.log(chalk.gray(' Create routes/schedule.js to define scheduled tasks.'));
35
- } else {
36
- console.log(chalk.bold(' Scheduled Tasks\n'));
21
+ this.logger.log(this.style.bold('\n Scheduled Tasks\n'));
37
22
 
38
23
  for (const task of tasks) {
39
- const status = task.isRunning() ? chalk.yellow('RUNNING') : chalk.green('READY');
24
+ const status = task.isRunning()
25
+ ? this.style.warning('RUNNING')
26
+ : this.style.success('READY');
40
27
  const nextRun = task.lastRun
41
28
  ? `Last: ${task.lastRun.toLocaleString()}`
42
- : chalk.gray('Never run');
29
+ : this.style.muted('Never run');
43
30
 
44
- console.log(` ${chalk.cyan(task.jobClass.name.padEnd(30))} ${status.padEnd(15)} ${nextRun}`);
31
+ this.logger.log(` ${this.style.info(task.jobClass.name.padEnd(30))} ${status.padEnd(15)} ${nextRun}`);
45
32
 
46
33
  if (task.cronExpression) {
47
- console.log(` ${chalk.gray('Cron:')} ${task.cronExpression}`);
34
+ this.logger.log(` ${this.style.muted('Cron:')} ${task.cronExpression}`);
48
35
  }
49
36
 
50
37
  if (Object.keys(task.parameters).length > 0) {
51
- console.log(` ${chalk.gray('Params:')} ${JSON.stringify(task.parameters)}`);
38
+ this.logger.log(` ${this.style.muted('Params:')} ${JSON.stringify(task.parameters)}`);
52
39
  }
53
40
 
54
41
  if (task.failures.length > 0) {
55
42
  const lastFailure = task.failures[task.failures.length - 1];
56
- console.log(` ${chalk.red('Last failure:')} ${lastFailure.error} (${lastFailure.timestamp.toLocaleString()})`);
43
+ this.logger.log(` ${this.style.danger('Last failure:')} ${lastFailure.error} (${lastFailure.timestamp.toLocaleString()})`);
57
44
  }
58
45
 
59
- console.log();
46
+ this.logger.log('');
47
+ }
48
+ })
49
+ .name('list')
50
+ .description('Show all scheduled tasks and their next run times');
51
+
52
+ register
53
+ .command(async (taskName) => {
54
+ await this.appBoot();
55
+ const app = this.getApp();
56
+ const scheduler = app.make('scheduler');
57
+ const tasks = scheduler.getTasks();
58
+ const task = tasks.find(t => t.jobClass.name === taskName);
59
+
60
+ if (!task) {
61
+ this.error(`Task "${taskName}" not found.`);
62
+ this.info('Available tasks:');
63
+ tasks.forEach(t => this.logger.log(this.style.muted(` - ${t.jobClass.name}`)));
64
+ throw new Error(`Task "${taskName}" not found`);
60
65
  }
61
- }
62
-
63
- process.exit(0);
64
- });
65
-
66
- program
67
- .command('schedule:test <taskName>')
68
- .description('Run a specific scheduled task immediately for testing')
69
- .action(async (taskName) => {
70
- const bootstrapPath = path.resolve(process.cwd(), 'bootstrap/app.js');
71
- if (!fs.existsSync(bootstrapPath)) {
72
- console.error(chalk.red('\n ✖ Not inside a Millas project.\n'));
73
- process.exit(1);
74
- }
75
-
76
- // Boot the app
77
- let app;
78
- try {
79
- app = await require(bootstrapPath);
80
- } catch (e) {
81
- console.error(chalk.red(`\n ✖ Failed to load app: ${e.message}\n`));
82
- process.exit(1);
83
- }
84
-
85
- const scheduler = app.make('scheduler');
86
- const tasks = scheduler.getTasks();
87
- const task = tasks.find(t => t.jobClass.name === taskName);
88
-
89
- if (!task) {
90
- console.error(chalk.red(`\n ✖ Task "${taskName}" not found.\n`));
91
- console.log(chalk.gray(' Available tasks:'));
92
- tasks.forEach(t => console.log(chalk.gray(` - ${t.jobClass.name}`)));
93
- console.log();
94
- process.exit(1);
95
- }
96
66
 
97
- console.log(chalk.blue(`\n ▶ Running ${taskName}...\n`));
67
+ this.logger.log(this.style.primary(`\n ▶ Running ${taskName}...\n`));
98
68
 
99
- try {
100
69
  await scheduler._executeTask(task, new Date());
101
- console.log(chalk.green(` ✔ ${taskName} completed successfully.\n`));
102
- } catch (error) {
103
- console.error(chalk.red(` ✖ ${taskName} failed: ${error.message}\n`));
104
- process.exit(1);
105
- }
106
-
107
- process.exit(0);
108
- });
109
-
110
- program
111
- .command('make:task <name>')
112
- .description('Generate a new scheduled task class')
113
- .action(async (name) => {
114
- const taskName = name.endsWith('Task') ? name : `${name}Task`;
115
- const taskPath = path.resolve(process.cwd(), 'app', 'tasks', `${taskName}.js`);
116
-
117
- // Ensure directory exists
118
- await fs.ensureDir(path.dirname(taskPath));
119
-
120
- // Check if file already exists
121
- if (await fs.pathExists(taskPath)) {
122
- console.error(chalk.red(`\n ✖ Task ${taskName} already exists.\n`));
123
- process.exit(1);
124
- }
125
-
126
- // Generate task class
127
- const template = `'use strict';
128
-
129
- const { Job } = require('millas/core/queue');
130
-
131
- /**
132
- * ${taskName}
133
- *
134
- * Scheduled task that runs automatically based on the schedule defined in routes/schedule.js
135
- *
136
- * Usage in routes/schedule.js:
137
- * Schedule.job(${taskName}).daily().at('09:00');
138
- */
139
- class ${taskName} extends Job {
140
- /**
141
- * Constructor - DI container will inject dependencies automatically
142
- */
143
- constructor(/* inject dependencies here */) {
144
- super();
145
- // Store injected dependencies
146
- }
147
-
148
- /**
149
- * Execute the scheduled task
150
- */
151
- async handle() {
152
- // Implement your scheduled task logic here
153
- console.log('${taskName} is running...');
154
- }
155
-
156
- /**
157
- * Handle task failure (optional)
158
- */
159
- async failed(error) {
160
- console.error('${taskName} failed:', error.message);
70
+ this.success(`${taskName} completed successfully.`);
71
+ })
72
+ .name('test')
73
+ .str('taskName', 'Task name to run')
74
+ .description('Run a specific scheduled task immediately for testing');
161
75
  }
162
76
  }
163
77
 
164
- module.exports = ${taskName};
165
- `;
166
-
167
- await fs.writeFile(taskPath, template);
168
-
169
- console.log(chalk.green(`\n ✔ Task created: ${taskPath}`));
170
- console.log(chalk.gray('\n Next steps:'));
171
- console.log(chalk.gray(` 1. Implement the handle() method in ${taskName}`));
172
- console.log(chalk.gray(` 2. Add the task to routes/schedule.js:`));
173
- console.log(chalk.gray(` Schedule.job(${taskName}).daily().at('09:00');`));
174
- console.log();
175
- });
176
- };
78
+ module.exports = ScheduleCommand;