@loom-framework/core 0.1.0-alpha.164 → 0.1.0-alpha.166
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/builtin-skills/loom/SKILL.md +9 -6
- package/builtin-skills/loom/references/eject.md +102 -0
- package/dist/cli/commands/eject.d.ts.map +1 -1
- package/dist/cli/commands/eject.js +52 -285
- package/dist/cli/commands/eject.js.map +1 -1
- package/dist/cli/commands/generate-system-settings.d.ts +3 -3
- package/dist/cli/commands/generate-system-settings.d.ts.map +1 -1
- package/dist/cli/commands/generate-system-settings.js +29 -204
- package/dist/cli/commands/generate-system-settings.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +27 -44
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/helpers/app-tsx-utils.d.ts +12 -0
- package/dist/cli/helpers/app-tsx-utils.d.ts.map +1 -0
- package/dist/cli/helpers/app-tsx-utils.js +22 -0
- package/dist/cli/helpers/app-tsx-utils.js.map +1 -0
- package/dist/cli/helpers/app-tsx-wiring-engine.d.ts +70 -0
- package/dist/cli/helpers/app-tsx-wiring-engine.d.ts.map +1 -0
- package/dist/cli/helpers/app-tsx-wiring-engine.js +546 -0
- package/dist/cli/helpers/app-tsx-wiring-engine.js.map +1 -0
- package/dist/cli/helpers/app-tsx-wiring.d.ts +4 -39
- package/dist/cli/helpers/app-tsx-wiring.d.ts.map +1 -1
- package/dist/cli/helpers/app-tsx-wiring.js +5 -608
- package/dist/cli/helpers/app-tsx-wiring.js.map +1 -1
- package/dist/cli/helpers/system-page-config.d.ts +27 -0
- package/dist/cli/helpers/system-page-config.d.ts.map +1 -0
- package/dist/cli/helpers/system-page-config.js +73 -0
- package/dist/cli/helpers/system-page-config.js.map +1 -0
- package/dist/cli/templates/login-page.d.ts +2 -3
- package/dist/cli/templates/login-page.d.ts.map +1 -1
- package/dist/cli/templates/login-page.js +18 -29
- package/dist/cli/templates/login-page.js.map +1 -1
- package/dist/cli/templates/model-management-page.d.ts +2 -3
- package/dist/cli/templates/model-management-page.d.ts.map +1 -1
- package/dist/cli/templates/model-management-page.js +11 -9
- package/dist/cli/templates/model-management-page.js.map +1 -1
- package/dist/cli/templates/notification-center-page.d.ts +2 -3
- package/dist/cli/templates/notification-center-page.d.ts.map +1 -1
- package/dist/cli/templates/notification-center-page.js +25 -62
- package/dist/cli/templates/notification-center-page.js.map +1 -1
- package/dist/cli/templates/notification-detail-page.d.ts +2 -3
- package/dist/cli/templates/notification-detail-page.d.ts.map +1 -1
- package/dist/cli/templates/notification-detail-page.js +13 -41
- package/dist/cli/templates/notification-detail-page.js.map +1 -1
- package/dist/cli/templates/process-management-page.d.ts +3 -3
- package/dist/cli/templates/process-management-page.d.ts.map +1 -1
- package/dist/cli/templates/process-management-page.js +75 -565
- package/dist/cli/templates/process-management-page.js.map +1 -1
- package/dist/cli/templates/skill-management-page.d.ts +2 -3
- package/dist/cli/templates/skill-management-page.d.ts.map +1 -1
- package/dist/cli/templates/skill-management-page.js +12 -15
- package/dist/cli/templates/skill-management-page.js.map +1 -1
- package/dist/cli/templates/user-management-page.d.ts +2 -4
- package/dist/cli/templates/user-management-page.d.ts.map +1 -1
- package/dist/cli/templates/user-management-page.js +21 -22
- package/dist/cli/templates/user-management-page.js.map +1 -1
- package/package.json +4 -2
|
@@ -3,40 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Surgically modifies App.tsx to add imports, navItems, and switch cases.
|
|
5
5
|
* Nav labels use t() calls, so i18n keys are registered via registerMessages.
|
|
6
|
+
*
|
|
7
|
+
* For system settings pages (model/skill/user/login/process/notification),
|
|
8
|
+
* use wireAppTsx() from app-tsx-wiring-engine.ts instead.
|
|
6
9
|
*/
|
|
7
10
|
import { promises as fs } from 'fs';
|
|
8
11
|
import path from 'path';
|
|
9
|
-
|
|
10
|
-
* Find the index of the newline character immediately before the first
|
|
11
|
-
* JS/TS export statement. Matches `export default function`, `export function`,
|
|
12
|
-
* `export const`, `export class`, `export type`, `export interface`.
|
|
13
|
-
*
|
|
14
|
-
* More robust than `indexOf('\nexport')` — avoids false matches on the word
|
|
15
|
-
* "export" inside strings, comments, or object literals.
|
|
16
|
-
*/
|
|
17
|
-
function findExportLineIndex(content) {
|
|
18
|
-
const match = content.match(/\nexport\s+(?:default\s+)?(?:function|const|let|class|type|interface)\s/);
|
|
19
|
-
return match && match.index !== undefined ? match.index : null;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Find the position right after the last import statement.
|
|
23
|
-
* Returns the index of the newline after the last `import ... from '...'` or `import ...` line.
|
|
24
|
-
*/
|
|
25
|
-
function findLastImportEnd(content) {
|
|
26
|
-
const importRegex = /^import\s.+;?\s*$/gm;
|
|
27
|
-
let lastMatch = null;
|
|
28
|
-
let m;
|
|
29
|
-
while ((m = importRegex.exec(content)) !== null) {
|
|
30
|
-
lastMatch = m;
|
|
31
|
-
}
|
|
32
|
-
if (!lastMatch)
|
|
33
|
-
return null;
|
|
34
|
-
// Return the position right after the last import line (including its newline)
|
|
35
|
-
const endPos = lastMatch.index + lastMatch[0].length;
|
|
36
|
-
return endPos;
|
|
37
|
-
}
|
|
12
|
+
import { findLastImportEnd } from './app-tsx-utils.js';
|
|
38
13
|
/**
|
|
39
14
|
* Auto-wire App.tsx: add import, navItem, and switch case for a new page.
|
|
15
|
+
* Used by `loom generate page` for CRUD pages (not system settings pages).
|
|
40
16
|
* Returns true if successful, false if App.tsx structure is unexpected.
|
|
41
17
|
*/
|
|
42
18
|
export async function wireAppTsxAutomatic(projectRoot, pascalName, navKey, navLabel, options) {
|
|
@@ -166,583 +142,4 @@ export async function wireAppTsxAutomatic(projectRoot, pascalName, navKey, navLa
|
|
|
166
142
|
await fs.writeFile(appPath, modified, 'utf-8');
|
|
167
143
|
return true;
|
|
168
144
|
}
|
|
169
|
-
/**
|
|
170
|
-
* Auto-wire the system-settings navigation group into App.tsx.
|
|
171
|
-
* Creates or extends a system-settings submenu with the given page as a child.
|
|
172
|
-
*
|
|
173
|
-
* On first call, creates the submenu structure. On subsequent calls, appends
|
|
174
|
-
* the new child to the existing submenu. Also handles migration from legacy
|
|
175
|
-
* npm-import pattern to local imports.
|
|
176
|
-
*
|
|
177
|
-
* Returns true if wiring was performed, false if already wired or structure is unexpected.
|
|
178
|
-
*/
|
|
179
|
-
export async function wireAppTsxForSystemSettings(projectRoot, pascalName, navKey, navLabelZh, navLabelEn) {
|
|
180
|
-
const appPath = path.join(projectRoot, 'frontend', 'src', 'App.tsx');
|
|
181
|
-
let content;
|
|
182
|
-
try {
|
|
183
|
-
content = await fs.readFile(appPath, 'utf-8');
|
|
184
|
-
}
|
|
185
|
-
catch {
|
|
186
|
-
return false;
|
|
187
|
-
}
|
|
188
|
-
// Check if this page is already wired with local import (npm imports are migrated below)
|
|
189
|
-
if (content.includes(`from './components/pages/${pascalName}'`)) {
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
let modified = content;
|
|
193
|
-
// ── Migration: replace legacy npm imports with local imports ──
|
|
194
|
-
const migratedImports = [];
|
|
195
|
-
const migratedComponents = [];
|
|
196
|
-
// Find npm imports that contain page components (ending with 'Page')
|
|
197
|
-
const legacyPageImport = modified.match(/import\s*\{([^}]+)\}\s*from\s*'@loom-framework\/frontend-antd';/g);
|
|
198
|
-
if (legacyPageImport) {
|
|
199
|
-
for (const importLine of legacyPageImport) {
|
|
200
|
-
const namesMatch = importLine.match(/import\s*\{([^}]+)\}/);
|
|
201
|
-
if (!namesMatch)
|
|
202
|
-
continue;
|
|
203
|
-
const importNames = namesMatch[1].split(',').map(s => s.trim()).filter(Boolean);
|
|
204
|
-
const pageNames = importNames.filter(n => n.endsWith('Page'));
|
|
205
|
-
const nonPageNames = importNames.filter(n => !n.endsWith('Page'));
|
|
206
|
-
if (pageNames.length === 0)
|
|
207
|
-
continue;
|
|
208
|
-
for (const name of pageNames) {
|
|
209
|
-
const pascalName = name.slice(0, -4);
|
|
210
|
-
migratedImports.push(`import ${name} from './components/pages/${pascalName}';`);
|
|
211
|
-
const navKey = pascalName.replace(/([A-Z])/g, (m, c, i) => i === 0 ? c.toLowerCase() : '-' + c.toLowerCase()).replace(/^-/, '');
|
|
212
|
-
migratedComponents.push({ pascalName, navKey });
|
|
213
|
-
}
|
|
214
|
-
// Remove page names from npm import, keep non-page imports
|
|
215
|
-
if (nonPageNames.length > 0) {
|
|
216
|
-
const keptImport = `import { ${nonPageNames.join(', ')} } from '@loom-framework/frontend-antd';`;
|
|
217
|
-
modified = modified.replace(importLine, keptImport);
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
modified = modified.replace(importLine, '');
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
const legacySystemImport = modified.match(/import\s*\{\s*SystemSettingsPage\s*\}\s*from\s*'@loom-framework\/frontend-antd';/);
|
|
225
|
-
if (legacySystemImport) {
|
|
226
|
-
modified = modified.replace(legacySystemImport[0], '');
|
|
227
|
-
}
|
|
228
|
-
// ── 1. Add local import ──
|
|
229
|
-
const importLine = `import ${pascalName}Page from './components/pages/${pascalName}';`;
|
|
230
|
-
let lastImportEnd = findLastImportEnd(modified);
|
|
231
|
-
if (lastImportEnd === null)
|
|
232
|
-
return false;
|
|
233
|
-
modified = modified.slice(0, lastImportEnd) + '\n' + importLine + modified.slice(lastImportEnd);
|
|
234
|
-
// ── 1a. Add migrated imports (from legacy npm imports) ──
|
|
235
|
-
if (migratedImports.length > 0) {
|
|
236
|
-
for (const migImport of migratedImports) {
|
|
237
|
-
if (!modified.includes(migImport)) {
|
|
238
|
-
lastImportEnd = findLastImportEnd(modified);
|
|
239
|
-
if (lastImportEnd === null)
|
|
240
|
-
return false;
|
|
241
|
-
modified = modified.slice(0, lastImportEnd) + '\n' + migImport + modified.slice(lastImportEnd);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
// ── 1b. Ensure registerMessages is imported ──
|
|
246
|
-
if (!modified.includes('registerMessages')) {
|
|
247
|
-
modified = modified.replace(/\}\s*from\s*'@loom-framework\/frontend-antd';/, ", registerMessages } from '@loom-framework/frontend-antd';");
|
|
248
|
-
}
|
|
249
|
-
// ── 1c. Register i18n keys ──
|
|
250
|
-
// nav.system-settings (register if not present)
|
|
251
|
-
const i18nLines = [];
|
|
252
|
-
if (!modified.includes("'nav.system-settings'")) {
|
|
253
|
-
i18nLines.push(`registerMessages('zh-CN', { 'nav.system-settings': '系统配置' });`, `registerMessages('en-US', { 'nav.system-settings': 'System Settings' });`);
|
|
254
|
-
}
|
|
255
|
-
// nav.navKey for this page
|
|
256
|
-
if (!modified.includes(`'nav.${navKey}'`)) {
|
|
257
|
-
i18nLines.push(`registerMessages('zh-CN', { 'nav.${navKey}': '${navLabelZh}' });`, `registerMessages('en-US', { 'nav.${navKey}': '${navLabelEn}' });`);
|
|
258
|
-
}
|
|
259
|
-
if (i18nLines.length > 0) {
|
|
260
|
-
const registerBlock = i18nLines.join('\n');
|
|
261
|
-
const afterImportsIdx = findLastImportEnd(modified);
|
|
262
|
-
if (afterImportsIdx === null)
|
|
263
|
-
return false;
|
|
264
|
-
modified = modified.slice(0, afterImportsIdx) + '\n' + registerBlock + modified.slice(afterImportsIdx);
|
|
265
|
-
}
|
|
266
|
-
// ── 2. Handle navItems ──
|
|
267
|
-
const childNavItem = `{ key: '${navKey}', label: t('nav.${navKey}') }`;
|
|
268
|
-
if (modified.includes("key: 'system-settings'") && modified.includes('children:')) {
|
|
269
|
-
// Submenu already exists — check if this child already exists
|
|
270
|
-
const childAlreadyInNav = modified.includes(`key: '${navKey}', label: t('nav.${navKey}')`) ||
|
|
271
|
-
modified.includes(`key: '${navKey}', label:`);
|
|
272
|
-
if (childAlreadyInNav) {
|
|
273
|
-
// Child already in submenu, skip navItem modification
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
// Append child to children array
|
|
277
|
-
const childrenMatch = modified.match(/key:\s*'system-settings',\s*label:\s*t\('nav\.system-settings'\),\s*children:\s*\[/);
|
|
278
|
-
if (childrenMatch) {
|
|
279
|
-
// Find the closing ] of the children array
|
|
280
|
-
const childrenStart = childrenMatch.index + childrenMatch[0].length;
|
|
281
|
-
let depth = 1;
|
|
282
|
-
let pos = childrenStart;
|
|
283
|
-
while (pos < modified.length && depth > 0) {
|
|
284
|
-
if (modified[pos] === '[')
|
|
285
|
-
depth++;
|
|
286
|
-
else if (modified[pos] === ']')
|
|
287
|
-
depth--;
|
|
288
|
-
pos++;
|
|
289
|
-
}
|
|
290
|
-
const childrenEnd = pos - 1;
|
|
291
|
-
// Insert before the closing ]
|
|
292
|
-
const beforeClose = modified.slice(0, childrenEnd).trimEnd();
|
|
293
|
-
const needsComma = beforeClose.endsWith('}');
|
|
294
|
-
modified = beforeClose + (needsComma ? ',' : '') + '\n ' + childNavItem + modified.slice(childrenEnd);
|
|
295
|
-
}
|
|
296
|
-
} // end if childAlreadyInNav
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
// No submenu exists — create it
|
|
300
|
-
// First, remove any old flat navItem for system-settings (legacy migration)
|
|
301
|
-
modified = modified.replace(/\{\s*key:\s*'system-settings',\s*label:\s*t\('nav\.system-settings'\)\s*\},?\n?/g, '');
|
|
302
|
-
// Build the submenu navItem
|
|
303
|
-
const submenuItem = ` { key: 'system-settings', label: t('nav.system-settings'), children: [
|
|
304
|
-
${childNavItem},
|
|
305
|
-
] },`;
|
|
306
|
-
// Find where to insert: at the end of the navItems array, or before comment placeholder
|
|
307
|
-
const commentIdx = modified.indexOf('// Add nav items');
|
|
308
|
-
if (commentIdx !== -1) {
|
|
309
|
-
const lineStart = modified.lastIndexOf('\n', commentIdx) + 1;
|
|
310
|
-
modified = modified.slice(0, lineStart) + submenuItem + '\n' + modified.slice(lineStart);
|
|
311
|
-
}
|
|
312
|
-
else {
|
|
313
|
-
// Append at end of navItems array
|
|
314
|
-
const navArrayMatch = modified.match(/const navItems\s*=\s*\[/);
|
|
315
|
-
if (!navArrayMatch)
|
|
316
|
-
return false;
|
|
317
|
-
const arrayStart = navArrayMatch.index + navArrayMatch[0].length;
|
|
318
|
-
let depth = 1;
|
|
319
|
-
let pos = arrayStart;
|
|
320
|
-
while (pos < modified.length && depth > 0) {
|
|
321
|
-
if (modified[pos] === '[')
|
|
322
|
-
depth++;
|
|
323
|
-
else if (modified[pos] === ']')
|
|
324
|
-
depth--;
|
|
325
|
-
pos++;
|
|
326
|
-
}
|
|
327
|
-
const closingBracket = pos - 1;
|
|
328
|
-
const lineStart = modified.lastIndexOf('\n', closingBracket) + 1;
|
|
329
|
-
modified = modified.slice(0, lineStart) + submenuItem + '\n' + modified.slice(lineStart);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
// ── 3. Add switch case ──
|
|
333
|
-
const caseLine = ` case '${navKey}': return <${pascalName}Page />;`;
|
|
334
|
-
// Remove legacy switch cases: case 'system-settings': return <SystemSettingsPage />;
|
|
335
|
-
if (legacySystemImport) {
|
|
336
|
-
modified = modified.replace(/case\s*'system-settings':\s*return\s*<SystemSettingsPage\s*\/>;\n?/g, '');
|
|
337
|
-
}
|
|
338
|
-
// Remove legacy npm-import switch cases only when migrating from npm imports
|
|
339
|
-
if (migratedComponents.length > 0) {
|
|
340
|
-
// Remove legacy npm-import switch cases for known system pages
|
|
341
|
-
modified = modified.replace(/case\s*'model-management':\s*return\s*<ModelManagementPage\s*\/>;\n?/g, '');
|
|
342
|
-
modified = modified.replace(/case\s*'skill-management':\s*return\s*<SkillManagementPage\s*\/>;\n?/g, '');
|
|
343
|
-
}
|
|
344
|
-
// Only add new case if it doesn't already exist
|
|
345
|
-
if (!modified.includes(`case '${navKey}':`)) {
|
|
346
|
-
// Insert before default case
|
|
347
|
-
const defaultIdx = modified.indexOf('default:');
|
|
348
|
-
if (defaultIdx !== -1) {
|
|
349
|
-
const lineStart = modified.lastIndexOf('\n', defaultIdx) + 1;
|
|
350
|
-
modified = modified.slice(0, lineStart) + caseLine + '\n' + modified.slice(lineStart);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
// Add switch cases for migrated components (they were removed above as npm-import cases)
|
|
354
|
-
for (const { pascalName: migPascal, navKey: migKey } of migratedComponents) {
|
|
355
|
-
if (migKey !== navKey && !modified.includes(`case '${migKey}':`)) {
|
|
356
|
-
const migCaseLine = ` case '${migKey}': return <${migPascal}Page />;`;
|
|
357
|
-
const defaultIdx = modified.indexOf('default:');
|
|
358
|
-
if (defaultIdx !== -1) {
|
|
359
|
-
const lineStart = modified.lastIndexOf('\n', defaultIdx) + 1;
|
|
360
|
-
modified = modified.slice(0, lineStart) + migCaseLine + '\n' + modified.slice(lineStart);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
// ── 4. Add breadcrumb mapping ──
|
|
365
|
-
const breadcrumbLine = ` '${navKey}': [{ title: t('nav.${navKey}') }],`;
|
|
366
|
-
// Check if breadcrumb already exists in the breadcrumbMap object (not in switch cases or other locations)
|
|
367
|
-
const bcMapMatch = modified.match(/const\s+breadcrumbMap[^=]*=\s*\{([\s\S]*?)\n\s*\};/);
|
|
368
|
-
const hasBreadcrumb = bcMapMatch ? bcMapMatch[1].includes(`'${navKey}':`) : modified.includes(`'${navKey}': [{ title:`);
|
|
369
|
-
if (!hasBreadcrumb) {
|
|
370
|
-
const breadcrumbCommentIdx = modified.indexOf('// Add breadcrumb mappings');
|
|
371
|
-
if (breadcrumbCommentIdx !== -1) {
|
|
372
|
-
const lineStart = modified.lastIndexOf('\n', breadcrumbCommentIdx) + 1;
|
|
373
|
-
modified = modified.slice(0, lineStart) + breadcrumbLine + '\n' + modified.slice(lineStart);
|
|
374
|
-
}
|
|
375
|
-
else {
|
|
376
|
-
// Add to existing breadcrumbMap
|
|
377
|
-
const mapMatch = modified.match(/const\s+breadcrumbMap[\s\S]*?\{/);
|
|
378
|
-
const mapObjMatch = modified.match(/const\s+breadcrumbMap[^=]*=\s*\{/);
|
|
379
|
-
if (mapObjMatch) {
|
|
380
|
-
const mapStart = mapObjMatch.index + mapObjMatch[0].length;
|
|
381
|
-
modified = modified.slice(0, mapStart) + '\n' + breadcrumbLine + modified.slice(mapStart);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
// Also add system-settings breadcrumb if not present
|
|
386
|
-
if (!modified.includes("'system-settings':") && modified.includes("key: 'system-settings'")) {
|
|
387
|
-
const sysBcLine = ` 'system-settings': [{ title: t('nav.system-settings') }],`;
|
|
388
|
-
const breadcrumbCommentIdx = modified.indexOf('// Add breadcrumb mappings');
|
|
389
|
-
if (breadcrumbCommentIdx !== -1) {
|
|
390
|
-
const lineStart = modified.lastIndexOf('\n', breadcrumbCommentIdx) + 1;
|
|
391
|
-
modified = modified.slice(0, lineStart) + sysBcLine + '\n' + modified.slice(lineStart);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
// ── 5. Migration cleanups ──
|
|
395
|
-
// Ensure breadcrumbsMap prop on AppShell
|
|
396
|
-
if (modified.includes('<AppShell')) {
|
|
397
|
-
if (modified.includes('breadcrumbs={breadcrumbMap[selectedKey]}')) {
|
|
398
|
-
modified = modified.replace('breadcrumbs={breadcrumbMap[selectedKey]}', 'breadcrumbsMap={breadcrumbMap}');
|
|
399
|
-
}
|
|
400
|
-
else if (!modified.includes('breadcrumbsMap=')) {
|
|
401
|
-
modified = modified.replace('onNavClick={setSelectedKey}', 'onNavClick={setSelectedKey}\n breadcrumbsMap={breadcrumbMap}');
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
// Ensure baseUrl="" on AppShell
|
|
405
|
-
if (modified.includes('<AppShell') && !modified.includes('baseUrl=')) {
|
|
406
|
-
modified = modified.replace('<AppShell', '<AppShell\n baseUrl=""');
|
|
407
|
-
}
|
|
408
|
-
await fs.writeFile(appPath, modified, 'utf-8');
|
|
409
|
-
return true;
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Wire App.tsx for npm-first system settings pages.
|
|
413
|
-
* Instead of writing local files, imports page components directly from @loom-framework/frontend-antd.
|
|
414
|
-
* Follows the same nav/breadcrumb/switch pattern as wireAppTsxForSystemSettings but uses npm imports.
|
|
415
|
-
*
|
|
416
|
-
* Returns true if wiring was performed, false if already wired or structure unexpected.
|
|
417
|
-
*/
|
|
418
|
-
export async function wireAppTsxForNpmSystemSettings(projectRoot, pascalName, navKey, navLabelZh, navLabelEn, options) {
|
|
419
|
-
const appPath = path.join(projectRoot, 'frontend', 'src', 'App.tsx');
|
|
420
|
-
let content;
|
|
421
|
-
try {
|
|
422
|
-
content = await fs.readFile(appPath, 'utf-8');
|
|
423
|
-
}
|
|
424
|
-
catch {
|
|
425
|
-
return false;
|
|
426
|
-
}
|
|
427
|
-
// Check if this page is already wired (either local or npm import)
|
|
428
|
-
if (content.includes(`from './components/pages/${pascalName}'`) || content.includes(`${pascalName}Page`)) {
|
|
429
|
-
return false;
|
|
430
|
-
}
|
|
431
|
-
let modified = content;
|
|
432
|
-
// ── 1. Add npm import for the page component ──
|
|
433
|
-
const pageComponentName = `${pascalName}Page`;
|
|
434
|
-
const existingNpmImport = modified.match(/import\s*\{([^}]+)\}\s*from\s*'@loom-framework\/frontend-antd';/);
|
|
435
|
-
if (existingNpmImport) {
|
|
436
|
-
const currentNames = existingNpmImport[1].split(',').map(s => s.trim()).filter(Boolean);
|
|
437
|
-
if (!currentNames.includes(pageComponentName)) {
|
|
438
|
-
const newNames = [...currentNames, pageComponentName].join(', ');
|
|
439
|
-
const newImport = `import { ${newNames} } from '@loom-framework/frontend-antd';`;
|
|
440
|
-
modified = modified.replace(existingNpmImport[0], newImport);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
else {
|
|
444
|
-
const importLine = `import { ${pageComponentName} } from '@loom-framework/frontend-antd';`;
|
|
445
|
-
const lastImportEnd = findLastImportEnd(modified);
|
|
446
|
-
if (lastImportEnd === null)
|
|
447
|
-
return false;
|
|
448
|
-
modified = modified.slice(0, lastImportEnd) + '\n' + importLine + modified.slice(lastImportEnd);
|
|
449
|
-
}
|
|
450
|
-
// ── 1b. Ensure registerMessages is imported ──
|
|
451
|
-
if (!modified.includes('registerMessages')) {
|
|
452
|
-
const npmImportMatch = modified.match(/import\s*\{([^}]+)\}\s*from\s*'@loom-framework\/frontend-antd';/);
|
|
453
|
-
if (npmImportMatch) {
|
|
454
|
-
const currentNames = npmImportMatch[1].split(',').map(s => s.trim()).filter(Boolean);
|
|
455
|
-
if (!currentNames.includes('registerMessages')) {
|
|
456
|
-
const newNames = [...currentNames, 'registerMessages'].join(', ');
|
|
457
|
-
const newImport = `import { ${newNames} } from '@loom-framework/frontend-antd';`;
|
|
458
|
-
modified = modified.replace(npmImportMatch[0], newImport);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
// ── 1c. Register i18n keys ──
|
|
463
|
-
const i18nLines = [];
|
|
464
|
-
if (!modified.includes("'nav.system-settings'")) {
|
|
465
|
-
i18nLines.push(`registerMessages('zh-CN', { 'nav.system-settings': '系统配置' });`, `registerMessages('en-US', { 'nav.system-settings': 'System Settings' });`);
|
|
466
|
-
}
|
|
467
|
-
if (!modified.includes(`'nav.${navKey}'`)) {
|
|
468
|
-
i18nLines.push(`registerMessages('zh-CN', { 'nav.${navKey}': '${navLabelZh}' });`, `registerMessages('en-US', { 'nav.${navKey}': '${navLabelEn}' });`);
|
|
469
|
-
}
|
|
470
|
-
if (i18nLines.length > 0) {
|
|
471
|
-
const registerBlock = i18nLines.join('\n');
|
|
472
|
-
const afterImportsIdx = findLastImportEnd(modified);
|
|
473
|
-
if (afterImportsIdx === null)
|
|
474
|
-
return false;
|
|
475
|
-
modified = modified.slice(0, afterImportsIdx) + '\n' + registerBlock + modified.slice(afterImportsIdx);
|
|
476
|
-
}
|
|
477
|
-
// ── 2. Handle navItems ── (same logic as wireAppTsxForSystemSettings)
|
|
478
|
-
// Skip nav item for pages accessed via other UI elements (e.g., notification bell icon)
|
|
479
|
-
if (!options?.skipNav) {
|
|
480
|
-
const childNavItem = `{ key: '${navKey}', label: t('nav.${navKey}') }`;
|
|
481
|
-
if (modified.includes("key: 'system-settings'") && modified.includes('children:')) {
|
|
482
|
-
const childAlreadyInNav = modified.includes(`key: '${navKey}', label: t('nav.${navKey}')`) ||
|
|
483
|
-
modified.includes(`key: '${navKey}', label:`);
|
|
484
|
-
if (!childAlreadyInNav) {
|
|
485
|
-
const childrenMatch = modified.match(/key:\s*'system-settings',\s*label:\s*t\('nav\.system-settings'\),\s*children:\s*\[/);
|
|
486
|
-
if (childrenMatch) {
|
|
487
|
-
const childrenStart = childrenMatch.index + childrenMatch[0].length;
|
|
488
|
-
let depth = 1;
|
|
489
|
-
let pos = childrenStart;
|
|
490
|
-
while (pos < modified.length && depth > 0) {
|
|
491
|
-
if (modified[pos] === '[')
|
|
492
|
-
depth++;
|
|
493
|
-
else if (modified[pos] === ']')
|
|
494
|
-
depth--;
|
|
495
|
-
pos++;
|
|
496
|
-
}
|
|
497
|
-
const childrenEnd = pos - 1;
|
|
498
|
-
const beforeClose = modified.slice(0, childrenEnd).trimEnd();
|
|
499
|
-
const needsComma = beforeClose.endsWith('}');
|
|
500
|
-
modified = beforeClose + (needsComma ? ',' : '') + '\n ' + childNavItem + modified.slice(childrenEnd);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
else {
|
|
505
|
-
modified = modified.replace(/\{\s*key:\s*'system-settings',\s*label:\s*t\('nav\.system-settings'\)\s*\},?\n?/g, '');
|
|
506
|
-
const submenuItem = ` { key: 'system-settings', label: t('nav.system-settings'), children: [
|
|
507
|
-
${childNavItem},
|
|
508
|
-
] },`;
|
|
509
|
-
const commentIdx = modified.indexOf('// Add nav items');
|
|
510
|
-
if (commentIdx !== -1) {
|
|
511
|
-
const lineStart = modified.lastIndexOf('\n', commentIdx) + 1;
|
|
512
|
-
modified = modified.slice(0, lineStart) + submenuItem + '\n' + modified.slice(lineStart);
|
|
513
|
-
}
|
|
514
|
-
else {
|
|
515
|
-
const navArrayMatch = modified.match(/const navItems\s*=\s*\[/);
|
|
516
|
-
if (!navArrayMatch)
|
|
517
|
-
return false;
|
|
518
|
-
const arrayStart = navArrayMatch.index + navArrayMatch[0].length;
|
|
519
|
-
let depth = 1;
|
|
520
|
-
let pos = arrayStart;
|
|
521
|
-
while (pos < modified.length && depth > 0) {
|
|
522
|
-
if (modified[pos] === '[')
|
|
523
|
-
depth++;
|
|
524
|
-
else if (modified[pos] === ']')
|
|
525
|
-
depth--;
|
|
526
|
-
pos++;
|
|
527
|
-
}
|
|
528
|
-
const closingBracket = pos - 1;
|
|
529
|
-
const lineStart = modified.lastIndexOf('\n', closingBracket) + 1;
|
|
530
|
-
modified = modified.slice(0, lineStart) + submenuItem + '\n' + modified.slice(lineStart);
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
} // end if (!skipNav)
|
|
534
|
-
// ── 3. Add switch case ──
|
|
535
|
-
const caseLine = ` case '${navKey}': return <${pageComponentName} />;`;
|
|
536
|
-
if (!modified.includes(`case '${navKey}':`)) {
|
|
537
|
-
const defaultIdx = modified.indexOf('default:');
|
|
538
|
-
if (defaultIdx !== -1) {
|
|
539
|
-
const lineStart = modified.lastIndexOf('\n', defaultIdx) + 1;
|
|
540
|
-
modified = modified.slice(0, lineStart) + caseLine + '\n' + modified.slice(lineStart);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
// ── 4. Add breadcrumb mapping ──
|
|
544
|
-
const breadcrumbLine = ` '${navKey}': [{ title: t('nav.${navKey}') }],`;
|
|
545
|
-
const bcMapMatch = modified.match(/const\s+breadcrumbMap[^=]*=\s*\{([\s\S]*?)\n\s*\};/);
|
|
546
|
-
const hasBreadcrumb = bcMapMatch ? bcMapMatch[1].includes(`'${navKey}':`) : modified.includes(`'${navKey}': [{ title:`);
|
|
547
|
-
if (!hasBreadcrumb) {
|
|
548
|
-
const breadcrumbCommentIdx = modified.indexOf('// Add breadcrumb mappings');
|
|
549
|
-
if (breadcrumbCommentIdx !== -1) {
|
|
550
|
-
const lineStart = modified.lastIndexOf('\n', breadcrumbCommentIdx) + 1;
|
|
551
|
-
modified = modified.slice(0, lineStart) + breadcrumbLine + '\n' + modified.slice(lineStart);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
// Also add system-settings breadcrumb if not present
|
|
555
|
-
if (!modified.includes("'system-settings':") && modified.includes("key: 'system-settings'")) {
|
|
556
|
-
const sysBcLine = ` 'system-settings': [{ title: t('nav.system-settings') }],`;
|
|
557
|
-
const breadcrumbCommentIdx = modified.indexOf('// Add breadcrumb mappings');
|
|
558
|
-
if (breadcrumbCommentIdx !== -1) {
|
|
559
|
-
const lineStart = modified.lastIndexOf('\n', breadcrumbCommentIdx) + 1;
|
|
560
|
-
modified = modified.slice(0, lineStart) + sysBcLine + '\n' + modified.slice(lineStart);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
// ── 5. Migration cleanups ──
|
|
564
|
-
if (modified.includes('<AppShell')) {
|
|
565
|
-
if (modified.includes('breadcrumbs={breadcrumbMap[selectedKey]}')) {
|
|
566
|
-
modified = modified.replace('breadcrumbs={breadcrumbMap[selectedKey]}', 'breadcrumbsMap={breadcrumbMap}');
|
|
567
|
-
}
|
|
568
|
-
else if (!modified.includes('breadcrumbsMap=')) {
|
|
569
|
-
modified = modified.replace('onNavClick={setSelectedKey}', 'onNavClick={setSelectedKey}\n breadcrumbsMap={breadcrumbMap}');
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
if (modified.includes('<AppShell') && !modified.includes('baseUrl=')) {
|
|
573
|
-
modified = modified.replace('<AppShell', '<AppShell\n baseUrl=""');
|
|
574
|
-
}
|
|
575
|
-
await fs.writeFile(appPath, modified, 'utf-8');
|
|
576
|
-
return true;
|
|
577
|
-
}
|
|
578
|
-
/**
|
|
579
|
-
* Wire App.tsx for Auth: inject AuthProvider + AuthGuard wrapping.
|
|
580
|
-
* Provider order: LoomConfigProvider > AuthProvider > AuthGuard > AppContent
|
|
581
|
-
*
|
|
582
|
-
* Returns true if wiring was performed, false if already wired or structure unexpected.
|
|
583
|
-
*/
|
|
584
|
-
export async function wireAppTsxForAuth(projectRoot) {
|
|
585
|
-
const appPath = path.join(projectRoot, 'frontend', 'src', 'App.tsx');
|
|
586
|
-
let content;
|
|
587
|
-
try {
|
|
588
|
-
content = await fs.readFile(appPath, 'utf-8');
|
|
589
|
-
}
|
|
590
|
-
catch {
|
|
591
|
-
return false;
|
|
592
|
-
}
|
|
593
|
-
// Check if auth is already wired
|
|
594
|
-
if (content.includes('AuthProvider') && content.includes('AuthGuard')) {
|
|
595
|
-
return false;
|
|
596
|
-
}
|
|
597
|
-
let modified = content;
|
|
598
|
-
// 1. Add AuthProvider + AuthGuard imports after last import
|
|
599
|
-
const authImport = `import { AuthProvider, AuthGuard } from '@loom-framework/frontend-antd';`;
|
|
600
|
-
const lastImportEnd = findLastImportEnd(modified);
|
|
601
|
-
if (lastImportEnd === null)
|
|
602
|
-
return false;
|
|
603
|
-
modified = modified.slice(0, lastImportEnd) + '\n' + authImport + modified.slice(lastImportEnd);
|
|
604
|
-
// 2. Wrap the main content return with AuthProvider > AuthGuard
|
|
605
|
-
// Look for <LoomConfigProvider> ... </LoomConfigProvider>
|
|
606
|
-
const loomProviderMatch = modified.match(/<LoomConfigProvider[^>]*>/);
|
|
607
|
-
if (loomProviderMatch) {
|
|
608
|
-
const providerStart = loomProviderMatch.index + loomProviderMatch[0].length;
|
|
609
|
-
const closingTag = '</LoomConfigProvider>';
|
|
610
|
-
const closingIdx = modified.lastIndexOf(closingTag);
|
|
611
|
-
if (closingIdx === -1)
|
|
612
|
-
return false;
|
|
613
|
-
modified = modified.slice(0, providerStart) +
|
|
614
|
-
'\n <AuthProvider>\n <AuthGuard>' +
|
|
615
|
-
modified.slice(providerStart, closingIdx) +
|
|
616
|
-
'</AuthGuard>\n </AuthProvider>\n ' +
|
|
617
|
-
modified.slice(closingIdx);
|
|
618
|
-
}
|
|
619
|
-
await fs.writeFile(appPath, modified, 'utf-8');
|
|
620
|
-
return true;
|
|
621
|
-
}
|
|
622
|
-
/**
|
|
623
|
-
* Wire App.tsx for Notifications: inject NotificationCenterProvider wrapping
|
|
624
|
-
* and add notification switch case + breadcrumb + i18n.
|
|
625
|
-
*
|
|
626
|
-
* Navigation is via the bell icon in AppShell header (no sidebar navItem needed).
|
|
627
|
-
*
|
|
628
|
-
* Provider order: LoomConfigProvider > AuthProvider > NotificationCenterProvider > AuthGuard > AppContent
|
|
629
|
-
*
|
|
630
|
-
* Returns true if wiring was performed, false if already wired or structure unexpected.
|
|
631
|
-
*/
|
|
632
|
-
export async function wireAppTsxForNotification(projectRoot) {
|
|
633
|
-
const appPath = path.join(projectRoot, 'frontend', 'src', 'App.tsx');
|
|
634
|
-
let content;
|
|
635
|
-
try {
|
|
636
|
-
content = await fs.readFile(appPath, 'utf-8');
|
|
637
|
-
}
|
|
638
|
-
catch {
|
|
639
|
-
return false;
|
|
640
|
-
}
|
|
641
|
-
// Check if notification is already wired
|
|
642
|
-
if (content.includes('NotificationCenterProvider')) {
|
|
643
|
-
return false;
|
|
644
|
-
}
|
|
645
|
-
let modified = content;
|
|
646
|
-
// 1. Add NotificationCenterProvider to existing @loom-framework/frontend-antd import
|
|
647
|
-
// (NotificationCenterPage and NotificationDetailPage are already added by wireAppTsxForNpmSystemSettings)
|
|
648
|
-
const existingNpmImport = modified.match(/import\s*\{([^}]+)\}\s*from\s*'@loom-framework\/frontend-antd';/);
|
|
649
|
-
if (existingNpmImport) {
|
|
650
|
-
const currentNames = existingNpmImport[1].split(',').map(s => s.trim()).filter(Boolean);
|
|
651
|
-
if (!currentNames.includes('NotificationCenterProvider')) {
|
|
652
|
-
const newNames = [...currentNames, 'NotificationCenterProvider'].join(', ');
|
|
653
|
-
const newImport = `import { ${newNames} } from '@loom-framework/frontend-antd';`;
|
|
654
|
-
modified = modified.replace(existingNpmImport[0], newImport);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
else {
|
|
658
|
-
// Fallback: no existing import, add a new one
|
|
659
|
-
const notifImport = `import { NotificationCenterProvider } from '@loom-framework/frontend-antd';`;
|
|
660
|
-
const lastImportEnd = findLastImportEnd(modified);
|
|
661
|
-
if (lastImportEnd === null)
|
|
662
|
-
return false;
|
|
663
|
-
modified = modified.slice(0, lastImportEnd) + '\n' + notifImport + modified.slice(lastImportEnd);
|
|
664
|
-
}
|
|
665
|
-
// 2. Wrap with NotificationCenterProvider (inside AuthProvider, outside AuthGuard)
|
|
666
|
-
if (modified.includes('<AuthProvider>') && modified.includes('<AuthGuard>')) {
|
|
667
|
-
modified = modified.replace('<AuthGuard>', '<NotificationCenterProvider>\n <AuthGuard>');
|
|
668
|
-
modified = modified.replace('</AuthGuard>', '</AuthGuard>\n </NotificationCenterProvider>');
|
|
669
|
-
}
|
|
670
|
-
else if (modified.includes('<LoomConfigProvider')) {
|
|
671
|
-
const loomProviderMatch2 = modified.match(/<LoomConfigProvider[^>]*>/);
|
|
672
|
-
if (loomProviderMatch2) {
|
|
673
|
-
const providerStart = loomProviderMatch2.index + loomProviderMatch2[0].length;
|
|
674
|
-
const closingTag = '</LoomConfigProvider>';
|
|
675
|
-
const closingIdx = modified.lastIndexOf(closingTag);
|
|
676
|
-
if (closingIdx === -1)
|
|
677
|
-
return false;
|
|
678
|
-
modified = modified.slice(0, providerStart) +
|
|
679
|
-
'\n <NotificationCenterProvider>' +
|
|
680
|
-
modified.slice(providerStart, closingIdx) +
|
|
681
|
-
'</NotificationCenterProvider>\n ' +
|
|
682
|
-
modified.slice(closingIdx);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
// 3. Ensure registerMessages is imported
|
|
686
|
-
if (!modified.includes('registerMessages')) {
|
|
687
|
-
modified = modified.replace(/\}\s*from\s*'@loom-framework\/frontend-antd';/, ", registerMessages } from '@loom-framework/frontend-antd';");
|
|
688
|
-
}
|
|
689
|
-
// 4. Register i18n keys for notifications breadcrumb
|
|
690
|
-
if (!modified.includes("'nav.notifications'")) {
|
|
691
|
-
const registerBlock = `registerMessages('zh-CN', { 'nav.notifications': '通知', 'notification.detail': '通知详情' });\nregisterMessages('en-US', { 'nav.notifications': 'Notifications', 'notification.detail': 'Notification Detail' });`;
|
|
692
|
-
const afterImportsIdx = findLastImportEnd(modified);
|
|
693
|
-
if (afterImportsIdx === null)
|
|
694
|
-
return false;
|
|
695
|
-
modified = modified.slice(0, afterImportsIdx) + '\n' + registerBlock + modified.slice(afterImportsIdx);
|
|
696
|
-
}
|
|
697
|
-
// 5. Add switch cases for notifications pages
|
|
698
|
-
const caseLine = ` case 'notifications': return <NotificationCenterPage />;`;
|
|
699
|
-
const detailCaseLine = ` case 'notification-detail': return <NotificationDetailPage />;`;
|
|
700
|
-
if (!modified.includes("case 'notifications':")) {
|
|
701
|
-
const defaultIdx = modified.indexOf('default:');
|
|
702
|
-
if (defaultIdx !== -1) {
|
|
703
|
-
const lineStart = modified.lastIndexOf('\n', defaultIdx) + 1;
|
|
704
|
-
modified = modified.slice(0, lineStart) + caseLine + '\n' + modified.slice(lineStart);
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
if (!modified.includes("case 'notification-detail':")) {
|
|
708
|
-
const notifCaseIdx = modified.indexOf("case 'notifications':");
|
|
709
|
-
if (notifCaseIdx !== -1) {
|
|
710
|
-
const lineStart = modified.lastIndexOf('\n', notifCaseIdx) + 1;
|
|
711
|
-
const afterCase = modified.indexOf('\n', notifCaseIdx) + 1;
|
|
712
|
-
modified = modified.slice(0, afterCase) + detailCaseLine + '\n' + modified.slice(afterCase);
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
// 6. Add breadcrumb mapping for notifications
|
|
716
|
-
const breadcrumbLine = ` 'notifications': [{ title: t('nav.notifications') }],`;
|
|
717
|
-
const detailBreadcrumbLine = ` 'notification-detail': [{ title: t('nav.notifications'), path: 'notifications' }, { title: t('notification.detail') }],`;
|
|
718
|
-
const bcMapMatch = modified.match(/const\s+breadcrumbMap[^=]*=\s*\{([\s\S]*?)\n\s*\};/);
|
|
719
|
-
const hasBreadcrumb = bcMapMatch ? bcMapMatch[1].includes("'notifications':") : modified.includes("'notifications': [{ title:");
|
|
720
|
-
const hasDetailBreadcrumb = bcMapMatch ? bcMapMatch[1].includes("'notification-detail':") : modified.includes("'notification-detail':");
|
|
721
|
-
const breadcrumbsToAdd = [];
|
|
722
|
-
if (!hasBreadcrumb)
|
|
723
|
-
breadcrumbsToAdd.push(breadcrumbLine);
|
|
724
|
-
if (!hasDetailBreadcrumb)
|
|
725
|
-
breadcrumbsToAdd.push(detailBreadcrumbLine);
|
|
726
|
-
if (breadcrumbsToAdd.length > 0) {
|
|
727
|
-
const allBreadcrumbLines = breadcrumbsToAdd.join('\n') + '\n';
|
|
728
|
-
const breadcrumbCommentIdx = modified.indexOf('// Add breadcrumb mappings');
|
|
729
|
-
if (breadcrumbCommentIdx !== -1) {
|
|
730
|
-
const lineStart = modified.lastIndexOf('\n', breadcrumbCommentIdx) + 1;
|
|
731
|
-
modified = modified.slice(0, lineStart) + allBreadcrumbLines + modified.slice(lineStart);
|
|
732
|
-
}
|
|
733
|
-
else {
|
|
734
|
-
const mapObjMatch = modified.match(/const\s+breadcrumbMap[^=]*=\s*\{/);
|
|
735
|
-
if (mapObjMatch) {
|
|
736
|
-
const mapStart = mapObjMatch.index + mapObjMatch[0].length;
|
|
737
|
-
modified = modified.slice(0, mapStart) + '\n' + allBreadcrumbLines + modified.slice(mapStart);
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
// 7. Add onNotificationViewAll + onNotificationClick props to AppShell
|
|
742
|
-
if (modified.includes('<AppShell') && !modified.includes('onNotificationViewAll')) {
|
|
743
|
-
modified = modified.replace('onNavClick={setSelectedKey}', "onNavClick={setSelectedKey}\n onNotificationViewAll={() => setSelectedKey('notifications')}\n onNotificationClick={(id) => setSelectedKey('notification-detail')}");
|
|
744
|
-
}
|
|
745
|
-
await fs.writeFile(appPath, modified, 'utf-8');
|
|
746
|
-
return true;
|
|
747
|
-
}
|
|
748
145
|
//# sourceMappingURL=app-tsx-wiring.js.map
|