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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module.exports = `const { Model, fields } = require('millas/src');
|
|
2
|
+
|
|
3
|
+
class {{ name | pascalCase }} extends Model {
|
|
4
|
+
static table = '{{ name | snakeCase | plural }}';
|
|
5
|
+
|
|
6
|
+
static fields = {
|
|
7
|
+
id: fields.id(),
|
|
8
|
+
created_at: fields.timestamp(),
|
|
9
|
+
updated_at: fields.timestamp(),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = {{ name | pascalCase }};
|
|
14
|
+
`;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module.exports = `const { Migration } = require('millas/src');
|
|
2
|
+
|
|
3
|
+
class Create{{ name | pascalCase | plural }}Table extends Migration {
|
|
4
|
+
async up(schema) {
|
|
5
|
+
await schema.createTable('{{ name | snakeCase | plural }}', (table) => {
|
|
6
|
+
table.id();
|
|
7
|
+
table.timestamps();
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async down(schema) {
|
|
12
|
+
await schema.dropTable('{{ name | snakeCase | plural }}');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = Create{{ name | pascalCase | plural }}Table;
|
|
17
|
+
`;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "model",
|
|
3
|
+
"description": "Generate a new model",
|
|
4
|
+
"arguments": [
|
|
5
|
+
{
|
|
6
|
+
"name": "name",
|
|
7
|
+
"type": "string",
|
|
8
|
+
"required": true,
|
|
9
|
+
"description": "Model name"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"options": [
|
|
13
|
+
{
|
|
14
|
+
"name": "migration",
|
|
15
|
+
"type": "boolean",
|
|
16
|
+
"description": "Generate migration file"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"files": [
|
|
20
|
+
{
|
|
21
|
+
"template": "default.template.js",
|
|
22
|
+
"output": "app/models/{{ name | pascalCase }}.js"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"template": "migration.template.js",
|
|
26
|
+
"output": "database/migrations/{{ timestamp }}_create_{{ name | snakeCase | plural }}_table.js",
|
|
27
|
+
"condition": "migration"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "service",
|
|
3
|
+
"description": "Generate a new service",
|
|
4
|
+
"arguments": [
|
|
5
|
+
{
|
|
6
|
+
"name": "name",
|
|
7
|
+
"type": "string",
|
|
8
|
+
"required": true,
|
|
9
|
+
"description": "Service name"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"options": [],
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"template": "default.template.js",
|
|
16
|
+
"output": "app/services/{{ name | pascalCase }}Service.js"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module.exports = `const { Shape } = require('millas/src');
|
|
2
|
+
|
|
3
|
+
class {{ name | pascalCase }}Shape extends Shape {
|
|
4
|
+
static rules = {
|
|
5
|
+
// Define validation rules here
|
|
6
|
+
// Example: name: v => v.string().required(),
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = {{ name | pascalCase }}Shape;
|
|
11
|
+
`;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shape",
|
|
3
|
+
"description": "Generate a new validation shape",
|
|
4
|
+
"arguments": [
|
|
5
|
+
{
|
|
6
|
+
"name": "name",
|
|
7
|
+
"type": "string",
|
|
8
|
+
"required": true,
|
|
9
|
+
"description": "Shape name"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"options": [],
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"template": "default.template.js",
|
|
16
|
+
"output": "app/shapes/{{ name | pascalCase }}Shape.js"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -14,6 +14,7 @@ class BaseValidator {
|
|
|
14
14
|
* @param {string} [typeError] — message shown when the value fails the base type check
|
|
15
15
|
*/
|
|
16
16
|
constructor(typeError) {
|
|
17
|
+
this._type = undefined;
|
|
17
18
|
this._typeError = typeError || null;
|
|
18
19
|
this._required = false;
|
|
19
20
|
this._requiredMsg = null;
|
|
@@ -162,6 +163,8 @@ class BaseValidator {
|
|
|
162
163
|
*/
|
|
163
164
|
async run(value, key, allData = {}) {
|
|
164
165
|
const label = this._fieldLabel(key);
|
|
166
|
+
if (this._type === undefined || this._type === null)
|
|
167
|
+
throw new Error(`[Millas] Validator for "${key}" has no known type.`);
|
|
165
168
|
|
|
166
169
|
// ── Apply default ───────────────────────────────────────────────────────
|
|
167
170
|
if (this._isEmpty(value) && this._defaultValue !== undefined) {
|
package/src/validation/types.js
CHANGED
|
@@ -39,8 +39,8 @@ const { BaseValidator, _titleCase } = require('./BaseValidator');
|
|
|
39
39
|
// ── StringValidator ────────────────────────────────────────────────────────────
|
|
40
40
|
|
|
41
41
|
class StringValidator extends BaseValidator {
|
|
42
|
-
constructor() {
|
|
43
|
-
super('Must be a string');
|
|
42
|
+
constructor(message) {
|
|
43
|
+
super(message || 'Must be a string');
|
|
44
44
|
this._type = 'string';
|
|
45
45
|
this._minLen = null;
|
|
46
46
|
this._maxLen = null;
|
|
@@ -479,7 +479,7 @@ function _parseSize(str) {
|
|
|
479
479
|
|
|
480
480
|
// ── Factory functions ─────────────────────────────────────────────────────────
|
|
481
481
|
|
|
482
|
-
const string = () => new StringValidator();
|
|
482
|
+
const string = (message) => new StringValidator(message);
|
|
483
483
|
const email = () => new EmailValidator();
|
|
484
484
|
const number = () => new NumberValidator();
|
|
485
485
|
const boolean = () => new BooleanValidator();
|