@currentjs/gen 0.5.1 → 0.5.2
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/CHANGELOG.md +9 -0
- package/README.md +374 -996
- package/dist/cli.js +28 -10
- package/dist/commands/createModel.d.ts +1 -0
- package/dist/commands/createModel.js +764 -0
- package/dist/commands/createModule.js +13 -0
- package/dist/commands/generateAll.d.ts +1 -0
- package/dist/commands/generateAll.js +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/{createApp.js → init.js} +2 -2
- package/dist/commands/migrateCommit.js +33 -68
- package/dist/generators/domainLayerGenerator.js +34 -1
- package/dist/generators/templateGenerator.d.ts +1 -0
- package/dist/generators/templateGenerator.js +8 -2
- package/dist/generators/templates/data/cursorRulesTemplate +11 -755
- package/dist/types/configTypes.d.ts +5 -0
- package/dist/utils/migrationUtils.d.ts +9 -19
- package/dist/utils/migrationUtils.js +80 -110
- package/dist/utils/promptUtils.d.ts +37 -0
- package/dist/utils/promptUtils.js +149 -0
- package/package.json +1 -1
- package/dist/commands/createApp.d.ts +0 -1
- package/howto.md +0 -667
|
@@ -38,6 +38,7 @@ const fs = __importStar(require("fs"));
|
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const cliUtils_1 = require("../utils/cliUtils");
|
|
40
40
|
const yaml_1 = require("yaml");
|
|
41
|
+
const colors_1 = require("../utils/colors");
|
|
41
42
|
function moduleYamlTemplate(moduleName) {
|
|
42
43
|
const entityName = moduleName.charAt(0).toUpperCase() + moduleName.slice(1);
|
|
43
44
|
const lower = moduleName.charAt(0).toLowerCase() + moduleName.slice(1);
|
|
@@ -226,4 +227,16 @@ function handleCreateModule(name) {
|
|
|
226
227
|
appConfig.modules[moduleKey] = { path: moduleYamlRel };
|
|
227
228
|
}
|
|
228
229
|
fs.writeFileSync(appYamlPath, (0, yaml_1.stringify)(appConfig), 'utf8');
|
|
230
|
+
const output = `
|
|
231
|
+
${colors_1.colors.green(`Module ${colors_1.colors.bold(name)} has been created`)}
|
|
232
|
+
|
|
233
|
+
Run command ${colors_1.colors.green(colors_1.colors.bold(`current create model ${name}:modelname`))}
|
|
234
|
+
where ${colors_1.colors.italic(colors_1.colors.yellow('modelname'))} is name of the model in your module.
|
|
235
|
+
At the end, that command will suggest you to create CRUD actions for the model and further steps.
|
|
236
|
+
You can run this command as many times as needed.
|
|
237
|
+
|
|
238
|
+
Alternatively, you may consider modifying module's config manually, and then run ${colors_1.colors.bold(colors_1.colors.green('current generate'))}
|
|
239
|
+
${colors_1.colors.gray('config:')} ${colors_1.colors.cyan(colors_1.colors.italic(moduleYamlFile))}
|
|
240
|
+
`;
|
|
241
|
+
console.log(output);
|
|
229
242
|
}
|
|
@@ -94,7 +94,7 @@ async function handleGenerateAll(yamlPathArg, _outArg, moduleName, opts) {
|
|
|
94
94
|
// eslint-disable-next-line no-await-in-loop
|
|
95
95
|
await controllerGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts);
|
|
96
96
|
// eslint-disable-next-line no-await-in-loop
|
|
97
|
-
await templateGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts);
|
|
97
|
+
await templateGen.generateAndSaveFiles(moduleYamlPath, moduleDir, { force: opts === null || opts === void 0 ? void 0 : opts.force, skipOnConflict: opts === null || opts === void 0 ? void 0 : opts.skip, onlyIfMissing: !(opts === null || opts === void 0 ? void 0 : opts.withTemplates) });
|
|
98
98
|
// Find srcDir by probing upward for app.ts
|
|
99
99
|
let probeDir = moduleDir;
|
|
100
100
|
let srcDir = null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function handleInit(rawName?: string): void;
|
|
@@ -33,11 +33,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.
|
|
36
|
+
exports.handleInit = handleInit;
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
38
|
const cliUtils_1 = require("../utils/cliUtils");
|
|
39
39
|
const appTemplates_1 = require("../generators/templates/appTemplates");
|
|
40
|
-
function
|
|
40
|
+
function handleInit(rawName) {
|
|
41
41
|
const targetRoot = rawName ? (0, cliUtils_1.toAbsolute)(rawName) : process.cwd();
|
|
42
42
|
(0, cliUtils_1.ensureDir)(targetRoot);
|
|
43
43
|
// Basic structure using constants
|
|
@@ -39,70 +39,41 @@ const path = __importStar(require("path"));
|
|
|
39
39
|
const yaml_1 = require("yaml");
|
|
40
40
|
const colors_1 = require("../utils/colors");
|
|
41
41
|
const cliUtils_1 = require("../utils/cliUtils");
|
|
42
|
+
const commandUtils_1 = require("../utils/commandUtils");
|
|
43
|
+
const configTypes_1 = require("../types/configTypes");
|
|
42
44
|
const migrationUtils_1 = require("../utils/migrationUtils");
|
|
43
|
-
function
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const projectRoot = path.dirname(
|
|
47
|
-
const
|
|
45
|
+
function collectAggregatesFromModules(appYamlPath) {
|
|
46
|
+
const appConfig = (0, commandUtils_1.loadAppConfig)(appYamlPath);
|
|
47
|
+
const moduleEntries = (0, commandUtils_1.getModuleEntries)(appConfig);
|
|
48
|
+
const projectRoot = path.dirname(appYamlPath);
|
|
49
|
+
const allAggregates = {};
|
|
48
50
|
const sources = [];
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
for (const entry of Object.values(config.modules)) {
|
|
58
|
-
const modulePath = entry.path;
|
|
59
|
-
if (!modulePath)
|
|
60
|
-
continue;
|
|
61
|
-
const moduleYamlPath = path.isAbsolute(modulePath)
|
|
62
|
-
? modulePath
|
|
63
|
-
: path.resolve(projectRoot, modulePath);
|
|
64
|
-
if (fs.existsSync(moduleYamlPath)) {
|
|
65
|
-
const moduleYamlContent = fs.readFileSync(moduleYamlPath, 'utf8');
|
|
66
|
-
const moduleConfig = (0, yaml_1.parse)(moduleYamlContent);
|
|
67
|
-
if (moduleConfig.models) {
|
|
68
|
-
allModels.push(...moduleConfig.models);
|
|
69
|
-
moduleCount++;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
if (moduleCount > 0) {
|
|
74
|
-
sources.push(`app.yaml modules section (${moduleCount} module(s))`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
// Also check for module YAMLs in src/modules/*/module.yaml (as fallback)
|
|
78
|
-
const modulesDir = path.join(projectRoot, 'src', 'modules');
|
|
79
|
-
if (fs.existsSync(modulesDir)) {
|
|
80
|
-
const moduleFolders = fs.readdirSync(modulesDir).filter(f => {
|
|
81
|
-
const stat = fs.statSync(path.join(modulesDir, f));
|
|
82
|
-
return stat.isDirectory();
|
|
83
|
-
});
|
|
84
|
-
let moduleYamlCount = 0;
|
|
85
|
-
for (const moduleFolder of moduleFolders) {
|
|
86
|
-
const moduleYamlPath = path.join(modulesDir, moduleFolder, 'module.yaml');
|
|
87
|
-
if (fs.existsSync(moduleYamlPath)) {
|
|
88
|
-
const moduleYamlContent = fs.readFileSync(moduleYamlPath, 'utf8');
|
|
89
|
-
const moduleConfig = (0, yaml_1.parse)(moduleYamlContent);
|
|
90
|
-
if (moduleConfig.models) {
|
|
91
|
-
allModels.push(...moduleConfig.models);
|
|
92
|
-
moduleYamlCount++;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
51
|
+
for (const entry of moduleEntries) {
|
|
52
|
+
const moduleYamlPath = path.isAbsolute(entry.path)
|
|
53
|
+
? entry.path
|
|
54
|
+
: path.resolve(projectRoot, entry.path);
|
|
55
|
+
if (!fs.existsSync(moduleYamlPath)) {
|
|
56
|
+
// eslint-disable-next-line no-console
|
|
57
|
+
console.warn(colors_1.colors.yellow(` Module YAML not found: ${moduleYamlPath}`));
|
|
58
|
+
continue;
|
|
95
59
|
}
|
|
96
|
-
|
|
97
|
-
|
|
60
|
+
const moduleYamlContent = fs.readFileSync(moduleYamlPath, 'utf8');
|
|
61
|
+
const moduleConfig = (0, yaml_1.parse)(moduleYamlContent);
|
|
62
|
+
if (!(0, configTypes_1.isValidModuleConfig)(moduleConfig)) {
|
|
63
|
+
// eslint-disable-next-line no-console
|
|
64
|
+
console.warn(colors_1.colors.yellow(` Skipping ${moduleYamlPath}: not a valid module config (missing domain/useCases)`));
|
|
65
|
+
continue;
|
|
98
66
|
}
|
|
67
|
+
const aggregates = moduleConfig.domain.aggregates;
|
|
68
|
+
const count = Object.keys(aggregates).length;
|
|
69
|
+
Object.assign(allAggregates, aggregates);
|
|
70
|
+
sources.push(`${entry.name} (${count} aggregate(s))`);
|
|
99
71
|
}
|
|
100
|
-
// Log sources
|
|
101
72
|
if (sources.length > 0) {
|
|
102
73
|
// eslint-disable-next-line no-console
|
|
103
74
|
console.log(colors_1.colors.gray(` Sources: ${sources.join(', ')}`));
|
|
104
75
|
}
|
|
105
|
-
return
|
|
76
|
+
return allAggregates;
|
|
106
77
|
}
|
|
107
78
|
function handleMigrateCommit(yamlPath) {
|
|
108
79
|
try {
|
|
@@ -116,24 +87,21 @@ function handleMigrateCommit(yamlPath) {
|
|
|
116
87
|
console.log(colors_1.colors.gray(` Project root: ${projectRoot}`));
|
|
117
88
|
// eslint-disable-next-line no-console
|
|
118
89
|
console.log(colors_1.colors.gray(` Migrations dir: ${migrationsDir}`));
|
|
119
|
-
// Ensure migrations directory exists
|
|
120
90
|
if (!fs.existsSync(migrationsDir)) {
|
|
121
91
|
fs.mkdirSync(migrationsDir, { recursive: true });
|
|
122
92
|
// eslint-disable-next-line no-console
|
|
123
93
|
console.log(colors_1.colors.green(` ✓ Created migrations directory`));
|
|
124
94
|
}
|
|
125
|
-
// Collect all models from YAML files
|
|
126
95
|
// eslint-disable-next-line no-console
|
|
127
|
-
console.log(colors_1.colors.cyan('\n📋 Collecting
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
96
|
+
console.log(colors_1.colors.cyan('\n📋 Collecting aggregates from all modules...'));
|
|
97
|
+
const currentAggregates = collectAggregatesFromModules(resolvedYamlPath);
|
|
98
|
+
if (Object.keys(currentAggregates).length === 0) {
|
|
130
99
|
// eslint-disable-next-line no-console
|
|
131
|
-
console.log(colors_1.colors.yellow('⚠️ No
|
|
100
|
+
console.log(colors_1.colors.yellow('⚠️ No aggregates found in module configuration.'));
|
|
132
101
|
return;
|
|
133
102
|
}
|
|
134
103
|
// eslint-disable-next-line no-console
|
|
135
|
-
console.log(colors_1.colors.green(`✓ Found ${
|
|
136
|
-
// Load previous state
|
|
104
|
+
console.log(colors_1.colors.green(`✓ Found ${Object.keys(currentAggregates).length} aggregate(s): ${Object.keys(currentAggregates).join(', ')}`));
|
|
137
105
|
const oldState = (0, migrationUtils_1.loadSchemaState)(stateFilePath);
|
|
138
106
|
if (oldState) {
|
|
139
107
|
// eslint-disable-next-line no-console
|
|
@@ -143,16 +111,14 @@ function handleMigrateCommit(yamlPath) {
|
|
|
143
111
|
// eslint-disable-next-line no-console
|
|
144
112
|
console.log(colors_1.colors.cyan('📖 No previous schema state found - will generate initial migration'));
|
|
145
113
|
}
|
|
146
|
-
// Compare schemas and generate SQL
|
|
147
114
|
// eslint-disable-next-line no-console
|
|
148
115
|
console.log(colors_1.colors.cyan('\n🔍 Comparing schemas...'));
|
|
149
|
-
const sqlStatements = (0, migrationUtils_1.compareSchemas)(oldState,
|
|
116
|
+
const sqlStatements = (0, migrationUtils_1.compareSchemas)(oldState, currentAggregates);
|
|
150
117
|
if (sqlStatements.length === 0 || sqlStatements.every(s => s.trim() === '' || s.startsWith('--'))) {
|
|
151
118
|
// eslint-disable-next-line no-console
|
|
152
119
|
console.log(colors_1.colors.yellow('⚠️ No changes detected. Schema is up to date.'));
|
|
153
120
|
return;
|
|
154
121
|
}
|
|
155
|
-
// Generate migration file
|
|
156
122
|
const timestamp = (0, migrationUtils_1.generateTimestamp)();
|
|
157
123
|
const migrationFileName = (0, migrationUtils_1.getMigrationFileName)(timestamp);
|
|
158
124
|
const migrationFilePath = path.join(migrationsDir, migrationFileName);
|
|
@@ -163,9 +129,8 @@ function handleMigrateCommit(yamlPath) {
|
|
|
163
129
|
`;
|
|
164
130
|
const migrationContent = migrationHeader + sqlStatements.join('\n');
|
|
165
131
|
fs.writeFileSync(migrationFilePath, migrationContent);
|
|
166
|
-
// Update state file
|
|
167
132
|
const newState = {
|
|
168
|
-
|
|
133
|
+
aggregates: currentAggregates,
|
|
169
134
|
version: timestamp,
|
|
170
135
|
timestamp: new Date().toISOString()
|
|
171
136
|
};
|
|
@@ -215,12 +215,45 @@ class DomainLayerGenerator {
|
|
|
215
215
|
}`;
|
|
216
216
|
})
|
|
217
217
|
.join('\n');
|
|
218
|
+
// Generate validation logic from field constraints
|
|
219
|
+
// we don't use constraints at models, since we use DTOs (use cases) for validation
|
|
220
|
+
// Model – is a place not for validation, but for business logic!
|
|
221
|
+
// kept this code for reference
|
|
222
|
+
/*
|
|
223
|
+
const validations: string[] = [];
|
|
224
|
+
sortedFields.forEach(([fieldName, fieldConfig]) => {
|
|
225
|
+
const { constraints } = fieldConfig;
|
|
226
|
+
if (!constraints) return;
|
|
227
|
+
|
|
228
|
+
if (constraints.min !== undefined) {
|
|
229
|
+
validations.push(` if (this.${fieldName} < ${constraints.min}) {
|
|
230
|
+
throw new Error('${name}.${fieldName} must be at least ${constraints.min}');
|
|
231
|
+
}`);
|
|
232
|
+
}
|
|
233
|
+
if (constraints.max !== undefined) {
|
|
234
|
+
validations.push(` if (this.${fieldName} > ${constraints.max}) {
|
|
235
|
+
throw new Error('${name}.${fieldName} must be at most ${constraints.max}');
|
|
236
|
+
}`);
|
|
237
|
+
}
|
|
238
|
+
if (constraints.pattern) {
|
|
239
|
+
validations.push(` if (!/${constraints.pattern}/.test(String(this.${fieldName}))) {
|
|
240
|
+
throw new Error('${name}.${fieldName} does not match required pattern');
|
|
241
|
+
}`);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const constructorBody = validations.length > 0
|
|
246
|
+
? `\n this.validate();\n }\n\n private validate(): void {\n${validations.join('\n')}\n }`
|
|
247
|
+
: ' }';
|
|
248
|
+
*/
|
|
249
|
+
const constructorBody = '';
|
|
218
250
|
const rootComment = config.root ? '// Aggregate Root\n' : '';
|
|
219
251
|
const enumTypeDefsCode = enumTypeDefinitions.length > 0 ? enumTypeDefinitions.join('\n') + '\n\n' : '';
|
|
220
252
|
return `${imports ? imports + '\n\n' : ''}${enumTypeDefsCode}${rootComment}export class ${name} {
|
|
221
253
|
public constructor(
|
|
222
254
|
${constructorParamsStr}
|
|
223
|
-
) {
|
|
255
|
+
) {${constructorBody}
|
|
256
|
+
}
|
|
224
257
|
${setterMethods}
|
|
225
258
|
}`;
|
|
226
259
|
}
|
|
@@ -458,16 +458,22 @@ ${options}
|
|
|
458
458
|
return this.generateFromConfig(config);
|
|
459
459
|
}
|
|
460
460
|
async generateAndSaveFiles(yamlFilePath, moduleDir, opts) {
|
|
461
|
+
let isGenerated = false;
|
|
461
462
|
const templatesByName = this.generateFromYamlFile(yamlFilePath);
|
|
462
463
|
const viewsDir = path.join(moduleDir, 'views');
|
|
463
464
|
fs.mkdirSync(viewsDir, { recursive: true });
|
|
464
465
|
for (const [name, content] of Object.entries(templatesByName)) {
|
|
465
466
|
const filePath = path.join(viewsDir, `${name}.html`);
|
|
467
|
+
if ((opts === null || opts === void 0 ? void 0 : opts.onlyIfMissing) && fs.existsSync(filePath))
|
|
468
|
+
continue;
|
|
466
469
|
// eslint-disable-next-line no-await-in-loop
|
|
467
470
|
await (0, generationRegistry_1.writeGeneratedFile)(filePath, content, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skipOnConflict) });
|
|
471
|
+
isGenerated = true;
|
|
472
|
+
}
|
|
473
|
+
if (isGenerated) {
|
|
474
|
+
// eslint-disable-next-line no-console
|
|
475
|
+
console.log('\n' + colors_1.colors.green('Template files generated successfully!') + '\n');
|
|
468
476
|
}
|
|
469
|
-
// eslint-disable-next-line no-console
|
|
470
|
-
console.log('\n' + colors_1.colors.green('Template files generated successfully!') + '\n');
|
|
471
477
|
}
|
|
472
478
|
}
|
|
473
479
|
exports.TemplateGenerator = TemplateGenerator;
|