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.
@@ -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
- const featuresRoot = resolveFeaturesRoot(options.featuresPath);
17
- const schemasRoot = resolveSchemasRoot(options.schemasPath, featuresRoot);
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
- if (!fs_1.default.existsSync(overlaysDir)) {
30
- const example = [
31
- 'generate-ui angular \\',
32
- ' --schemas /path/to/generate-ui \\',
33
- ' --features /path/to/src/app/features'
34
- ].join('\n');
35
- throw new Error(`Overlays directory not found: ${overlaysDir}\n` +
36
- 'Run `generate-ui generate --openapi <path> --output <schemas>` first to create overlays.\n' +
37
- `Example:\n${example}`);
38
- }
39
- (0, logger_1.logDebug)(`Overlays dir: ${overlaysDir}`);
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
- const example = [
46
- 'generate-ui angular \\',
47
- ' --schemas /path/to/generate-ui \\',
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
- * Onde gerar as features Angular
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
- const schema = JSON.parse(fs_1.default.readFileSync(path_1.default.join(overlaysDir, file), 'utf-8'));
61
- const route = (0, feature_generator_1.generateFeature)(schema, featuresRoot, schemasRoot);
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
- (0, routes_generator_1.generateRoutes)(routes, featuresRoot, schemasRoot);
65
- (0, menu_generator_1.generateMenu)(schemasRoot);
66
- applyAppLayout(featuresRoot, schemasRoot);
67
- console.log(`✔ Angular features generated at ${featuresRoot}`);
68
- (0, logger_1.logTip)('Run with --dev to see detailed logs and file paths.');
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 resolveSchemasRoot(value, featuresRoot) {
71
- if (value) {
72
- return path_1.default.resolve(process.cwd(), value);
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
- if (value) {
81
- const resolved = path_1.default.resolve(process.cwd(), value);
82
- const isSrcApp = path_1.default.basename(resolved) === 'app' &&
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 (!fs_1.default.existsSync(srcAppRoot)) {
91
- throw new Error('Default features path not found: ./src/app. Provide --features /path/to/src/app (or /path/to/src/app/features)');
251
+ if (fs_1.default.existsSync(srcAppRoot)) {
252
+ return path_1.default.join(srcAppRoot, 'features');
92
253
  }
93
- return path_1.default.join(srcAppRoot, 'features');
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, add generateui-config.json at your project root.');
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 (!autoInject)
148
- return;
149
- const appTitle = config?.appTitle || 'Generate UI';
150
- injectMenuLayout(appRoot, appTitle, schemasRoot);
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:\n radial-gradient(circle at top left, rgba(14, 116, 144, 0.14), transparent 55%),\n radial-gradient(circle at bottom right, rgba(59, 130, 246, 0.12), transparent 50%),\n linear-gradient(180deg, #f8fafc 0%, #eef2f7 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`;
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
  }