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
@@ -325,74 +325,134 @@ async function makeShape(name) {
325
325
  return write(filePath, lines.join('\n'));
326
326
  }
327
327
  async function makeCommand(name) {
328
- // If name contains ':' it's a namespaced signature like 'email:SendDigest'.
329
- // Split off the namespace prefix and use only the last segment for the
330
- // class name / filename, but keep the full input as the signature.
331
- const parts = name.split(':');
332
- const basePart = parts[parts.length - 1]; // e.g. 'SendDigest'
333
- const cleanBase = basePart.replace(/Command$/i, ''); // strip trailing Command
334
- const className = pascalCase(cleanBase) + 'Command'; // e.g. 'SendDigestCommand'
335
-
336
- // Build signature from the full name:
337
- // 'email:SendDigest' → 'email:digest'
338
- // 'SendDigest' → 'send-digest'
339
- // 'send-digest' → 'send-digest'
340
- const signatureParts = name
328
+ // Convert command signature to class name
329
+ // Examples:
330
+ // 'user' → UserCommand (subcommand group)
331
+ // 'send:newsletter' → SendNewsletterCommand (simple)
332
+ // 'email' → EmailCommand (subcommand group)
333
+
334
+ const isGroup = !name.includes(':');
335
+
336
+ const parts = name
341
337
  .replace(/Command$/i, '')
342
338
  .split(':')
343
- .map((seg, i, arr) => {
344
- // Last segment: strip camelCase — 'SendDigest' → 'send-digest'
345
- if (i === arr.length - 1) {
346
- return seg
347
- .replace(/([a-z])([A-Z])/g, '$1-$2')
348
- .toLowerCase();
349
- }
350
- // Namespace segments: lowercase as-is
351
- return seg.toLowerCase();
352
- });
353
- const signature = signatureParts.join(':');
339
+ .map(part => pascalCase(part));
340
+
341
+ const className = parts.join('') + 'Command';
342
+ const signature = name.toLowerCase();
354
343
 
355
344
  const filePath = resolveAppPath('app/commands', `${className}.js`);
356
345
 
357
- const content = `'use strict';
346
+ let content;
347
+
348
+ if (isGroup) {
349
+ // Generate subcommand group template
350
+ content = `'use strict';
358
351
 
359
- const { Command } = require('millas/console');
352
+ const { BaseCommand } = require('millas/console');
360
353
 
361
354
  /**
362
355
  * ${className}
363
356
  *
364
- * Run with: millas call ${signature}
357
+ * Command group - methods become subcommands automatically.
358
+ * Class name auto-derived: ${className} → ${signature}:*
359
+ *
360
+ * Examples:
361
+ * millas ${signature}:create --name <value>
362
+ * millas ${signature}:update <id> --name <value>
363
+ * millas ${signature}:delete <id>
365
364
  */
366
- class ${className} extends Command {
367
- static signature = '${signature}';
368
- static description = 'Description of ${signature}';
365
+ class ${className} extends BaseCommand {
366
+ // Optional: Add descriptions for subcommands
367
+ // static commands = {
368
+ // create: 'Create a new ${signature}',
369
+ // update: 'Update a ${signature}',
370
+ // delete: 'Delete a ${signature}',
371
+ // };
372
+
373
+ // Private helper (underscore = not a command)
374
+ // async _get${pascalCase(signature)}(id) {
375
+ // return this.container.resolve('Database')
376
+ // .table('${signature}s').find(id);
377
+ // }
378
+
379
+ // Subcommand: Only options
380
+ async create({ name }) {
381
+ // millas ${signature}:create --name <value>
382
+ this.info('Creating ${signature}...');
383
+ this.success('${pascalCase(signature)} created!');
384
+ }
369
385
 
370
- // Optional: positional arguments
371
- // static args = [
372
- // { name: 'target', description: 'The target to act on', default: 'all' },
373
- // ];
386
+ // Subcommand: Positional + options
387
+ async update(id, { name }) {
388
+ // millas ${signature}:update <id> --name <value>
389
+ this.info(\`Updating ${signature} \${id}...\`);
390
+ this.success('${pascalCase(signature)} updated!');
391
+ }
374
392
 
375
- // Optional: named options / flags
393
+ // Subcommand: Only positional
394
+ async delete(id) {
395
+ // millas ${signature}:delete <id>
396
+ this.info(\`Deleting ${signature} \${id}...\`);
397
+ this.success('${pascalCase(signature)} deleted!');
398
+ }
399
+ }
400
+
401
+ module.exports = ${className};
402
+ `;
403
+ } else {
404
+ // Generate simple command template
405
+ content = `'use strict';
406
+
407
+ const { BaseCommand } = require('millas/console');
408
+
409
+ /**
410
+ * ${className}
411
+ *
412
+ * Simple command - uses handle() method.
413
+ * Command name auto-derived: ${className} → ${signature}
414
+ * Run with: millas ${signature}
415
+ */
416
+ class ${className} extends BaseCommand {
417
+ static description = 'Description of ${signature}';
418
+
419
+ // Optional: simple options (declarative)
376
420
  // static options = [
377
- // { flag: '--dry-run', description: 'Preview without making changes' },
378
- // { flag: '--limit <n>', description: 'Max items to process', default: '50' },
421
+ // { flags: '--dry-run', description: 'Preview without making changes' },
422
+ // { flags: '--limit <n>', description: 'Max items', defaultValue: '50' },
379
423
  // ];
380
424
 
381
- async handle() {
382
- // const target = this.argument('target');
383
- // const limit = this.option('limit');
384
- // const dry = this.option('dryRun');
425
+ // Optional: custom arguments (advanced)
426
+ // addArguments(parser) {
427
+ // parser
428
+ // .argument('<file>', 'Input file path')
429
+ // .argument('[output]', 'Output file path', 'output.txt')
430
+ // .option('--force', 'Force overwrite');
431
+ // }
432
+
433
+ // Optional: validate inputs before execution
434
+ // async validate(...args) {
435
+ // if (!args[0]) {
436
+ // throw new Error('Missing required argument');
437
+ // }
438
+ // }
439
+
440
+ async handle(options) {
441
+ // Access options: options.dryRun, options.limit, etc.
442
+ // Access DI container: this.container.resolve('ServiceName')
443
+ // Logging helpers: this.info(), this.success(), this.warn(), this.error()
385
444
 
386
445
  this.info('Running ${signature}...');
387
446
 
388
447
  // Your command logic here
389
448
 
390
- this.success('Done.');
449
+ this.success('Done!');
391
450
  }
392
451
  }
393
452
 
394
453
  module.exports = ${className};
395
454
  `;
455
+ }
396
456
 
397
457
  return write(filePath, content);
398
458
  }
@@ -0,0 +1,28 @@
1
+ const SchematicEngine = require('./SchematicEngine');
2
+
3
+ class Collection {
4
+ constructor(name, templatesDir) {
5
+ this.name = name;
6
+ this.engine = new SchematicEngine(templatesDir);
7
+ this.schematics = new Map();
8
+ }
9
+
10
+ add(name, schematicName) {
11
+ this.schematics.set(name, schematicName);
12
+ return this;
13
+ }
14
+
15
+ async generate(name, data, options = {}) {
16
+ const schematicName = this.schematics.get(name);
17
+ if (!schematicName) {
18
+ throw new Error(`Schematic '${name}' not found in collection '${this.name}'`);
19
+ }
20
+ return await this.engine.generate(schematicName, data, options);
21
+ }
22
+
23
+ list() {
24
+ return Array.from(this.schematics.keys());
25
+ }
26
+ }
27
+
28
+ module.exports = Collection;
@@ -0,0 +1,122 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const Template = require('./Template');
4
+
5
+ class SchematicEngine {
6
+ constructor(templatesDir) {
7
+ this.templatesDir = templatesDir;
8
+ }
9
+
10
+ load(name) {
11
+ const dir = path.join(this.templatesDir, name);
12
+ const schemaPath = path.join(dir, 'schema.json');
13
+
14
+ if (!fs.existsSync(schemaPath)) {
15
+ throw new Error(`Schematic not found: ${name}`);
16
+ }
17
+
18
+ const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
19
+ return { schema, dir };
20
+ }
21
+
22
+ async generate(name, data, options = {}) {
23
+ const { schema, dir } = this.load(name);
24
+
25
+ this.#validate(schema, data, options);
26
+
27
+ const results = [];
28
+ const allData = { ...data, ...options };
29
+
30
+ for (const fileConfig of schema.files) {
31
+ // Skip conditional files if condition not met
32
+ if (fileConfig.condition && !options[fileConfig.condition]) {
33
+ continue;
34
+ }
35
+
36
+ const templateFile = this.#selectTemplate(schema, fileConfig, options);
37
+ const templatePath = path.join(dir, templateFile);
38
+
39
+ if (!fs.existsSync(templatePath)) {
40
+ throw new Error(`Template file not found: ${templateFile}`);
41
+ }
42
+
43
+ const templateContent = require(templatePath);
44
+ const template = new Template(templateContent, { helpers: schema.helpers || {} });
45
+ const rendered = template.render(allData);
46
+
47
+ const outputPath = this.#resolveOutputPath(fileConfig.output, allData);
48
+
49
+ if (fs.existsSync(outputPath) && !options.force) {
50
+ throw new Error(`File already exists: ${outputPath}. Use --force to overwrite.`);
51
+ }
52
+
53
+ this.#ensureDir(path.dirname(outputPath));
54
+ fs.writeFileSync(outputPath, rendered);
55
+
56
+ results.push({ path: outputPath, content: rendered });
57
+ }
58
+
59
+ return results.length === 1 ? results[0] : results;
60
+ }
61
+
62
+ #validate(schema, data, options) {
63
+ for (const arg of schema.arguments || []) {
64
+ const value = data[arg.name];
65
+
66
+ if (arg.required && !value) {
67
+ throw new Error(`Missing required argument: ${arg.name}`);
68
+ }
69
+
70
+ if (value && arg.type) {
71
+ const actualType = typeof value;
72
+ if (actualType !== arg.type) {
73
+ throw new Error(`Argument '${arg.name}' must be of type ${arg.type}, got ${actualType}`);
74
+ }
75
+ }
76
+
77
+ if (value && arg.enum && !arg.enum.includes(value)) {
78
+ throw new Error(`Argument '${arg.name}' must be one of: ${arg.enum.join(', ')}`);
79
+ }
80
+ }
81
+
82
+ for (const opt of schema.options || []) {
83
+ const value = options[opt.name];
84
+
85
+ if (value !== undefined && opt.type) {
86
+ const actualType = typeof value;
87
+ if (actualType !== opt.type) {
88
+ throw new Error(`Option '${opt.name}' must be of type ${opt.type}, got ${actualType}`);
89
+ }
90
+ }
91
+
92
+ if (value && opt.enum && !opt.enum.includes(value)) {
93
+ throw new Error(`Option '${opt.name}' must be one of: ${opt.enum.join(', ')}`);
94
+ }
95
+ }
96
+ }
97
+
98
+ #selectTemplate(schema, fileConfig, options) {
99
+ for (const [key, value] of Object.entries(options)) {
100
+ if (value === true) {
101
+ const variant = `${key}.template.js`;
102
+ if (fs.existsSync(path.join(this.templatesDir, schema.name, variant))) {
103
+ return variant;
104
+ }
105
+ }
106
+ }
107
+ return fileConfig.template;
108
+ }
109
+
110
+ #resolveOutputPath(pattern, allData) {
111
+ const template = new Template(pattern);
112
+ return path.join(process.cwd(), template.render(allData));
113
+ }
114
+
115
+ #ensureDir(dir) {
116
+ if (!fs.existsSync(dir)) {
117
+ fs.mkdirSync(dir, { recursive: true });
118
+ }
119
+ }
120
+ }
121
+
122
+ module.exports = SchematicEngine;
@@ -0,0 +1,99 @@
1
+ class Template {
2
+ constructor(content, options = {}) {
3
+ this.content = content;
4
+ this.helpers = options.helpers || {};
5
+ }
6
+
7
+ render(data) {
8
+ let result = this.content;
9
+
10
+ // Handle conditionals: {{#if key}}...{{else}}...{{/if}}
11
+ result = this.#processConditionals(result, data);
12
+
13
+ // Handle loops: {{#each key}}...{{/each}}
14
+ result = this.#processLoops(result, data);
15
+
16
+ // Handle variables: {{ name }}, {{ name | filter | filter2 }}
17
+ result = result.replace(/\{\{\s*([^}#/|]+)(\s*\|\s*([^}]+))?\s*\}\}/g,
18
+ (match, key, _, filters) => {
19
+ let value = data[key.trim()];
20
+ if (value === undefined || value === null) return '';
21
+ if (filters) value = this.#applyFilters(value, filters.trim());
22
+ return value;
23
+ }
24
+ );
25
+
26
+ return result;
27
+ }
28
+
29
+ #processConditionals(content, data) {
30
+ // Handle {{#if !key}} negation
31
+ content = content.replace(/\{\{#if\s+!(\w+)\}\}([\s\S]*?)(?:\{\{else\}\}([\s\S]*?))?\{\{\/if\}\}/g,
32
+ (match, key, trueBranch, falseBranch = '') => {
33
+ return !data[key] ? trueBranch : falseBranch;
34
+ }
35
+ );
36
+
37
+ // Handle {{#if key}}
38
+ content = content.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)(?:\{\{else\}\}([\s\S]*?))?\{\{\/if\}\}/g,
39
+ (match, key, trueBranch, falseBranch = '') => {
40
+ return data[key] ? trueBranch : falseBranch;
41
+ }
42
+ );
43
+
44
+ return content;
45
+ }
46
+
47
+ #processLoops(content, data) {
48
+ return content.replace(/\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g,
49
+ (match, key, template) => {
50
+ const items = data[key];
51
+ if (!Array.isArray(items)) return '';
52
+ return items.map(item => {
53
+ const itemTemplate = new Template(template);
54
+ return itemTemplate.render(typeof item === 'object' ? item : { item });
55
+ }).join('');
56
+ }
57
+ );
58
+ }
59
+
60
+ #applyFilters(value, filtersStr) {
61
+ const filterNames = filtersStr.split('|').map(f => f.trim());
62
+ return filterNames.reduce((val, filterName) => this.#applyFilter(val, filterName), value);
63
+ }
64
+
65
+ #applyFilter(value, filter) {
66
+ if (value === undefined || value === null) return value;
67
+
68
+ const filters = {
69
+ pascalCase: str => String(str).replace(/(?:^|[-_])(\w)/g, (_, c) => c.toUpperCase()).replace(/[-_]/g, ''),
70
+ camelCase: str => {
71
+ const pascal = String(str).replace(/(?:^|[-_])(\w)/g, (_, c) => c.toUpperCase()).replace(/[-_]/g, '');
72
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
73
+ },
74
+ snakeCase: str => String(str).replace(/[A-Z]/g, (c, i) => (i ? '_' : '') + c.toLowerCase()).replace(/[-\s]/g, '_'),
75
+ kebabCase: str => String(str).replace(/[A-Z]/g, (c, i) => (i ? '-' : '') + c.toLowerCase()).replace(/[_\s]/g, '-'),
76
+ plural: str => {
77
+ const s = String(str);
78
+ if (s.endsWith('s')) return s;
79
+ if (s.endsWith('y')) return s.slice(0, -1) + 'ies';
80
+ return s + 's';
81
+ },
82
+ singular: str => {
83
+ const s = String(str);
84
+ if (s.endsWith('ies')) return s.slice(0, -3) + 'y';
85
+ if (s.endsWith('s')) return s.slice(0, -1);
86
+ return s;
87
+ },
88
+ lower: str => String(str).toLowerCase(),
89
+ upper: str => String(str).toUpperCase(),
90
+ };
91
+
92
+ const customFilter = this.helpers[filter];
93
+ if (customFilter) return customFilter(value);
94
+
95
+ return filters[filter]?.(value) ?? value;
96
+ }
97
+ }
98
+
99
+ module.exports = Template;
@@ -0,0 +1,7 @@
1
+ const Template = require('./Template');
2
+ const SchematicEngine = require('./SchematicEngine');
3
+
4
+ module.exports = {
5
+ Template,
6
+ SchematicEngine,
7
+ };
@@ -0,0 +1,14 @@
1
+ module.exports = `const { BaseCommand } = require('millas/src/console');
2
+
3
+ class {{ name | pascalCase }}Command extends BaseCommand {
4
+ static signature = '{{ name | kebabCase }}';
5
+ static description = '{{ name | pascalCase }} command description';
6
+
7
+ async run(args, opts) {
8
+ this.info('Running {{ name | pascalCase }}Command');
9
+ // Command logic here
10
+ }
11
+ }
12
+
13
+ module.exports = {{ name | pascalCase }}Command;
14
+ `;
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "command",
3
+ "description": "Generate a new CLI command",
4
+ "arguments": [
5
+ {
6
+ "name": "name",
7
+ "type": "string",
8
+ "required": true,
9
+ "description": "Command name"
10
+ }
11
+ ],
12
+ "options": [],
13
+ "files": [
14
+ {
15
+ "template": "default.template.js",
16
+ "output": "app/commands/{{ name | pascalCase }}Command.js"
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,10 @@
1
+ module.exports = `const { Controller } = require('millas/src');
2
+
3
+ class {{ name | pascalCase }}Controller extends Controller {
4
+ async handle(req, res) {
5
+ res.json({ message: 'Hello from {{ name | pascalCase }}Controller' });
6
+ }
7
+ }
8
+
9
+ module.exports = {{ name | pascalCase }}Controller;
10
+ `;
@@ -0,0 +1,59 @@
1
+ module.exports = `const { Controller } = require('millas/src');
2
+ {{#if model}}
3
+ const {{ model | pascalCase }} = require('../models/{{ model | pascalCase }}');
4
+ {{/if}}
5
+
6
+ class {{ name | pascalCase }}Controller extends Controller {
7
+ async index(req, res) {
8
+ {{#if model}}
9
+ const items = await {{ model | pascalCase }}.all();
10
+ res.json(items);
11
+ {{else}}
12
+ res.json({ message: 'List all {{ name | plural }}' });
13
+ {{/if}}
14
+ }
15
+
16
+ async show(req, res) {
17
+ {{#if model}}
18
+ const item = await {{ model | pascalCase }}.find(req.params.id);
19
+ if (!item) return res.status(404).json({ error: '{{ model | pascalCase }} not found' });
20
+ res.json(item);
21
+ {{else}}
22
+ res.json({ message: 'Show {{ name }} with id: ' + req.params.id });
23
+ {{/if}}
24
+ }
25
+
26
+ async store(req, res) {
27
+ {{#if model}}
28
+ const item = await {{ model | pascalCase }}.create(req.body);
29
+ res.status(201).json(item);
30
+ {{else}}
31
+ res.status(201).json({ message: 'Create new {{ name }}', data: req.body });
32
+ {{/if}}
33
+ }
34
+
35
+ async update(req, res) {
36
+ {{#if model}}
37
+ const item = await {{ model | pascalCase }}.find(req.params.id);
38
+ if (!item) return res.status(404).json({ error: '{{ model | pascalCase }} not found' });
39
+ await item.update(req.body);
40
+ res.json(item);
41
+ {{else}}
42
+ res.json({ message: 'Update {{ name }} with id: ' + req.params.id, data: req.body });
43
+ {{/if}}
44
+ }
45
+
46
+ async destroy(req, res) {
47
+ {{#if model}}
48
+ const item = await {{ model | pascalCase }}.find(req.params.id);
49
+ if (!item) return res.status(404).json({ error: '{{ model | pascalCase }} not found' });
50
+ await item.delete();
51
+ res.status(204).send();
52
+ {{else}}
53
+ res.status(204).send();
54
+ {{/if}}
55
+ }
56
+ }
57
+
58
+ module.exports = {{ name | pascalCase }}Controller;
59
+ `;
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "controller",
3
+ "description": "Generate a new controller",
4
+ "arguments": [
5
+ {
6
+ "name": "name",
7
+ "type": "string",
8
+ "required": true,
9
+ "description": "Controller name"
10
+ }
11
+ ],
12
+ "options": [
13
+ {
14
+ "name": "resource",
15
+ "type": "boolean",
16
+ "description": "Generate resource controller with CRUD methods"
17
+ },
18
+ {
19
+ "name": "model",
20
+ "type": "string",
21
+ "description": "Associated model name"
22
+ }
23
+ ],
24
+ "files": [
25
+ {
26
+ "template": "default.template.js",
27
+ "output": "app/controllers/{{ name | pascalCase }}Controller.js"
28
+ }
29
+ ]
30
+ }
@@ -0,0 +1,11 @@
1
+ module.exports = `const { Job } = require('millas/src');
2
+
3
+ class {{ name | pascalCase }}Job extends Job {
4
+ async handle(data) {
5
+ // Job logic here
6
+ this.logger.info('Processing {{ name | pascalCase }}Job', data);
7
+ }
8
+ }
9
+
10
+ module.exports = {{ name | pascalCase }}Job;
11
+ `;
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "job",
3
+ "description": "Generate a new job",
4
+ "arguments": [
5
+ {
6
+ "name": "name",
7
+ "type": "string",
8
+ "required": true,
9
+ "description": "Job name"
10
+ }
11
+ ],
12
+ "options": [],
13
+ "files": [
14
+ {
15
+ "template": "default.template.js",
16
+ "output": "app/jobs/{{ name | pascalCase }}Job.js"
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,11 @@
1
+ module.exports = `const { Middleware } = require('millas/src');
2
+
3
+ class {{ name | pascalCase }}Middleware extends Middleware {
4
+ async handle(req, res, next) {
5
+ // Add your middleware logic here
6
+ next();
7
+ }
8
+ }
9
+
10
+ module.exports = {{ name | pascalCase }}Middleware;
11
+ `;
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "middleware",
3
+ "description": "Generate a new middleware",
4
+ "arguments": [
5
+ {
6
+ "name": "name",
7
+ "type": "string",
8
+ "required": true,
9
+ "description": "Middleware name"
10
+ }
11
+ ],
12
+ "options": [],
13
+ "files": [
14
+ {
15
+ "template": "default.template.js",
16
+ "output": "app/middleware/{{ name | pascalCase }}Middleware.js"
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,14 @@
1
+ module.exports = `const { Migration } = require('millas/src');
2
+
3
+ class {{ name | pascalCase }} extends Migration {
4
+ async up(schema) {
5
+ // Define schema changes here
6
+ }
7
+
8
+ async down(schema) {
9
+ // Reverse schema changes here
10
+ }
11
+ }
12
+
13
+ module.exports = {{ name | pascalCase }};
14
+ `;
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "migration",
3
+ "description": "Generate a new migration",
4
+ "arguments": [
5
+ {
6
+ "name": "name",
7
+ "type": "string",
8
+ "required": true,
9
+ "description": "Migration name"
10
+ }
11
+ ],
12
+ "options": [],
13
+ "files": [
14
+ {
15
+ "template": "default.template.js",
16
+ "output": "database/migrations/{{ timestamp }}_{{ name | snakeCase }}.js"
17
+ }
18
+ ]
19
+ }