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.
- 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/container/Application.js +2 -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/src/scaffold/maker.js
CHANGED
|
@@ -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
|
-
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
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(
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
346
|
+
let content;
|
|
347
|
+
|
|
348
|
+
if (isGroup) {
|
|
349
|
+
// Generate subcommand group template
|
|
350
|
+
content = `'use strict';
|
|
358
351
|
|
|
359
|
-
const {
|
|
352
|
+
const { BaseCommand } = require('millas/console');
|
|
360
353
|
|
|
361
354
|
/**
|
|
362
355
|
* ${className}
|
|
363
356
|
*
|
|
364
|
-
*
|
|
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
|
|
367
|
-
|
|
368
|
-
static
|
|
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
|
-
//
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
//
|
|
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
|
-
// {
|
|
378
|
-
// {
|
|
421
|
+
// { flags: '--dry-run', description: 'Preview without making changes' },
|
|
422
|
+
// { flags: '--limit <n>', description: 'Max items', defaultValue: '50' },
|
|
379
423
|
// ];
|
|
380
424
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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,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
|
+
}
|