generate-ui-cli 2.2.0 → 2.3.0
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/README.md +177 -35
- package/dist/commands/angular.js +343 -57
- package/dist/commands/generate.js +242 -30
- package/dist/commands/login.js +87 -25
- package/dist/commands/merge.js +201 -0
- package/dist/generators/angular/feature.generator.js +2262 -637
- package/dist/generators/angular/menu.generator.js +1 -1
- package/dist/generators/angular/routes.generator.js +8 -3
- package/dist/generators/screen.generator.js +100 -5
- package/dist/generators/screen.merge.js +70 -15
- package/dist/index.js +69 -8
- package/dist/license/guard.js +2 -1
- package/dist/license/permissions.js +62 -8
- package/dist/license/token.js +19 -0
- package/dist/postinstall.js +42 -2
- package/dist/runtime/config.js +4 -0
- package/dist/runtime/project-config.js +64 -0
- package/package.json +1 -1
package/dist/commands/angular.js
CHANGED
|
@@ -7,15 +7,38 @@ exports.angular = angular;
|
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const feature_generator_1 = require("../generators/angular/feature.generator");
|
|
10
|
+
const feature_generator_2 = require("../generators/angular/feature.generator");
|
|
10
11
|
const routes_generator_1 = require("../generators/angular/routes.generator");
|
|
11
12
|
const menu_generator_1 = require("../generators/angular/menu.generator");
|
|
12
13
|
const telemetry_1 = require("../telemetry");
|
|
14
|
+
const user_config_1 = require("../runtime/user-config");
|
|
15
|
+
const permissions_1 = require("../license/permissions");
|
|
13
16
|
const logger_1 = require("../runtime/logger");
|
|
17
|
+
const project_config_1 = require("../runtime/project-config");
|
|
14
18
|
async function angular(options) {
|
|
15
19
|
void (0, telemetry_1.trackCommand)('angular', options.telemetryEnabled);
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
let intelligentEnabled = false;
|
|
21
|
+
let subscriptionReason = '';
|
|
22
|
+
try {
|
|
23
|
+
const permissions = await (0, permissions_1.getPermissions)();
|
|
24
|
+
intelligentEnabled = Boolean(permissions.features.intelligentGeneration);
|
|
25
|
+
subscriptionReason = String(permissions.subscription.reason ?? '').trim();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
intelligentEnabled = false;
|
|
29
|
+
subscriptionReason = '';
|
|
30
|
+
}
|
|
31
|
+
const projectConfig = (0, project_config_1.findProjectConfig)(process.cwd());
|
|
32
|
+
const configuredFeatures = (0, project_config_1.pickConfiguredPath)(projectConfig.config, 'features');
|
|
33
|
+
const configuredSchemas = (0, project_config_1.pickConfiguredPath)(projectConfig.config, 'schemas') ??
|
|
34
|
+
(0, project_config_1.pickConfiguredPath)(projectConfig.config, 'output');
|
|
35
|
+
const featuresRoot = resolveFeaturesRoot(options.featuresPath, configuredFeatures, projectConfig.configPath);
|
|
36
|
+
const generatedFeaturesRoot = path_1.default.join(featuresRoot, 'generated');
|
|
37
|
+
const overridesFeaturesRoot = path_1.default.join(featuresRoot, 'overrides');
|
|
38
|
+
const schemasRoot = resolveSchemasRoot(options.schemasPath, configuredSchemas, projectConfig.configPath, featuresRoot);
|
|
18
39
|
(0, logger_1.logStep)(`Features output: ${featuresRoot}`);
|
|
40
|
+
(0, logger_1.logDebug)(`Generated features: ${generatedFeaturesRoot}`);
|
|
41
|
+
(0, logger_1.logDebug)(`Overrides: ${overridesFeaturesRoot}`);
|
|
19
42
|
(0, logger_1.logStep)(`Schemas input: ${schemasRoot}`);
|
|
20
43
|
/**
|
|
21
44
|
* Onde estão os schemas
|
|
@@ -26,71 +49,210 @@ async function angular(options) {
|
|
|
26
49
|
console.log(`ℹ Created generate-ui folder at ${schemasRoot}`);
|
|
27
50
|
}
|
|
28
51
|
const overlaysDir = path_1.default.join(schemasRoot, 'overlays');
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
52
|
+
await generateAngularOnce({
|
|
53
|
+
overlaysDir,
|
|
54
|
+
featuresRoot,
|
|
55
|
+
generatedFeaturesRoot,
|
|
56
|
+
overridesFeaturesRoot,
|
|
57
|
+
schemasRoot,
|
|
58
|
+
intelligentEnabled,
|
|
59
|
+
subscriptionReason
|
|
60
|
+
});
|
|
61
|
+
if (!options.watch) {
|
|
62
|
+
(0, logger_1.logTip)('Run with --dev to see detailed logs and file paths.');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
console.log(`👀 Watching ${overlaysDir} for .screen.json changes...`);
|
|
66
|
+
let debounceTimer = null;
|
|
67
|
+
const rerun = (fileName) => {
|
|
68
|
+
if (debounceTimer)
|
|
69
|
+
clearTimeout(debounceTimer);
|
|
70
|
+
debounceTimer = setTimeout(async () => {
|
|
71
|
+
console.log(`↻ Change detected: ${fileName}`);
|
|
72
|
+
try {
|
|
73
|
+
await generateAngularOnce({
|
|
74
|
+
overlaysDir,
|
|
75
|
+
featuresRoot,
|
|
76
|
+
generatedFeaturesRoot,
|
|
77
|
+
overridesFeaturesRoot,
|
|
78
|
+
schemasRoot,
|
|
79
|
+
intelligentEnabled,
|
|
80
|
+
subscriptionReason
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
const message = error instanceof Error ? error.message : 'Unexpected error';
|
|
85
|
+
console.error(message);
|
|
86
|
+
}
|
|
87
|
+
}, 180);
|
|
88
|
+
};
|
|
89
|
+
const watcher = fs_1.default.watch(overlaysDir, (event, fileName) => {
|
|
90
|
+
if (!fileName)
|
|
91
|
+
return;
|
|
92
|
+
if (!String(fileName).endsWith('.screen.json'))
|
|
93
|
+
return;
|
|
94
|
+
if (event !== 'change' && event !== 'rename')
|
|
95
|
+
return;
|
|
96
|
+
rerun(String(fileName));
|
|
97
|
+
});
|
|
98
|
+
const closeWatcher = () => {
|
|
99
|
+
watcher.close();
|
|
100
|
+
console.log('\nStopped screen watch.');
|
|
101
|
+
process.exit(0);
|
|
102
|
+
};
|
|
103
|
+
process.on('SIGINT', closeWatcher);
|
|
104
|
+
process.on('SIGTERM', closeWatcher);
|
|
105
|
+
}
|
|
106
|
+
async function generateAngularOnce(options) {
|
|
107
|
+
if (!fs_1.default.existsSync(options.overlaysDir)) {
|
|
108
|
+
throw new Error(`Overlays directory not found: ${options.overlaysDir}\n` +
|
|
109
|
+
'Run `generate-ui generate` first to create overlays.\n' +
|
|
110
|
+
'If needed, configure "openapi", "schemas", and "features" in generateui-config.json.');
|
|
111
|
+
}
|
|
112
|
+
(0, logger_1.logDebug)(`Overlays dir: ${options.overlaysDir}`);
|
|
40
113
|
const screens = fs_1.default
|
|
41
|
-
.readdirSync(overlaysDir)
|
|
114
|
+
.readdirSync(options.overlaysDir)
|
|
42
115
|
.filter(f => f.endsWith('.screen.json'));
|
|
43
116
|
(0, logger_1.logDebug)(`Screens found: ${screens.length}`);
|
|
44
117
|
if (screens.length === 0) {
|
|
45
|
-
|
|
46
|
-
'generate-ui angular
|
|
47
|
-
'
|
|
48
|
-
' --features /path/to/src/app/features'
|
|
49
|
-
].join('\n');
|
|
50
|
-
throw new Error(`No .screen.json files found in: ${overlaysDir}\n` +
|
|
51
|
-
'Run again with --schemas pointing to your generate-ui folder.\n' +
|
|
52
|
-
`Example:\n${example}`);
|
|
118
|
+
throw new Error(`No .screen.json files found in: ${options.overlaysDir}\n` +
|
|
119
|
+
'Run `generate-ui generate` and then `generate-ui angular`.\n' +
|
|
120
|
+
'If needed, set "schemas" (or "paths.schemas") in generateui-config.json.');
|
|
53
121
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
fs_1.default.mkdirSync(featuresRoot, { recursive: true });
|
|
122
|
+
fs_1.default.mkdirSync(options.featuresRoot, { recursive: true });
|
|
123
|
+
fs_1.default.mkdirSync(options.generatedFeaturesRoot, { recursive: true });
|
|
124
|
+
fs_1.default.mkdirSync(options.overridesFeaturesRoot, { recursive: true });
|
|
58
125
|
const routes = [];
|
|
126
|
+
const schemas = [];
|
|
127
|
+
const appRoot = path_1.default.resolve(options.featuresRoot, '..');
|
|
128
|
+
const configInfo = findConfig(appRoot);
|
|
129
|
+
const views = configInfo.config?.views ?? {};
|
|
130
|
+
const generatedSchemasDir = path_1.default.join(options.schemasRoot, 'generated');
|
|
59
131
|
for (const file of screens) {
|
|
60
|
-
|
|
61
|
-
const
|
|
132
|
+
let schema = JSON.parse(fs_1.default.readFileSync(path_1.default.join(options.overlaysDir, file), 'utf-8'));
|
|
133
|
+
const generatedPath = path_1.default.join(generatedSchemasDir, file);
|
|
134
|
+
if (fs_1.default.existsSync(generatedPath)) {
|
|
135
|
+
try {
|
|
136
|
+
const generatedSchema = JSON.parse(fs_1.default.readFileSync(generatedPath, 'utf-8'));
|
|
137
|
+
const normalized = enforceScreenShape(schema, generatedSchema);
|
|
138
|
+
schema = normalized.schema;
|
|
139
|
+
for (const warning of normalized.warnings) {
|
|
140
|
+
console.warn(warning);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Ignore malformed generated schema and keep overlay as source.
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
schemas.push({ file, schema });
|
|
148
|
+
}
|
|
149
|
+
const schemaByOpId = new Map();
|
|
150
|
+
for (const { schema } of schemas) {
|
|
151
|
+
const opId = schema?.api?.operationId;
|
|
152
|
+
if (opId)
|
|
153
|
+
schemaByOpId.set(opId, schema);
|
|
154
|
+
}
|
|
155
|
+
for (const { schema } of schemas) {
|
|
156
|
+
const opId = schema?.api?.operationId;
|
|
157
|
+
const view = opId ? views[opId] : undefined;
|
|
158
|
+
if (view) {
|
|
159
|
+
schema.meta = {
|
|
160
|
+
...(schema.meta ?? {}),
|
|
161
|
+
view
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (schema?.meta?.intelligent?.kind === 'adminList') {
|
|
165
|
+
if (!options.intelligentEnabled) {
|
|
166
|
+
const reason = String(options.subscriptionReason ?? '').trim();
|
|
167
|
+
(0, logger_1.logTip)(reason
|
|
168
|
+
? `Intelligent generation is disabled: ${reason}`
|
|
169
|
+
: 'Intelligent generation is disabled. Login and an active subscription are required to generate admin list screens.');
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const adminRoute = (0, feature_generator_2.generateAdminFeature)(schema, schemaByOpId, options.featuresRoot, options.generatedFeaturesRoot, options.schemasRoot);
|
|
173
|
+
routes.push(adminRoute);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const route = (0, feature_generator_1.generateFeature)(schema, options.featuresRoot, options.generatedFeaturesRoot, options.schemasRoot);
|
|
62
177
|
routes.push(route);
|
|
63
178
|
}
|
|
64
|
-
(
|
|
65
|
-
(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
(
|
|
179
|
+
migrateLegacyFeatures(routes, options.featuresRoot, options.generatedFeaturesRoot, options.overridesFeaturesRoot);
|
|
180
|
+
syncOverrides(routes, options.generatedFeaturesRoot, options.overridesFeaturesRoot);
|
|
181
|
+
(0, routes_generator_1.generateRoutes)(routes, options.generatedFeaturesRoot, options.overridesFeaturesRoot, options.schemasRoot);
|
|
182
|
+
(0, menu_generator_1.generateMenu)(options.schemasRoot);
|
|
183
|
+
applyAppLayout(options.featuresRoot, options.schemasRoot);
|
|
184
|
+
console.log(`✔ Angular features generated at ${options.featuresRoot}`);
|
|
185
|
+
const overrides = findOverrides(routes, options.overridesFeaturesRoot, options.generatedFeaturesRoot);
|
|
186
|
+
if (overrides.length) {
|
|
187
|
+
console.log('');
|
|
188
|
+
console.log('ℹ Overrides with differences detected:');
|
|
189
|
+
console.log(' Use merge to compare generated (left) vs overrides (right).');
|
|
190
|
+
console.log(' Keep your changes in the right file (overrides).');
|
|
191
|
+
for (const override of overrides) {
|
|
192
|
+
console.log(` - ${override.component} -> generate-ui merge --feature ${override.folder} --file all`);
|
|
193
|
+
}
|
|
194
|
+
console.log('');
|
|
195
|
+
}
|
|
69
196
|
}
|
|
70
|
-
function
|
|
71
|
-
|
|
72
|
-
|
|
197
|
+
function enforceScreenShape(overlay, generated) {
|
|
198
|
+
const warnings = [];
|
|
199
|
+
const next = overlay ?? {};
|
|
200
|
+
const generatedType = generated?.screen?.type;
|
|
201
|
+
const overlayType = next?.screen?.type;
|
|
202
|
+
if (generatedType &&
|
|
203
|
+
overlayType &&
|
|
204
|
+
String(overlayType) !== String(generatedType)) {
|
|
205
|
+
next.screen = { ...(next.screen ?? {}), type: generatedType };
|
|
206
|
+
warnings.push(`⚠ Ignored overlay change: screen.type is fixed by generation (${generatedType}).`);
|
|
207
|
+
}
|
|
208
|
+
const generatedMode = generated?.screen?.mode;
|
|
209
|
+
const overlayMode = next?.screen?.mode;
|
|
210
|
+
if (generatedMode &&
|
|
211
|
+
overlayMode &&
|
|
212
|
+
String(overlayMode) !== String(generatedMode)) {
|
|
213
|
+
next.screen = { ...(next.screen ?? {}), mode: generatedMode };
|
|
214
|
+
warnings.push(`⚠ Ignored overlay change: screen.mode is fixed by generation (${generatedMode}).`);
|
|
215
|
+
}
|
|
216
|
+
const generatedMethod = generated?.api?.method;
|
|
217
|
+
const overlayMethod = next?.api?.method;
|
|
218
|
+
if (generatedMethod &&
|
|
219
|
+
overlayMethod &&
|
|
220
|
+
String(overlayMethod).toLowerCase() !==
|
|
221
|
+
String(generatedMethod).toLowerCase()) {
|
|
222
|
+
next.api = { ...(next.api ?? {}), method: generatedMethod };
|
|
223
|
+
warnings.push(`⚠ Ignored overlay change: api.method is fixed by generation (${generatedMethod}).`);
|
|
224
|
+
}
|
|
225
|
+
const generatedOpId = generated?.api?.operationId;
|
|
226
|
+
const overlayOpId = next?.api?.operationId;
|
|
227
|
+
if (generatedOpId &&
|
|
228
|
+
overlayOpId &&
|
|
229
|
+
String(overlayOpId) !== String(generatedOpId)) {
|
|
230
|
+
next.api = { ...(next.api ?? {}), operationId: generatedOpId };
|
|
231
|
+
warnings.push(`⚠ Ignored overlay change: api.operationId is fixed by generation (${generatedOpId}).`);
|
|
232
|
+
}
|
|
233
|
+
return { schema: next, warnings };
|
|
234
|
+
}
|
|
235
|
+
function resolveSchemasRoot(value, configured, configPath, featuresRoot) {
|
|
236
|
+
const fromConfig = (0, project_config_1.resolveOptionalPath)(value, configured, configPath);
|
|
237
|
+
if (fromConfig) {
|
|
238
|
+
return fromConfig;
|
|
73
239
|
}
|
|
74
240
|
const inferred = inferSchemasRootFromFeatures(featuresRoot);
|
|
75
241
|
if (inferred)
|
|
76
242
|
return inferred;
|
|
77
243
|
return resolveDefaultSchemasRoot();
|
|
78
244
|
}
|
|
79
|
-
function resolveFeaturesRoot(value) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
path_1.default.basename(path_1.default.dirname(resolved)) === 'src';
|
|
84
|
-
if (isSrcApp) {
|
|
85
|
-
return path_1.default.join(resolved, 'features');
|
|
86
|
-
}
|
|
87
|
-
return resolved;
|
|
245
|
+
function resolveFeaturesRoot(value, configured, configPath) {
|
|
246
|
+
const fromConfig = (0, project_config_1.resolveOptionalPath)(value, configured, configPath);
|
|
247
|
+
if (fromConfig) {
|
|
248
|
+
return normalizeFeaturesRoot(fromConfig);
|
|
88
249
|
}
|
|
89
250
|
const srcAppRoot = path_1.default.resolve(process.cwd(), 'src', 'app');
|
|
90
|
-
if (
|
|
91
|
-
|
|
251
|
+
if (fs_1.default.existsSync(srcAppRoot)) {
|
|
252
|
+
return path_1.default.join(srcAppRoot, 'features');
|
|
92
253
|
}
|
|
93
|
-
|
|
254
|
+
throw new Error('Default features path not found.\n' +
|
|
255
|
+
'Use --features <path> or set "features" (or "paths.features") in generateui-config.json.');
|
|
94
256
|
}
|
|
95
257
|
function inferSchemasRootFromFeatures(featuresRoot) {
|
|
96
258
|
const srcCandidate = path_1.default.resolve(featuresRoot, '..', '..', 'generate-ui');
|
|
@@ -104,15 +266,85 @@ function inferSchemasRootFromFeatures(featuresRoot) {
|
|
|
104
266
|
return null;
|
|
105
267
|
}
|
|
106
268
|
function resolveDefaultSchemasRoot() {
|
|
269
|
+
const userConfig = (0, user_config_1.loadUserConfig)();
|
|
270
|
+
if (userConfig?.lastSchemasPath &&
|
|
271
|
+
fs_1.default.existsSync(path_1.default.join(userConfig.lastSchemasPath, 'overlays'))) {
|
|
272
|
+
return userConfig.lastSchemasPath;
|
|
273
|
+
}
|
|
107
274
|
const cwd = process.cwd();
|
|
108
275
|
if (fs_1.default.existsSync(path_1.default.join(cwd, 'src'))) {
|
|
109
276
|
return path_1.default.join(cwd, 'src', 'generate-ui');
|
|
110
277
|
}
|
|
111
|
-
if (fs_1.default.existsSync(path_1.default.join(cwd, 'frontend', 'src'))) {
|
|
112
|
-
return path_1.default.join(cwd, 'frontend', 'src', 'generate-ui');
|
|
113
|
-
}
|
|
114
278
|
return path_1.default.join(cwd, 'generate-ui');
|
|
115
279
|
}
|
|
280
|
+
function normalizeFeaturesRoot(value) {
|
|
281
|
+
const isSrcApp = path_1.default.basename(value) === 'app' &&
|
|
282
|
+
path_1.default.basename(path_1.default.dirname(value)) === 'src';
|
|
283
|
+
if (isSrcApp)
|
|
284
|
+
return path_1.default.join(value, 'features');
|
|
285
|
+
return value;
|
|
286
|
+
}
|
|
287
|
+
function findOverrides(routes, overridesRoot, generatedRoot) {
|
|
288
|
+
const results = [];
|
|
289
|
+
for (const route of routes) {
|
|
290
|
+
const overridePath = path_1.default.join(overridesRoot, route.folder, `${route.fileBase}.component.ts`);
|
|
291
|
+
const generatedPath = path_1.default.join(generatedRoot, route.folder, `${route.fileBase}.component.ts`);
|
|
292
|
+
if (!fs_1.default.existsSync(overridePath))
|
|
293
|
+
continue;
|
|
294
|
+
if (!fs_1.default.existsSync(generatedPath))
|
|
295
|
+
continue;
|
|
296
|
+
const overrideRaw = fs_1.default.readFileSync(overridePath, 'utf-8');
|
|
297
|
+
const generatedRaw = fs_1.default.readFileSync(generatedPath, 'utf-8');
|
|
298
|
+
if (overrideRaw !== generatedRaw) {
|
|
299
|
+
results.push({
|
|
300
|
+
folder: route.folder,
|
|
301
|
+
component: route.component,
|
|
302
|
+
generatedPath,
|
|
303
|
+
overridePath
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return results;
|
|
308
|
+
}
|
|
309
|
+
function syncOverrides(routes, generatedRoot, overridesRoot) {
|
|
310
|
+
const overridesEmpty = fs_1.default.existsSync(overridesRoot) &&
|
|
311
|
+
fs_1.default.readdirSync(overridesRoot).length === 0;
|
|
312
|
+
if (overridesEmpty && fs_1.default.existsSync(generatedRoot)) {
|
|
313
|
+
fs_1.default.cpSync(generatedRoot, overridesRoot, { recursive: true });
|
|
314
|
+
console.log('ℹ Seeded overrides from generated (initial sync).');
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
for (const route of routes) {
|
|
318
|
+
const sourceDir = path_1.default.join(generatedRoot, route.folder);
|
|
319
|
+
const targetDir = path_1.default.join(overridesRoot, route.folder);
|
|
320
|
+
if (!fs_1.default.existsSync(sourceDir))
|
|
321
|
+
continue;
|
|
322
|
+
if (fs_1.default.existsSync(targetDir))
|
|
323
|
+
continue;
|
|
324
|
+
fs_1.default.mkdirSync(path_1.default.dirname(targetDir), { recursive: true });
|
|
325
|
+
fs_1.default.cpSync(sourceDir, targetDir, { recursive: true });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function migrateLegacyFeatures(routes, featuresRoot, generatedRoot, overridesRoot) {
|
|
329
|
+
let migrated = false;
|
|
330
|
+
for (const route of routes) {
|
|
331
|
+
const legacyDir = path_1.default.join(featuresRoot, route.folder);
|
|
332
|
+
const generatedDir = path_1.default.join(generatedRoot, route.folder);
|
|
333
|
+
const overrideDir = path_1.default.join(overridesRoot, route.folder);
|
|
334
|
+
if (!fs_1.default.existsSync(legacyDir))
|
|
335
|
+
continue;
|
|
336
|
+
if (fs_1.default.existsSync(generatedDir) || fs_1.default.existsSync(overrideDir)) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
fs_1.default.mkdirSync(path_1.default.dirname(generatedDir), { recursive: true });
|
|
340
|
+
fs_1.default.cpSync(legacyDir, generatedDir, { recursive: true });
|
|
341
|
+
fs_1.default.cpSync(legacyDir, overrideDir, { recursive: true });
|
|
342
|
+
migrated = true;
|
|
343
|
+
}
|
|
344
|
+
if (migrated) {
|
|
345
|
+
console.log('ℹ Legacy feature folders detected. Seeded generated/overrides.');
|
|
346
|
+
}
|
|
347
|
+
}
|
|
116
348
|
function applyAppLayout(featuresRoot, schemasRoot) {
|
|
117
349
|
const appRoot = path_1.default.resolve(featuresRoot, '..');
|
|
118
350
|
const configInfo = findConfig(appRoot);
|
|
@@ -129,6 +361,7 @@ function applyAppLayout(featuresRoot, schemasRoot) {
|
|
|
129
361
|
console.log(` 🏷️ appTitle: "${resolvedTitle}"`);
|
|
130
362
|
console.log(` ➡️ defaultRoute: ${resolvedRoute}`);
|
|
131
363
|
console.log(` 🧭 menu.autoInject: ${resolvedInject}`);
|
|
364
|
+
console.log(' ✍️ Edit generateui-config.json before running `generate-ui generate` when you want to change title, route, views, or menu behavior.');
|
|
132
365
|
console.log(' 🧩 menu overrides: edit generate-ui/menu.overrides.json to customize labels, groups, and order');
|
|
133
366
|
console.log(' (this file is created once and never overwritten)');
|
|
134
367
|
console.log('');
|
|
@@ -136,18 +369,19 @@ function applyAppLayout(featuresRoot, schemasRoot) {
|
|
|
136
369
|
else {
|
|
137
370
|
console.log('ℹ️ No generateui-config.json found. Using defaults.');
|
|
138
371
|
console.log('');
|
|
139
|
-
console.log(' ✨ To customize,
|
|
372
|
+
console.log(' ✨ To customize, create/edit generateui-config.json at your project root before running `generate-ui generate`.');
|
|
140
373
|
console.log(' 🧩 To customize the menu, edit generate-ui/menu.overrides.json (created on first generate).');
|
|
141
374
|
console.log('');
|
|
142
375
|
}
|
|
143
376
|
if (config?.defaultRoute) {
|
|
144
377
|
injectDefaultRoute(appRoot, config.defaultRoute);
|
|
145
378
|
}
|
|
379
|
+
ensureBaseStyles(appRoot);
|
|
146
380
|
const autoInject = config?.menu?.autoInject !== false;
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
381
|
+
if (autoInject) {
|
|
382
|
+
const appTitle = config?.appTitle || 'Generate UI';
|
|
383
|
+
injectMenuLayout(appRoot, appTitle, schemasRoot);
|
|
384
|
+
}
|
|
151
385
|
}
|
|
152
386
|
function findConfig(startDir) {
|
|
153
387
|
let dir = startDir;
|
|
@@ -192,6 +426,12 @@ function injectDefaultRoute(appRoot, value) {
|
|
|
192
426
|
}
|
|
193
427
|
content = content.replace(/export const routes\s*=/, 'export const routes: Routes =');
|
|
194
428
|
}
|
|
429
|
+
if (content.includes('generatedRoutes') &&
|
|
430
|
+
!content.match(/import\s+\{\s*generatedRoutes\s*\}\s+from\s+['"].*generate-ui\/routes\.gen['"]/)) {
|
|
431
|
+
content =
|
|
432
|
+
`import { generatedRoutes } from '../generate-ui/routes.gen'\n` +
|
|
433
|
+
content;
|
|
434
|
+
}
|
|
195
435
|
content = content.replace(/export const routes\s*=\s*\[/, match => `${match}\n${insertion}`);
|
|
196
436
|
const emptyRedirect = /path:\s*['"]\s*['"]\s*,\s*pathMatch:\s*['"]full['"]\s*,\s*redirectTo:\s*['"]\s*['"]/;
|
|
197
437
|
if (emptyRedirect.test(content)) {
|
|
@@ -232,7 +472,7 @@ function injectMenuLayout(appRoot, appTitle, schemasRoot) {
|
|
|
232
472
|
}
|
|
233
473
|
const cssRaw = fs_1.default.readFileSync(appCssPath, 'utf-8');
|
|
234
474
|
if (!cssRaw.includes('.app-shell')) {
|
|
235
|
-
const shellCss = `:host {\n display: block;\n min-height: 100vh;\n color: #0f172a;\n background
|
|
475
|
+
const shellCss = `:host {\n display: block;\n min-height: 100vh;\n color: #0f172a;\n background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);\n}\n\n.app-shell {\n display: grid;\n grid-template-columns: 260px 1fr;\n gap: 24px;\n padding: 24px;\n align-items: start;\n}\n\n.app-content {\n min-width: 0;\n}\n\n@media (max-width: 900px) {\n .app-shell {\n grid-template-columns: 1fr;\n }\n}\n`;
|
|
236
476
|
fs_1.default.writeFileSync(appCssPath, cssRaw.trim().length ? `${cssRaw.trim()}\n\n${shellCss}` : shellCss);
|
|
237
477
|
(0, logger_1.logDebug)(`Injected menu shell styles into: ${appCssPath}`);
|
|
238
478
|
}
|
|
@@ -272,6 +512,52 @@ function injectMenuLayout(appRoot, appTitle, schemasRoot) {
|
|
|
272
512
|
void schemasRoot;
|
|
273
513
|
}
|
|
274
514
|
}
|
|
515
|
+
function ensureBaseStyles(appRoot) {
|
|
516
|
+
const workspaceRoot = findAngularWorkspaceRoot(appRoot) ?? path_1.default.resolve(appRoot, '..');
|
|
517
|
+
const stylesPath = path_1.default.join(workspaceRoot, 'src', 'styles.css');
|
|
518
|
+
if (fs_1.default.existsSync(stylesPath))
|
|
519
|
+
return;
|
|
520
|
+
if (fs_1.default.existsSync(path_1.default.join(workspaceRoot, 'src', 'styles.scss')))
|
|
521
|
+
return;
|
|
522
|
+
const styles = `@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Instrument+Serif:ital@0;1&display=swap');
|
|
523
|
+
|
|
524
|
+
:root {
|
|
525
|
+
--bg-page: #f8fafc;
|
|
526
|
+
--bg-surface: #ffffff;
|
|
527
|
+
--bg-ink: #0f172a;
|
|
528
|
+
--color-text: #0f172a;
|
|
529
|
+
--color-muted: #64748b;
|
|
530
|
+
--color-border: #d1d5db;
|
|
531
|
+
--color-primary: #3b82f6;
|
|
532
|
+
--color-primary-strong: #1d4ed8;
|
|
533
|
+
--color-primary-soft: rgba(59, 130, 246, 0.14);
|
|
534
|
+
--color-accent: #0ea5e9;
|
|
535
|
+
--color-accent-strong: #0284c7;
|
|
536
|
+
--color-accent-soft: rgba(14, 165, 233, 0.14);
|
|
537
|
+
--shadow-card: 0 4px 16px rgba(15, 23, 42, 0.08);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
body {
|
|
541
|
+
margin: 0;
|
|
542
|
+
background: var(--bg-page);
|
|
543
|
+
color: var(--color-text);
|
|
544
|
+
font-family: "DM Sans", "Segoe UI", sans-serif;
|
|
545
|
+
}
|
|
546
|
+
`;
|
|
547
|
+
fs_1.default.writeFileSync(stylesPath, styles);
|
|
548
|
+
}
|
|
549
|
+
function findAngularWorkspaceRoot(startDir) {
|
|
550
|
+
let dir = startDir;
|
|
551
|
+
const root = path_1.default.parse(dir).root;
|
|
552
|
+
while (true) {
|
|
553
|
+
const candidate = path_1.default.join(dir, 'angular.json');
|
|
554
|
+
if (fs_1.default.existsSync(candidate))
|
|
555
|
+
return dir;
|
|
556
|
+
if (dir === root)
|
|
557
|
+
return null;
|
|
558
|
+
dir = path_1.default.dirname(dir);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
275
561
|
function escapeString(value) {
|
|
276
562
|
return String(value).replace(/'/g, "\\'");
|
|
277
563
|
}
|