@loom-framework/core 0.1.0-alpha.80 → 0.1.0-alpha.81

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.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * loom generate dashboard <name>
3
+ *
4
+ * Reads dashboard.config.json from the project root,
5
+ * generates a Dashboard page with G2 charts.
6
+ */
7
+ import type { Command } from 'commander';
8
+ export declare function registerGenerateDashboardCommand(program: Command): void;
9
+ //# sourceMappingURL=generate-dashboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-dashboard.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/generate-dashboard.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiZzC,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2FvE"}
@@ -0,0 +1,452 @@
1
+ /**
2
+ * loom generate dashboard <name>
3
+ *
4
+ * Reads dashboard.config.json from the project root,
5
+ * generates a Dashboard page with G2 charts.
6
+ */
7
+ import chalk from 'chalk';
8
+ import { promises as fs } from 'fs';
9
+ import path from 'path';
10
+ import { loadConfig, getModelSchema, loadDashboardConfig } from '../../index.js';
11
+ import { resolveProjectRoot } from '../utils.js';
12
+ import { toPascalCase } from '../helpers/naming.js';
13
+ import { wireAppTsxAutomatic } from '../helpers/app-tsx-wiring.js';
14
+ /** Build variable name for chart data: e.g. "subjectPieData" */
15
+ function buildChartDataVarName(widget) {
16
+ const prefix = widget.groupBy ? toPascalCase(widget.groupBy) : '';
17
+ const suffix = toPascalCase(widget.type);
18
+ return `${prefix.charAt(0).toLowerCase() + prefix.slice(1)}${suffix}Data`;
19
+ }
20
+ /** Build useChartData() call string */
21
+ function buildUseChartDataCall(widget, modelName, pascalModelName, isBooleanField) {
22
+ const listVar = `${pascalModelName.charAt(0).toLowerCase() + pascalModelName.slice(1)}List`;
23
+ const varName = buildChartDataVarName(widget);
24
+ const options = [];
25
+ if (widget.type === 'stat') {
26
+ // Stats don't use useChartData
27
+ return '';
28
+ }
29
+ // 2D charts use useCrossGroupChartData
30
+ const is2D = ['heatmap', 'stacked_bar', 'grouped_bar'].includes(widget.type) && widget.crossGroupBy;
31
+ // Treemap uses useTreemapChartData
32
+ const isTreemap = widget.type === 'treemap';
33
+ if (widget.groupBy) {
34
+ if (isBooleanField) {
35
+ options.push(`groupByBoolean: '${widget.groupBy}'`);
36
+ }
37
+ else {
38
+ options.push(`groupBy: '${widget.groupBy}'`);
39
+ }
40
+ }
41
+ if (widget.crossGroupBy) {
42
+ options.push(`crossGroupBy: '${widget.crossGroupBy}'`);
43
+ }
44
+ if (widget.aggregate && widget.aggregate !== 'count') {
45
+ options.push(`aggregate: '${widget.aggregate}'`);
46
+ }
47
+ if (widget.field) {
48
+ options.push(`field: '${widget.field}'`);
49
+ }
50
+ if (widget.interval) {
51
+ options.push(`interval: '${widget.interval}'`);
52
+ }
53
+ if (widget.filter) {
54
+ options.push(`filter: ${JSON.stringify(widget.filter)}`);
55
+ }
56
+ const optionsStr = options.length > 0 ? `{ ${options.join(', ')} }` : '{}';
57
+ if (is2D) {
58
+ return `const ${varName} = useCrossGroupChartData(${listVar}, ${optionsStr});`;
59
+ }
60
+ if (isTreemap) {
61
+ return `const ${varName} = useTreemapChartData(${listVar}, ${optionsStr});`;
62
+ }
63
+ return `const ${varName} = useChartData(${listVar}, ${optionsStr});`;
64
+ }
65
+ /** Build G2 Spec object literal code */
66
+ function buildG2Spec(widget) {
67
+ const varName = buildChartDataVarName(widget);
68
+ switch (widget.type) {
69
+ case 'pie':
70
+ return `{
71
+ type: 'interval',
72
+ data: ${varName},
73
+ encode: { y: 'value', color: 'name' },
74
+ coordinate: { type: 'theta' },
75
+ transform: [{ type: 'stackY' }],
76
+ labels: [{ text: 'name', position: 'outside' }],
77
+ }`;
78
+ case 'ring':
79
+ return `{
80
+ type: 'interval',
81
+ data: ${varName},
82
+ encode: { y: 'value', color: 'name' },
83
+ coordinate: { type: 'theta', innerRadius: 0.6 },
84
+ transform: [{ type: 'stackY' }],
85
+ labels: [{ text: 'name', position: 'outside' }],
86
+ }`;
87
+ case 'bar':
88
+ return `{
89
+ type: 'interval',
90
+ data: ${varName},
91
+ encode: { x: 'value', y: 'name', color: 'name' },
92
+ coordinate: { transform: [{ type: 'transpose' }] },
93
+ }`;
94
+ case 'stacked_bar':
95
+ if (widget.crossGroupBy) {
96
+ return `{
97
+ type: 'interval',
98
+ data: ${varName},
99
+ encode: { x: 'group', y: 'value', color: 'name' },
100
+ coordinate: { transform: [{ type: 'transpose' }] },
101
+ transform: [{ type: 'stackY' }],
102
+ }`;
103
+ }
104
+ return `{
105
+ type: 'interval',
106
+ data: ${varName},
107
+ encode: { x: 'value', y: 'name', color: 'name' },
108
+ coordinate: { transform: [{ type: 'transpose' }] },
109
+ transform: [{ type: 'stackY' }],
110
+ }`;
111
+ case 'grouped_bar':
112
+ if (widget.crossGroupBy) {
113
+ return `{
114
+ type: 'interval',
115
+ data: ${varName},
116
+ encode: { x: 'group', y: 'value', color: 'name' },
117
+ coordinate: { transform: [{ type: 'transpose' }] },
118
+ transform: [{ type: 'dodgeX' }],
119
+ }`;
120
+ }
121
+ return `{
122
+ type: 'interval',
123
+ data: ${varName},
124
+ encode: { x: 'value', y: 'name', color: 'name' },
125
+ coordinate: { transform: [{ type: 'transpose' }] },
126
+ transform: [{ type: 'dodgeX' }],
127
+ }`;
128
+ case 'line':
129
+ return `{
130
+ type: 'line',
131
+ data: ${varName},
132
+ encode: { x: 'name', y: 'value' },
133
+ }`;
134
+ case 'area':
135
+ return `{
136
+ type: 'area',
137
+ data: ${varName},
138
+ encode: { x: 'name', y: 'value' },
139
+ }`;
140
+ case 'scatter':
141
+ return `{
142
+ type: 'point',
143
+ data: ${varName},
144
+ encode: { x: 'name', y: 'value', color: 'name', size: 4 },
145
+ }`;
146
+ case 'radar':
147
+ return `{
148
+ type: 'line',
149
+ data: ${varName},
150
+ encode: { x: 'name', y: 'value', color: 'name' },
151
+ coordinate: { type: 'polar' },
152
+ }`;
153
+ case 'funnel':
154
+ return `{
155
+ type: 'interval',
156
+ data: ${varName},
157
+ encode: { x: 'name', y: 'value', color: 'name' },
158
+ coordinate: { transform: [{ type: 'transpose' }] },
159
+ transform: [{ type: 'stackY' }],
160
+ scale: { y: { type: 'identity' } },
161
+ }`;
162
+ case 'treemap':
163
+ return `{
164
+ type: 'treemap',
165
+ data: ${varName},
166
+ encode: { value: 'value' },
167
+ }`;
168
+ case 'gauge':
169
+ return `{
170
+ type: 'gauge',
171
+ data: ${varName}.length ? [${varName}[0].value] : [0],
172
+ scale: { color: { range: ['#5B8FF9', '#E8E8E8'] } },
173
+ }`;
174
+ case 'liquid':
175
+ return `{
176
+ type: 'liquid',
177
+ data: ${varName}.length ? [${varName}[0].value] : [0],
178
+ style: { outline: { border: 4, distance: 8 } },
179
+ }`;
180
+ case 'heatmap':
181
+ if (widget.crossGroupBy) {
182
+ return `{
183
+ type: 'cell',
184
+ data: ${varName},
185
+ encode: { x: 'name', y: 'group', color: 'value' },
186
+ style: { inset: 1 },
187
+ }`;
188
+ }
189
+ return `{
190
+ type: 'cell',
191
+ data: ${varName},
192
+ encode: { x: 'name', y: 'name', color: 'value' },
193
+ style: { inset: 1 },
194
+ }`;
195
+ default:
196
+ return `{
197
+ type: 'interval',
198
+ data: ${varName},
199
+ encode: { x: 'name', y: 'value', color: 'name' },
200
+ }`;
201
+ }
202
+ }
203
+ /** Build stat card code */
204
+ function statCardCode(widget, modelName, pascalModelName) {
205
+ const listVar = `${pascalModelName.charAt(0).toLowerCase() + pascalModelName.slice(1)}List`;
206
+ if (widget.aggregate === 'ratio') {
207
+ // Ratio: filtered count / total count, displayed as percentage
208
+ if (widget.filter && Object.keys(widget.filter).length > 0) {
209
+ const filterExpr = Object.entries(widget.filter)
210
+ .map(([key, val]) => {
211
+ if (typeof val === 'boolean')
212
+ return `d.${key} === ${val}`;
213
+ if (typeof val === 'string')
214
+ return `d.${key} === '${val}'`;
215
+ return `d.${key} === ${JSON.stringify(val)}`;
216
+ })
217
+ .join(' && ');
218
+ return `<Statistic title="${widget.title}" value={${listVar}.length ? Math.round(${listVar}.filter(d => ${filterExpr}).length / ${listVar}.length * 1000) / 10 : 0} suffix="%" precision={1} />`;
219
+ }
220
+ return `<Statistic title="${widget.title}" value={100} suffix="%" />`;
221
+ }
222
+ if (widget.aggregate === 'count' || !widget.aggregate) {
223
+ if (widget.filter && Object.keys(widget.filter).length > 0) {
224
+ // Filtered count — use inline filter in JSX
225
+ const filterExpr = Object.entries(widget.filter)
226
+ .map(([key, val]) => {
227
+ if (typeof val === 'boolean')
228
+ return `d.${key} === ${val}`;
229
+ if (typeof val === 'string')
230
+ return `d.${key} === '${val}'`;
231
+ return `d.${key} === ${JSON.stringify(val)}`;
232
+ })
233
+ .join(' && ');
234
+ return `<Statistic title="${widget.title}" value={${listVar}.filter(d => ${filterExpr}).length} />`;
235
+ }
236
+ return `<Statistic title="${widget.title}" value={${listVar}.length} />`;
237
+ }
238
+ // sum/avg/min/max on a field
239
+ const agg = widget.aggregate;
240
+ const field = widget.field || 'value';
241
+ if (agg === 'sum') {
242
+ return `<Statistic title="${widget.title}" value={${listVar}.reduce((s, d) => s + (Number(d.${field}) || 0), 0)} precision={1} />`;
243
+ }
244
+ if (agg === 'avg') {
245
+ return `<Statistic title="${widget.title}" value={${listVar}.length ? ${listVar}.reduce((s, d) => s + (Number(d.${field}) || 0), 0) / ${listVar}.length : 0} precision={1} />`;
246
+ }
247
+ if (agg === 'min') {
248
+ return `<Statistic title="${widget.title}" value={${listVar}.length ? Math.min(...${listVar}.map(d => Number(d.${field}) || 0)) : 0} />`;
249
+ }
250
+ if (agg === 'max') {
251
+ return `<Statistic title="${widget.title}" value={${listVar}.length ? Math.max(...${listVar}.map(d => Number(d.${field}) || 0)) : 0} />`;
252
+ }
253
+ return `<Statistic title="${widget.title}" value={${listVar}.length} />`;
254
+ }
255
+ /** Detect boolean fields for groupByBoolean usage in generated code */
256
+ function detectBooleanFields(models) {
257
+ const booleanFields = new Set();
258
+ for (const model of models) {
259
+ for (const field of model.fields) {
260
+ if (field.type === 'boolean') {
261
+ booleanFields.add(`${model.name}.${field.name}`);
262
+ }
263
+ }
264
+ }
265
+ return booleanFields;
266
+ }
267
+ /** Generate the full Dashboard page TSX */
268
+ function dashboardPageTemplate(dashboard, models) {
269
+ const pascalName = toPascalCase(dashboard.name);
270
+ const pageLabel = dashboard.description || pascalName;
271
+ const booleanFields = detectBooleanFields(models);
272
+ // Collect unique model names used in widgets
273
+ const usedModels = new Set();
274
+ for (const row of dashboard.layout) {
275
+ for (const widget of row.row) {
276
+ usedModels.add(widget.model);
277
+ }
278
+ }
279
+ // Also include all declared models
280
+ for (const modelName of dashboard.models) {
281
+ usedModels.add(modelName);
282
+ }
283
+ // Build useData hooks for each model
284
+ const useDataHooks = Array.from(usedModels).map((modelName) => {
285
+ const pascalModel = toPascalCase(modelName);
286
+ const listVar = `${pascalModel.charAt(0).toLowerCase() + pascalModel.slice(1)}List`;
287
+ return `const { list: ${listVar} } = useData('${modelName}');`;
288
+ }).join('\n ');
289
+ // Build useChartData calls — collect all chart (non-stat) widget data hooks
290
+ const chartDataHooks = [];
291
+ for (const row of dashboard.layout) {
292
+ for (const widget of row.row) {
293
+ if (widget.type !== 'stat') {
294
+ const pascalModel = toPascalCase(widget.model);
295
+ const isBoolean = booleanFields.has(`${widget.model}.${widget.groupBy}`);
296
+ const hookCall = buildUseChartDataCall(widget, widget.model, pascalModel, isBoolean);
297
+ if (hookCall) {
298
+ chartDataHooks.push(hookCall);
299
+ }
300
+ }
301
+ }
302
+ }
303
+ // Build layout rows JSX
304
+ const layoutJsx = dashboard.layout.map((layoutRow) => {
305
+ const colCount = layoutRow.row.length;
306
+ const defaultSpan = Math.floor(24 / colCount);
307
+ const cols = layoutRow.row.map((widget) => {
308
+ const span = widget.span || defaultSpan;
309
+ if (widget.type === 'stat') {
310
+ const pascalModel = toPascalCase(widget.model);
311
+ const statCode = statCardCode(widget, widget.model, pascalModel);
312
+ return ` <Col span={${span}}>
313
+ <Card styles={{ body: { textAlign: 'center' } }}>
314
+ ${statCode}
315
+ </Card>
316
+ </Col>`;
317
+ }
318
+ // Chart widget
319
+ const varName = buildChartDataVarName(widget);
320
+ const spec = buildG2Spec(widget);
321
+ return ` <Col span={${span}}>
322
+ <Card title="${widget.title}" styles={{ body: { padding: '8px 16px 16px' } }}>
323
+ <DashboardChart spec={${spec}} />
324
+ </Card>
325
+ </Col>`;
326
+ }).join('\n');
327
+ return ` <Row gutter={16}>
328
+ ${cols}
329
+ </Row>`;
330
+ }).join('\n');
331
+ // Build TypeScript interfaces for each model
332
+ const interfaces = models.map((model) => {
333
+ const pascalModel = toPascalCase(model.name);
334
+ const fieldDefs = model.fields.map((f) => {
335
+ const tsType = f.type === 'string[]' ? 'string[]'
336
+ : f.type === 'number[]' ? 'number[]'
337
+ : f.type === 'json' ? 'Record<string, unknown>'
338
+ : f.type === 'number' ? 'number'
339
+ : f.type === 'boolean' ? 'boolean'
340
+ : f.type === 'date' ? 'string'
341
+ : 'string';
342
+ return ` ${f.name}${f.required ? '' : '?'}: ${tsType};`;
343
+ }).join('\n');
344
+ return `interface ${pascalModel}Record {\n id: string;\n${fieldDefs}\n}`;
345
+ }).join('\n\n');
346
+ return `import React from 'react';
347
+ import { Row, Col, Card, Statistic } from 'antd';
348
+ import { useData, useChartData, useCrossGroupChartData, useTreemapChartData, DashboardChart } from '@loom-framework/frontend-antd';
349
+
350
+ ${interfaces}
351
+
352
+ export function ${pascalName}Page(): React.ReactElement {
353
+ ${useDataHooks}
354
+ ${chartDataHooks.join('\n ')}
355
+
356
+ return (
357
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
358
+ ${layoutJsx}
359
+ </div>
360
+ );
361
+ }
362
+
363
+ export default ${pascalName}Page;
364
+ `;
365
+ }
366
+ export function registerGenerateDashboardCommand(program) {
367
+ program
368
+ .command('dashboard <name>')
369
+ .description('Generate a Dashboard page from dashboard.config.json')
370
+ .action(async (name) => {
371
+ try {
372
+ const projectRoot = await resolveProjectRoot();
373
+ const pascalName = toPascalCase(name);
374
+ // 1. Load dashboard.config.json
375
+ const dashboard = await loadDashboardConfig(projectRoot);
376
+ if (!dashboard) {
377
+ console.error(chalk.red('No dashboard.config.json found in project root.'));
378
+ console.error(chalk.dim('Create one first — see the Loom skill Dashboard section for guidance.'));
379
+ process.exit(1);
380
+ }
381
+ if (dashboard.name !== name) {
382
+ console.error(chalk.red(`Dashboard name mismatch: command expects "${name}" but config has "${dashboard.name}"`));
383
+ process.exit(1);
384
+ }
385
+ // 2. Load loom.config.ts and validate referenced models
386
+ const config = await loadConfig(projectRoot);
387
+ const modelSchemas = [];
388
+ for (const modelName of dashboard.models) {
389
+ const schema = getModelSchema(config, modelName);
390
+ if (!schema) {
391
+ console.error(chalk.red(`Model "${modelName}" referenced in dashboard.config.json not found in loom.config.ts`));
392
+ console.error(chalk.dim(`Available models: ${config.data.models.map(m => m.name).join(', ')}`));
393
+ process.exit(1);
394
+ }
395
+ modelSchemas.push(schema);
396
+ }
397
+ // 3. Generate Dashboard page
398
+ const pageDir = path.join(projectRoot, 'frontend', 'src', 'components', 'pages');
399
+ const pagePath = path.join(pageDir, `${pascalName}.tsx`);
400
+ // Check if page already exists
401
+ try {
402
+ await fs.access(pagePath);
403
+ console.error(chalk.red(`Page "${pascalName}" already exists at ${pagePath}`));
404
+ console.error(chalk.dim('Delete the existing page first if you want to regenerate.'));
405
+ process.exit(1);
406
+ }
407
+ catch {
408
+ // File does not exist, proceed
409
+ }
410
+ const template = dashboardPageTemplate(dashboard, modelSchemas);
411
+ await fs.mkdir(pageDir, { recursive: true });
412
+ await fs.writeFile(pagePath, template, 'utf-8');
413
+ console.log(chalk.green('Dashboard page created successfully!'));
414
+ console.log();
415
+ console.log(chalk.bold(' Page:'), `${pascalName}Page`);
416
+ console.log(chalk.bold(' Path:'), path.join('frontend', 'src', 'components', 'pages', `${pascalName}.tsx`));
417
+ console.log(chalk.bold(' Models:'), dashboard.models.join(', '));
418
+ console.log(chalk.bold(' Widgets:'), dashboard.layout.reduce((sum, row) => sum + row.row.length, 0).toString());
419
+ // 4. Auto-wire App.tsx
420
+ const navLabel = dashboard.description || pascalName;
421
+ const wired = await wireAppTsxAutomatic(projectRoot, pascalName, dashboard.name, navLabel);
422
+ if (wired) {
423
+ console.log(chalk.green(' App.tsx wired automatically'));
424
+ }
425
+ else {
426
+ console.log(chalk.yellow(' Could not auto-wire App.tsx — add the page import, navItem, and switch case manually'));
427
+ }
428
+ // 5. Check if @antv/g2 is installed
429
+ const frontendPkgPath = path.join(projectRoot, 'frontend', 'package.json');
430
+ try {
431
+ const pkgContent = await fs.readFile(frontendPkgPath, 'utf-8');
432
+ const pkg = JSON.parse(pkgContent);
433
+ const hasG2 = (pkg.dependencies && pkg.dependencies['@antv/g2']) ||
434
+ (pkg.devDependencies && pkg.devDependencies['@antv/g2']);
435
+ if (!hasG2) {
436
+ console.log();
437
+ console.log(chalk.yellow(' @antv/g2 is not installed in frontend/.'));
438
+ console.log(chalk.dim(' Run: cd frontend && pnpm add @antv/g2'));
439
+ }
440
+ }
441
+ catch {
442
+ // No package.json found, skip check
443
+ }
444
+ }
445
+ catch (err) {
446
+ const message = err instanceof Error ? err.message : String(err);
447
+ console.error(chalk.red('Failed to create dashboard:'), message);
448
+ process.exit(1);
449
+ }
450
+ });
451
+ }
452
+ //# sourceMappingURL=generate-dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-dashboard.js","sourceRoot":"","sources":["../../../src/cli/commands/generate-dashboard.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"generate-page.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/generate-page.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwbzC,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAwFlE"}
1
+ {"version":3,"file":"generate-page.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/generate-page.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAybzC,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAwFlE"}
@@ -11,6 +11,7 @@ import { loadConfig, getModelSchema, generateCapabilities } from '../../index.js
11
11
  import { resolveProjectRoot } from '../utils.js';
12
12
  import { toPascalCase } from '../helpers/naming.js';
13
13
  import { fieldToFormItem } from '../helpers/field-template.js';
14
+ import { wireAppTsxAutomatic, wireSkillManagementPage } from '../helpers/app-tsx-wiring.js';
14
15
  /** Semantic color mapping for common enum values */
15
16
  const SEMANTIC_COLORS = {
16
17
  'active': 'green', 'inactive': 'default', 'pending': 'orange',
@@ -514,128 +515,4 @@ export function registerGeneratePageCommand(program) {
514
515
  }
515
516
  });
516
517
  }
517
- /**
518
- * Auto-wire App.tsx: add import, navItem, and switch case for a new page.
519
- * Returns true if successful, false if App.tsx structure is unexpected.
520
- */
521
- async function wireAppTsxAutomatic(projectRoot, pascalName, modelKey, modelLabel) {
522
- const appPath = path.join(projectRoot, 'frontend', 'src', 'App.tsx');
523
- let content;
524
- try {
525
- content = await fs.readFile(appPath, 'utf-8');
526
- }
527
- catch {
528
- return false;
529
- }
530
- // Check if this page is already wired (avoid duplicates)
531
- if (content.includes(`from './components/pages/${pascalName}'`) || content.includes(`<${pascalName}Page`)) {
532
- return false;
533
- }
534
- // Also check if navItem with same key already exists
535
- if (content.includes(`key: '${modelKey}'`)) {
536
- return false;
537
- }
538
- let modified = content;
539
- // 1. Add import after the last import line
540
- const importLine = `import ${pascalName}Page from './components/pages/${pascalName}';`;
541
- const lastImportEnd = modified.lastIndexOf('\n', modified.indexOf('\nexport'));
542
- if (lastImportEnd === -1)
543
- return false;
544
- modified = modified.slice(0, lastImportEnd + 1) + importLine + '\n' + modified.slice(lastImportEnd + 1);
545
- // 2. Add navItem (before the closing ] of navItems array)
546
- const navItemLine = ` { key: '${modelKey}', label: '${modelLabel}' },`;
547
- const navItemsEnd = modified.indexOf('// Add more nav items');
548
- if (navItemsEnd !== -1) {
549
- // Insert before the comment placeholder
550
- const lineStart = modified.lastIndexOf('\n', navItemsEnd) + 1;
551
- modified = modified.slice(0, lineStart) + navItemLine + '\n' + modified.slice(lineStart);
552
- }
553
- else {
554
- // Fallback: find the closing ] of navItems array
555
- const navArrayMatch = modified.match(/const navItems\s*=\s*\[/);
556
- if (!navArrayMatch)
557
- return false;
558
- const arrayStart = navArrayMatch.index + navArrayMatch[0].length;
559
- // Find the matching ]
560
- let depth = 1;
561
- let pos = arrayStart;
562
- while (pos < modified.length && depth > 0) {
563
- if (modified[pos] === '[')
564
- depth++;
565
- else if (modified[pos] === ']')
566
- depth--;
567
- pos++;
568
- }
569
- const closingBracket = pos - 1;
570
- modified = modified.slice(0, closingBracket) + navItemLine + '\n' + modified.slice(closingBracket);
571
- }
572
- // 3. Add switch case — before skill-management case if present, otherwise before default
573
- const caseLine = ` case '${modelKey}': return <${pascalName}Page />;`;
574
- const skillCaseIdx = modified.indexOf("case 'skill-management':");
575
- const insertAnchor = skillCaseIdx !== -1 ? skillCaseIdx : modified.indexOf('default:');
576
- if (insertAnchor === -1)
577
- return false;
578
- const insertLineStart = modified.lastIndexOf('\n', insertAnchor) + 1;
579
- modified = modified.slice(0, insertLineStart) + caseLine + '\n' + modified.slice(insertLineStart);
580
- // 4. Ensure baseUrl="" on AppShell (prevent /api/v1/api/v1 double prefix)
581
- if (modified.includes('<AppShell') && !modified.includes('baseUrl=')) {
582
- modified = modified.replace('<AppShell', '<AppShell\n baseUrl=""');
583
- }
584
- await fs.writeFile(appPath, modified, 'utf-8');
585
- return true;
586
- }
587
- /**
588
- * Auto-wire the SkillManagementPage into App.tsx.
589
- * Called once when the first CRUD page is generated.
590
- * Returns true if successful, false if already wired or structure is unexpected.
591
- */
592
- async function wireSkillManagementPage(projectRoot) {
593
- const appPath = path.join(projectRoot, 'frontend', 'src', 'App.tsx');
594
- let content;
595
- try {
596
- content = await fs.readFile(appPath, 'utf-8');
597
- }
598
- catch {
599
- return false;
600
- }
601
- // Check if already wired (avoid duplicates)
602
- if (content.includes('SkillManagementPage')) {
603
- return false;
604
- }
605
- let modified = content;
606
- // 1. Add import after the last import line
607
- const importLine = `import { SkillManagementPage } from '@loom-framework/frontend-antd';`;
608
- const lastImportEnd = modified.lastIndexOf('\n', modified.indexOf('\nexport'));
609
- if (lastImportEnd === -1)
610
- return false;
611
- modified = modified.slice(0, lastImportEnd + 1) + importLine + '\n' + modified.slice(lastImportEnd + 1);
612
- // 2. Add navItem — always at the end of the array so it stays below all model pages
613
- const navItemLine = ` { key: 'skill-management', label: 'Skill 管理' },`;
614
- const navArrayMatch = modified.match(/const navItems\s*=\s*\[/);
615
- if (!navArrayMatch)
616
- return false;
617
- const arrayStart = navArrayMatch.index + navArrayMatch[0].length;
618
- let depth = 1;
619
- let pos = arrayStart;
620
- while (pos < modified.length && depth > 0) {
621
- if (modified[pos] === '[')
622
- depth++;
623
- else if (modified[pos] === ']')
624
- depth--;
625
- pos++;
626
- }
627
- const closingBracket = pos - 1;
628
- // Insert before the line containing the closing bracket
629
- const lineStart = modified.lastIndexOf('\n', closingBracket) + 1;
630
- modified = modified.slice(0, lineStart) + navItemLine + '\n' + modified.slice(lineStart);
631
- // 3. Add switch case before default
632
- const caseLine = ` case 'skill-management': return <SkillManagementPage />;`;
633
- const defaultCase = modified.indexOf('default:');
634
- if (defaultCase === -1)
635
- return false;
636
- const defaultLineStart = modified.lastIndexOf('\n', defaultCase) + 1;
637
- modified = modified.slice(0, defaultLineStart) + caseLine + '\n' + modified.slice(defaultLineStart);
638
- await fs.writeFile(appPath, modified, 'utf-8');
639
- return true;
640
- }
641
518
  //# sourceMappingURL=generate-page.js.map