@oamm/textor 1.0.12 → 1.0.13
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 +16 -0
- package/dist/bin/textor.js +497 -333
- package/dist/bin/textor.js.map +1 -1
- package/dist/index.cjs +477 -282
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +476 -283
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -6,6 +6,7 @@ var path = require('path');
|
|
|
6
6
|
var crypto = require('crypto');
|
|
7
7
|
var child_process = require('child_process');
|
|
8
8
|
var util = require('util');
|
|
9
|
+
var readline = require('readline');
|
|
9
10
|
|
|
10
11
|
const CONFIG_DIR$1 = '.textor';
|
|
11
12
|
const CONFIG_FILE = 'config.json';
|
|
@@ -497,6 +498,8 @@ function normalizeRoute(route) {
|
|
|
497
498
|
}
|
|
498
499
|
function routeToFilePath(route, options = {}) {
|
|
499
500
|
const { extension = '.astro', mode = 'flat', indexFile = 'index.astro' } = options;
|
|
501
|
+
if (!route)
|
|
502
|
+
return null;
|
|
500
503
|
const normalized = normalizeRoute(route);
|
|
501
504
|
if (normalized === '/') {
|
|
502
505
|
return indexFile;
|
|
@@ -715,6 +718,10 @@ async function safeMove(fromPath, toPath, options = {}) {
|
|
|
715
718
|
if (!fs.existsSync(fromPath)) {
|
|
716
719
|
throw new Error(`Source file not found: ${fromPath}`);
|
|
717
720
|
}
|
|
721
|
+
if (path.resolve(fromPath) === path.resolve(toPath)) {
|
|
722
|
+
const content = await promises.readFile(toPath, 'utf-8');
|
|
723
|
+
return calculateHash(content, normalization);
|
|
724
|
+
}
|
|
718
725
|
if (fs.existsSync(toPath) && !force) {
|
|
719
726
|
throw new Error(`Destination already exists: ${toPath}\n` +
|
|
720
727
|
`Use --force to overwrite.`);
|
|
@@ -2350,6 +2357,189 @@ async function updateImportsInFile$1(filePath, oldFilePath, newFilePath) {
|
|
|
2350
2357
|
await promises.writeFile(filePath, content, 'utf-8');
|
|
2351
2358
|
}
|
|
2352
2359
|
|
|
2360
|
+
/**
|
|
2361
|
+
* Updates relative imports in a file after it has been moved.
|
|
2362
|
+
*/
|
|
2363
|
+
async function updateImportsInFile(filePath, oldFilePath, newFilePath) {
|
|
2364
|
+
if (!fs.existsSync(filePath))
|
|
2365
|
+
return;
|
|
2366
|
+
let content = await promises.readFile(filePath, 'utf-8');
|
|
2367
|
+
const oldDir = path.dirname(oldFilePath);
|
|
2368
|
+
const newDir = path.dirname(newFilePath);
|
|
2369
|
+
if (oldDir === newDir)
|
|
2370
|
+
return;
|
|
2371
|
+
// Find all relative imports
|
|
2372
|
+
const relativeImportRegex = /from\s+['"](\.\.?\/[^'"]+)['"]/g;
|
|
2373
|
+
let match;
|
|
2374
|
+
const replacements = [];
|
|
2375
|
+
while ((match = relativeImportRegex.exec(content)) !== null) {
|
|
2376
|
+
const relativePath = match[1];
|
|
2377
|
+
const absoluteTarget = path.resolve(oldDir, relativePath);
|
|
2378
|
+
const newRelativePath = getRelativeImportPath(newFilePath, absoluteTarget);
|
|
2379
|
+
replacements.push({
|
|
2380
|
+
full: match[0],
|
|
2381
|
+
oldRel: relativePath,
|
|
2382
|
+
newRel: newRelativePath
|
|
2383
|
+
});
|
|
2384
|
+
}
|
|
2385
|
+
for (const repl of replacements) {
|
|
2386
|
+
content = content.replace(repl.full, `from '${repl.newRel}'`);
|
|
2387
|
+
}
|
|
2388
|
+
await promises.writeFile(filePath, content, 'utf-8');
|
|
2389
|
+
}
|
|
2390
|
+
/**
|
|
2391
|
+
* Moves a directory and its contents, renaming files and updating internal content/imports.
|
|
2392
|
+
*/
|
|
2393
|
+
async function moveDirectory(fromPath, toPath, state, config, options = {}) {
|
|
2394
|
+
const { fromName, toName, owner = null, signatures = [] } = options;
|
|
2395
|
+
if (!fs.existsSync(fromPath)) {
|
|
2396
|
+
throw new Error(`Source directory not found: ${fromPath}`);
|
|
2397
|
+
}
|
|
2398
|
+
if (fs.existsSync(toPath) && !options.force) {
|
|
2399
|
+
throw new Error(`Destination already exists: ${toPath}\n` +
|
|
2400
|
+
`Use --force to overwrite.`);
|
|
2401
|
+
}
|
|
2402
|
+
await ensureDir(toPath);
|
|
2403
|
+
const entries = await promises.readdir(fromPath);
|
|
2404
|
+
for (const entry of entries) {
|
|
2405
|
+
let targetEntry = entry;
|
|
2406
|
+
// Rename files if they match the component name
|
|
2407
|
+
if (fromName && toName && fromName !== toName) {
|
|
2408
|
+
if (entry.includes(fromName)) {
|
|
2409
|
+
targetEntry = entry.replace(fromName, toName);
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
const fromEntryPath = path.join(fromPath, entry);
|
|
2413
|
+
const toEntryPath = path.join(toPath, targetEntry);
|
|
2414
|
+
const stats = await promises.stat(fromEntryPath);
|
|
2415
|
+
if (stats.isDirectory()) {
|
|
2416
|
+
await moveDirectory(fromEntryPath, toEntryPath, state, config, options);
|
|
2417
|
+
}
|
|
2418
|
+
else {
|
|
2419
|
+
const normalizedFromRelative = path.relative(process.cwd(), fromEntryPath).replace(/\\/g, '/');
|
|
2420
|
+
const fileState = state.files[normalizedFromRelative];
|
|
2421
|
+
const newHash = await safeMove(fromEntryPath, toEntryPath, {
|
|
2422
|
+
force: options.force,
|
|
2423
|
+
expectedHash: fileState?.hash,
|
|
2424
|
+
acceptChanges: options.acceptChanges,
|
|
2425
|
+
normalization: config.hashing?.normalization,
|
|
2426
|
+
owner,
|
|
2427
|
+
actualOwner: fileState?.owner,
|
|
2428
|
+
signatures
|
|
2429
|
+
});
|
|
2430
|
+
// Update internal content (signatures, component names) if renaming
|
|
2431
|
+
if (fromName && toName && fromName !== toName) {
|
|
2432
|
+
let content = await promises.readFile(toEntryPath, 'utf-8');
|
|
2433
|
+
let hasChanged = false;
|
|
2434
|
+
// Simple replacement of component names
|
|
2435
|
+
if (content.includes(fromName)) {
|
|
2436
|
+
content = content.replace(new RegExp(fromName, 'g'), toName);
|
|
2437
|
+
hasChanged = true;
|
|
2438
|
+
}
|
|
2439
|
+
// Also handle lowercase class names if any
|
|
2440
|
+
const fromLower = fromName.toLowerCase();
|
|
2441
|
+
const toLower = toName.toLowerCase();
|
|
2442
|
+
if (content.includes(fromLower)) {
|
|
2443
|
+
content = content.replace(new RegExp(fromLower, 'g'), toLower);
|
|
2444
|
+
hasChanged = true;
|
|
2445
|
+
}
|
|
2446
|
+
if (hasChanged) {
|
|
2447
|
+
await promises.writeFile(toEntryPath, content, 'utf-8');
|
|
2448
|
+
// Re-calculate hash after content update
|
|
2449
|
+
const updatedHash = calculateHash(content, config.hashing?.normalization);
|
|
2450
|
+
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
2451
|
+
if (fileState) {
|
|
2452
|
+
state.files[normalizedToRelative] = { ...fileState, hash: updatedHash };
|
|
2453
|
+
delete state.files[normalizedFromRelative];
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
else {
|
|
2457
|
+
// Update state for each file moved normally
|
|
2458
|
+
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
2459
|
+
if (fileState) {
|
|
2460
|
+
state.files[normalizedToRelative] = { ...fileState, hash: newHash };
|
|
2461
|
+
delete state.files[normalizedFromRelative];
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
else {
|
|
2466
|
+
// Update state for each file moved normally
|
|
2467
|
+
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
2468
|
+
if (fileState) {
|
|
2469
|
+
state.files[normalizedToRelative] = { ...fileState, hash: newHash };
|
|
2470
|
+
delete state.files[normalizedFromRelative];
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
const remainingFiles = await promises.readdir(fromPath);
|
|
2476
|
+
if (remainingFiles.length === 0) {
|
|
2477
|
+
await promises.rmdir(fromPath);
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Scans the project and replaces imports of a moved/renamed item.
|
|
2482
|
+
*/
|
|
2483
|
+
async function scanAndReplaceImports(config, state, fromInfo, toInfo, options) {
|
|
2484
|
+
const { fromPath: fromItemPath, fromName, type } = fromInfo;
|
|
2485
|
+
const { toPath: toItemPath, toName } = toInfo;
|
|
2486
|
+
const allFiles = new Set();
|
|
2487
|
+
await scanDirectory(process.cwd(), allFiles);
|
|
2488
|
+
const rootPath = resolvePath(config, type === 'component' ? 'components' : 'features');
|
|
2489
|
+
for (const relPath of allFiles) {
|
|
2490
|
+
const fullPath = path.resolve(process.cwd(), relPath);
|
|
2491
|
+
// Skip the moved directory itself
|
|
2492
|
+
const toFullPath = path.resolve(toItemPath);
|
|
2493
|
+
if (fullPath.startsWith(toFullPath))
|
|
2494
|
+
continue;
|
|
2495
|
+
let content = await promises.readFile(fullPath, 'utf-8');
|
|
2496
|
+
let changed = false;
|
|
2497
|
+
const aliasBase = config.importAliases[type === 'component' ? 'components' : 'features'];
|
|
2498
|
+
const ext = type === 'component' ? '' : (config.naming.featureExtension === '.astro' ? '.astro' : '');
|
|
2499
|
+
if (aliasBase) {
|
|
2500
|
+
const oldAlias = `${aliasBase}/${fromItemPath}`;
|
|
2501
|
+
const newAlias = `${aliasBase}/${toItemPath}`;
|
|
2502
|
+
const oldFullImport = `from '${oldAlias}/${fromName}${ext}'`;
|
|
2503
|
+
const newFullImport = `from '${newAlias}/${toName}${ext}'`;
|
|
2504
|
+
if (content.includes(oldFullImport)) {
|
|
2505
|
+
content = content.replace(new RegExp(oldFullImport.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newFullImport);
|
|
2506
|
+
changed = true;
|
|
2507
|
+
}
|
|
2508
|
+
else if (content.includes(oldAlias)) {
|
|
2509
|
+
content = content.replace(new RegExp(oldAlias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newAlias);
|
|
2510
|
+
changed = true;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
else {
|
|
2514
|
+
const oldDir = path.resolve(rootPath, fromItemPath);
|
|
2515
|
+
const newDir = path.resolve(rootPath, toItemPath);
|
|
2516
|
+
const oldRelPath = getRelativeImportPath(fullPath, oldDir);
|
|
2517
|
+
const newRelPath = getRelativeImportPath(fullPath, newDir);
|
|
2518
|
+
const oldImport = `'${oldRelPath}/${fromName}${ext}'`;
|
|
2519
|
+
const newImport = `'${newRelPath}/${toName}${ext}'`;
|
|
2520
|
+
if (content.includes(oldImport)) {
|
|
2521
|
+
content = content.replace(new RegExp(oldImport.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newImport);
|
|
2522
|
+
changed = true;
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
if (fromName !== toName && changed) {
|
|
2526
|
+
content = content.replace(new RegExp(`\\b${fromName}\\b`, 'g'), toName);
|
|
2527
|
+
}
|
|
2528
|
+
if (changed) {
|
|
2529
|
+
if (options.dryRun) {
|
|
2530
|
+
console.log(` [Scan] Would update imports in ${relPath}`);
|
|
2531
|
+
}
|
|
2532
|
+
else {
|
|
2533
|
+
await promises.writeFile(fullPath, content, 'utf-8');
|
|
2534
|
+
console.log(` [Scan] Updated imports in ${relPath}`);
|
|
2535
|
+
if (state.files[relPath]) {
|
|
2536
|
+
state.files[relPath].hash = calculateHash(content, config.hashing?.normalization);
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2353
2543
|
/**
|
|
2354
2544
|
* Move a section (route + feature).
|
|
2355
2545
|
*
|
|
@@ -2374,40 +2564,53 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2374
2564
|
let actualFromFeature = fromFeature;
|
|
2375
2565
|
let actualToRoute = toRoute;
|
|
2376
2566
|
let actualToFeature = toFeature;
|
|
2377
|
-
// Shift arguments if using state
|
|
2567
|
+
// Shift arguments if using state or if called with fewer arguments
|
|
2378
2568
|
if (!toRoute && fromRoute && fromFeature) {
|
|
2379
2569
|
// textor move-section /old-route /new-route
|
|
2380
|
-
|
|
2570
|
+
actualFromRoute = fromRoute;
|
|
2571
|
+
actualToRoute = fromFeature;
|
|
2572
|
+
actualFromFeature = undefined;
|
|
2573
|
+
actualToFeature = undefined;
|
|
2574
|
+
}
|
|
2575
|
+
// Lookup missing info from state
|
|
2576
|
+
if (actualFromRoute && !actualFromFeature) {
|
|
2577
|
+
const section = findSection(state, actualFromRoute);
|
|
2381
2578
|
if (section) {
|
|
2382
|
-
actualFromRoute = section.route;
|
|
2383
2579
|
actualFromFeature = section.featurePath;
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
}
|
|
2405
|
-
else {
|
|
2406
|
-
// Otherwise just keep it the same
|
|
2407
|
-
actualToFeature = actualFromFeature;
|
|
2408
|
-
}
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
else if (!actualFromRoute && actualFromFeature) {
|
|
2583
|
+
const section = findSection(state, actualFromFeature);
|
|
2584
|
+
if (section) {
|
|
2585
|
+
actualFromRoute = section.route;
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
// If toFeature is not provided, try to derive it from the new route if route moved
|
|
2589
|
+
if (!actualToFeature && actualToRoute && actualFromRoute && actualFromRoute !== actualToRoute && actualFromFeature) {
|
|
2590
|
+
const oldRouteParts = actualFromRoute.split('/').filter(Boolean);
|
|
2591
|
+
const newRouteParts = actualToRoute.split('/').filter(Boolean);
|
|
2592
|
+
const oldFeatureParts = actualFromFeature.split('/').filter(Boolean);
|
|
2593
|
+
let match = true;
|
|
2594
|
+
for (let i = 0; i < oldRouteParts.length; i++) {
|
|
2595
|
+
const routePart = oldRouteParts[i].toLowerCase();
|
|
2596
|
+
const featurePart = oldFeatureParts[i] ? oldFeatureParts[i].toLowerCase() : null;
|
|
2597
|
+
if (featurePart !== routePart) {
|
|
2598
|
+
match = false;
|
|
2599
|
+
break;
|
|
2409
2600
|
}
|
|
2410
2601
|
}
|
|
2602
|
+
if (match && oldRouteParts.length > 0) {
|
|
2603
|
+
actualToFeature = [...newRouteParts, ...oldFeatureParts.slice(oldRouteParts.length)].join('/');
|
|
2604
|
+
}
|
|
2605
|
+
else {
|
|
2606
|
+
actualToFeature = actualFromFeature;
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
else if (!actualToFeature) {
|
|
2610
|
+
actualToFeature = actualFromFeature;
|
|
2611
|
+
}
|
|
2612
|
+
if (!actualToRoute) {
|
|
2613
|
+
actualToRoute = actualFromRoute;
|
|
2411
2614
|
}
|
|
2412
2615
|
const isRouteOnly = options.keepFeature || (!actualToFeature && actualToRoute && !actualFromFeature);
|
|
2413
2616
|
if (isRouteOnly && !actualToRoute) {
|
|
@@ -2432,12 +2635,14 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2432
2635
|
mode: config.routing.mode,
|
|
2433
2636
|
indexFile: config.routing.indexFile
|
|
2434
2637
|
});
|
|
2435
|
-
const fromRoutePath = secureJoin(pagesRoot, fromRouteFile);
|
|
2436
|
-
const toRoutePath = secureJoin(pagesRoot, toRouteFile);
|
|
2638
|
+
const fromRoutePath = fromRouteFile ? secureJoin(pagesRoot, fromRouteFile) : null;
|
|
2639
|
+
const toRoutePath = toRouteFile ? secureJoin(pagesRoot, toRouteFile) : null;
|
|
2437
2640
|
const movedFiles = [];
|
|
2438
2641
|
if (options.dryRun) {
|
|
2439
2642
|
console.log('Dry run - would move:');
|
|
2440
|
-
|
|
2643
|
+
if (fromRoutePath && toRoutePath && fromRoutePath !== toRoutePath) {
|
|
2644
|
+
console.log(` Route: ${fromRoutePath} -> ${toRoutePath}`);
|
|
2645
|
+
}
|
|
2441
2646
|
if (!isRouteOnly && normalizedFromFeature && normalizedToFeature) {
|
|
2442
2647
|
const fromFeaturePath = secureJoin(featuresRoot, normalizedFromFeature);
|
|
2443
2648
|
const toFeaturePath = secureJoin(featuresRoot, normalizedToFeature);
|
|
@@ -2445,81 +2650,90 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2445
2650
|
}
|
|
2446
2651
|
return;
|
|
2447
2652
|
}
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
const fromFeatureComponentName = getFeatureComponentName(normalizedFromFeature);
|
|
2471
|
-
const toFeatureComponentName = getFeatureComponentName(targetFeature);
|
|
2472
|
-
// First, update all relative imports in the file because it moved
|
|
2473
|
-
await updateImportsInFile(toRoutePath, fromRoutePath, toRoutePath);
|
|
2474
|
-
let content = await promises.readFile(toRoutePath, 'utf-8');
|
|
2475
|
-
let changed = false;
|
|
2476
|
-
// Update component name in JSX tags
|
|
2477
|
-
if (fromFeatureComponentName !== toFeatureComponentName) {
|
|
2478
|
-
content = content.replace(new RegExp(`<${fromFeatureComponentName}`, 'g'), `<${toFeatureComponentName}`);
|
|
2479
|
-
content = content.replace(new RegExp(`</${fromFeatureComponentName}`, 'g'), `</${toFeatureComponentName}`);
|
|
2480
|
-
changed = true;
|
|
2653
|
+
let normalizedToRouteRelative = null;
|
|
2654
|
+
if (fromRoutePath && toRoutePath) {
|
|
2655
|
+
const normalizedFromRouteRelative = path.relative(process.cwd(), fromRoutePath).replace(/\\/g, '/');
|
|
2656
|
+
const routeFileState = state.files[normalizedFromRouteRelative];
|
|
2657
|
+
const newRouteHash = await safeMove(fromRoutePath, toRoutePath, {
|
|
2658
|
+
force: options.force,
|
|
2659
|
+
expectedHash: routeFileState?.hash,
|
|
2660
|
+
acceptChanges: options.acceptChanges,
|
|
2661
|
+
owner: normalizedFromRoute,
|
|
2662
|
+
actualOwner: routeFileState?.owner,
|
|
2663
|
+
signatures: configSignatures
|
|
2664
|
+
});
|
|
2665
|
+
if (fromRoutePath !== toRoutePath) {
|
|
2666
|
+
movedFiles.push({ from: fromRoutePath, to: toRoutePath });
|
|
2667
|
+
}
|
|
2668
|
+
// Update state for moved route file
|
|
2669
|
+
normalizedToRouteRelative = path.relative(process.cwd(), toRoutePath).replace(/\\/g, '/');
|
|
2670
|
+
if (routeFileState) {
|
|
2671
|
+
state.files[normalizedToRouteRelative] = { ...routeFileState, hash: newRouteHash };
|
|
2672
|
+
if (fromRoutePath !== toRoutePath) {
|
|
2673
|
+
delete state.files[normalizedFromRouteRelative];
|
|
2674
|
+
}
|
|
2481
2675
|
}
|
|
2482
|
-
if
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
const
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2676
|
+
// Update imports in the route file (even if it didn't move, as feature might have)
|
|
2677
|
+
const targetFeature = normalizedToFeature || normalizedFromFeature;
|
|
2678
|
+
if (targetFeature && fs.existsSync(toRoutePath)) {
|
|
2679
|
+
const fromFeatureDirPath = secureJoin(featuresRoot, normalizedFromFeature);
|
|
2680
|
+
const toFeatureDirPath = secureJoin(featuresRoot, targetFeature);
|
|
2681
|
+
const fromFeatureComponentName = getFeatureComponentName(normalizedFromFeature);
|
|
2682
|
+
const toFeatureComponentName = getFeatureComponentName(targetFeature);
|
|
2683
|
+
// First, update all relative imports in the file because it moved (or stayed)
|
|
2684
|
+
await updateImportsInFile(toRoutePath, fromRoutePath, toRoutePath);
|
|
2685
|
+
let content = await promises.readFile(toRoutePath, 'utf-8');
|
|
2686
|
+
let changed = false;
|
|
2687
|
+
// Update component name in JSX tags
|
|
2688
|
+
if (fromFeatureComponentName !== toFeatureComponentName) {
|
|
2689
|
+
content = content.replace(new RegExp(`<${fromFeatureComponentName}`, 'g'), `<${toFeatureComponentName}`);
|
|
2690
|
+
content = content.replace(new RegExp(`</${fromFeatureComponentName}`, 'g'), `</${toFeatureComponentName}`);
|
|
2495
2691
|
changed = true;
|
|
2496
2692
|
}
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2693
|
+
if (config.importAliases.features) {
|
|
2694
|
+
const oldAliasPath = `${config.importAliases.features}/${normalizedFromFeature}`;
|
|
2695
|
+
const newAliasPath = `${config.importAliases.features}/${targetFeature}`;
|
|
2696
|
+
// Flexible regex to match import identifier and path with alias
|
|
2697
|
+
const importRegex = new RegExp(`(import\\s+)(${fromFeatureComponentName})(\\s+from\\s+['"])${oldAliasPath}(/[^'"]+)?(['"])`, 'g');
|
|
2698
|
+
if (importRegex.test(content)) {
|
|
2699
|
+
content = content.replace(importRegex, (match, p1, p2, p3, subPath, p5) => {
|
|
2700
|
+
let newSubPath = subPath || '';
|
|
2701
|
+
if (subPath && subPath.includes(fromFeatureComponentName)) {
|
|
2702
|
+
newSubPath = subPath.replace(fromFeatureComponentName, toFeatureComponentName);
|
|
2703
|
+
}
|
|
2704
|
+
return `${p1}${toFeatureComponentName}${p3}${newAliasPath}${newSubPath}${p5}`;
|
|
2705
|
+
});
|
|
2706
|
+
changed = true;
|
|
2707
|
+
}
|
|
2708
|
+
else if (content.includes(oldAliasPath)) {
|
|
2709
|
+
// Fallback for path only replacement
|
|
2710
|
+
content = content.replace(new RegExp(oldAliasPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newAliasPath);
|
|
2711
|
+
changed = true;
|
|
2712
|
+
}
|
|
2501
2713
|
}
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2714
|
+
else {
|
|
2715
|
+
const oldRelativeDir = getRelativeImportPath(toRoutePath, fromFeatureDirPath);
|
|
2716
|
+
const newRelativeDir = getRelativeImportPath(toRoutePath, toFeatureDirPath);
|
|
2717
|
+
// Flexible regex for relative imports
|
|
2718
|
+
const relImportRegex = new RegExp(`(import\\s+)(${fromFeatureComponentName})(\\s+from\\s+['"])${oldRelativeDir}(/[^'"]+)?(['"])`, 'g');
|
|
2719
|
+
if (relImportRegex.test(content)) {
|
|
2720
|
+
content = content.replace(relImportRegex, (match, p1, p2, p3, subPath, p5) => {
|
|
2721
|
+
let newSubPath = subPath || '';
|
|
2722
|
+
if (subPath && subPath.includes(fromFeatureComponentName)) {
|
|
2723
|
+
newSubPath = subPath.replace(fromFeatureComponentName, toFeatureComponentName);
|
|
2724
|
+
}
|
|
2725
|
+
return `${p1}${toFeatureComponentName}${p3}${newRelativeDir}${newSubPath}${p5}`;
|
|
2726
|
+
});
|
|
2727
|
+
changed = true;
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
if (changed) {
|
|
2731
|
+
await promises.writeFile(toRoutePath, content, 'utf-8');
|
|
2732
|
+
// Update hash in state after changes
|
|
2733
|
+
if (state.files[normalizedToRouteRelative]) {
|
|
2734
|
+
state.files[normalizedToRouteRelative].hash = calculateHash(content, config.hashing?.normalization);
|
|
2735
|
+
}
|
|
2517
2736
|
}
|
|
2518
|
-
}
|
|
2519
|
-
if (changed) {
|
|
2520
|
-
await promises.writeFile(toRoutePath, content, 'utf-8');
|
|
2521
|
-
// Update hash in state after changes
|
|
2522
|
-
state.files[normalizedToRouteRelative].hash = calculateHash(content, config.hashing?.normalization);
|
|
2523
2737
|
}
|
|
2524
2738
|
}
|
|
2525
2739
|
if (!isRouteOnly && normalizedFromFeature && normalizedToFeature && normalizedFromFeature !== normalizedToFeature) {
|
|
@@ -2541,14 +2755,17 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2541
2755
|
}
|
|
2542
2756
|
if (options.scan && (normalizedFromFeature || normalizedToFeature)) {
|
|
2543
2757
|
await scanAndReplaceImports(config, state, {
|
|
2544
|
-
|
|
2545
|
-
|
|
2758
|
+
fromPath: normalizedFromFeature,
|
|
2759
|
+
fromName: getFeatureComponentName(normalizedFromFeature),
|
|
2760
|
+
type: 'feature'
|
|
2546
2761
|
}, {
|
|
2547
|
-
|
|
2548
|
-
|
|
2762
|
+
toPath: normalizedToFeature || normalizedFromFeature,
|
|
2763
|
+
toName: getFeatureComponentName(normalizedToFeature || normalizedFromFeature)
|
|
2549
2764
|
}, options);
|
|
2550
2765
|
}
|
|
2551
|
-
|
|
2766
|
+
if (fromRoutePath && toRoutePath && fromRoutePath !== toRoutePath) {
|
|
2767
|
+
await cleanupEmptyDirs(path.dirname(fromRoutePath), pagesRoot);
|
|
2768
|
+
}
|
|
2552
2769
|
console.log('✓ Moved:');
|
|
2553
2770
|
movedFiles.forEach(item => {
|
|
2554
2771
|
console.log(` ${item.from}`);
|
|
@@ -2556,6 +2773,14 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2556
2773
|
});
|
|
2557
2774
|
if (movedFiles.length > 0) {
|
|
2558
2775
|
const existingSection = fromSection;
|
|
2776
|
+
// Update ownership in state if route moved
|
|
2777
|
+
if (normalizedFromRoute && normalizedToRoute && normalizedFromRoute !== normalizedToRoute) {
|
|
2778
|
+
for (const f in state.files) {
|
|
2779
|
+
if (state.files[f].owner === normalizedFromRoute) {
|
|
2780
|
+
state.files[f].owner = normalizedToRoute;
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2559
2784
|
// Update section data in state
|
|
2560
2785
|
state.sections = state.sections.filter(s => s.route !== normalizedFromRoute);
|
|
2561
2786
|
state.sections.push({
|
|
@@ -2576,182 +2801,6 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2576
2801
|
throw error;
|
|
2577
2802
|
}
|
|
2578
2803
|
}
|
|
2579
|
-
async function scanAndReplaceImports(config, state, fromInfo, toInfo, options) {
|
|
2580
|
-
const { fromFeaturePath, fromComponentName } = fromInfo;
|
|
2581
|
-
const { toFeaturePath, toComponentName } = toInfo;
|
|
2582
|
-
const allFiles = new Set();
|
|
2583
|
-
await scanDirectory(process.cwd(), allFiles);
|
|
2584
|
-
const featuresRoot = resolvePath(config, 'features');
|
|
2585
|
-
for (const relPath of allFiles) {
|
|
2586
|
-
const fullPath = path.join(process.cwd(), relPath);
|
|
2587
|
-
// Skip the moved directory itself as it was already handled
|
|
2588
|
-
if (fullPath.startsWith(path.resolve(toFeaturePath)))
|
|
2589
|
-
continue;
|
|
2590
|
-
let content = await promises.readFile(fullPath, 'utf-8');
|
|
2591
|
-
let changed = false;
|
|
2592
|
-
const ext = config.naming.featureExtension === '.astro' ? '.astro' : '';
|
|
2593
|
-
// Handle Aliases
|
|
2594
|
-
if (config.importAliases.features) {
|
|
2595
|
-
const oldAlias = `${config.importAliases.features}/${fromFeaturePath}`;
|
|
2596
|
-
const newAlias = `${config.importAliases.features}/${toFeaturePath}`;
|
|
2597
|
-
// Update component name and path if both changed
|
|
2598
|
-
const oldFullImport = `from '${oldAlias}/${fromComponentName}${ext}'`;
|
|
2599
|
-
const newFullImport = `from '${newAlias}/${toComponentName}${ext}'`;
|
|
2600
|
-
if (content.includes(oldFullImport)) {
|
|
2601
|
-
content = content.replace(new RegExp(oldFullImport.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newFullImport);
|
|
2602
|
-
changed = true;
|
|
2603
|
-
}
|
|
2604
|
-
else if (content.includes(oldAlias)) {
|
|
2605
|
-
content = content.replace(new RegExp(oldAlias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newAlias);
|
|
2606
|
-
changed = true;
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
else {
|
|
2610
|
-
// Handle Relative Imports (more complex)
|
|
2611
|
-
// This is best-effort: we look for imports that resolve to the old feature path
|
|
2612
|
-
const fromFeatureDir = secureJoin(featuresRoot, fromFeaturePath);
|
|
2613
|
-
const toFeatureDir = secureJoin(featuresRoot, toFeaturePath);
|
|
2614
|
-
const oldRelPath = getRelativeImportPath(fullPath, fromFeatureDir);
|
|
2615
|
-
const newRelPath = getRelativeImportPath(fullPath, toFeatureDir);
|
|
2616
|
-
const oldImport = `'${oldRelPath}/${fromComponentName}${ext}'`;
|
|
2617
|
-
const newImport = `'${newRelPath}/${toComponentName}${ext}'`;
|
|
2618
|
-
if (content.includes(oldImport)) {
|
|
2619
|
-
content = content.replace(new RegExp(oldImport.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newImport);
|
|
2620
|
-
changed = true;
|
|
2621
|
-
}
|
|
2622
|
-
}
|
|
2623
|
-
// Update component name in JSX and imports if it changed
|
|
2624
|
-
if (fromComponentName !== toComponentName && changed) {
|
|
2625
|
-
content = content.replace(new RegExp(`\\b${fromComponentName}\\b`, 'g'), toComponentName);
|
|
2626
|
-
}
|
|
2627
|
-
if (changed) {
|
|
2628
|
-
if (options.dryRun) {
|
|
2629
|
-
console.log(` [Scan] Would update imports in ${relPath}`);
|
|
2630
|
-
}
|
|
2631
|
-
else {
|
|
2632
|
-
await promises.writeFile(fullPath, content, 'utf-8');
|
|
2633
|
-
console.log(` [Scan] Updated imports in ${relPath}`);
|
|
2634
|
-
// Update state hash if this file is managed
|
|
2635
|
-
if (state.files[relPath]) {
|
|
2636
|
-
state.files[relPath].hash = calculateHash(content, config.hashing?.normalization);
|
|
2637
|
-
}
|
|
2638
|
-
}
|
|
2639
|
-
}
|
|
2640
|
-
}
|
|
2641
|
-
}
|
|
2642
|
-
async function moveDirectory(fromPath, toPath, state, config, options = {}) {
|
|
2643
|
-
const { fromName, toName, owner = null } = options;
|
|
2644
|
-
if (!fs.existsSync(fromPath)) {
|
|
2645
|
-
throw new Error(`Source directory not found: ${fromPath}`);
|
|
2646
|
-
}
|
|
2647
|
-
if (fs.existsSync(toPath) && !options.force) {
|
|
2648
|
-
throw new Error(`Destination already exists: ${toPath}\n` +
|
|
2649
|
-
`Use --force to overwrite.`);
|
|
2650
|
-
}
|
|
2651
|
-
await ensureDir(toPath);
|
|
2652
|
-
const entries = await promises.readdir(fromPath);
|
|
2653
|
-
for (const entry of entries) {
|
|
2654
|
-
let targetEntry = entry;
|
|
2655
|
-
// Rename files if they match the component name
|
|
2656
|
-
if (fromName && toName && fromName !== toName) {
|
|
2657
|
-
if (entry.includes(fromName)) {
|
|
2658
|
-
targetEntry = entry.replace(fromName, toName);
|
|
2659
|
-
}
|
|
2660
|
-
}
|
|
2661
|
-
const fromEntryPath = path.join(fromPath, entry);
|
|
2662
|
-
const toEntryPath = path.join(toPath, targetEntry);
|
|
2663
|
-
const stats = await promises.stat(fromEntryPath);
|
|
2664
|
-
if (stats.isDirectory()) {
|
|
2665
|
-
await moveDirectory(fromEntryPath, toEntryPath, state, config, options);
|
|
2666
|
-
}
|
|
2667
|
-
else {
|
|
2668
|
-
const normalizedFromRelative = path.relative(process.cwd(), fromEntryPath).replace(/\\/g, '/');
|
|
2669
|
-
const fileState = state.files[normalizedFromRelative];
|
|
2670
|
-
const newHash = await safeMove(fromEntryPath, toEntryPath, {
|
|
2671
|
-
force: options.force,
|
|
2672
|
-
expectedHash: fileState?.hash,
|
|
2673
|
-
acceptChanges: options.acceptChanges,
|
|
2674
|
-
normalization: config.hashing?.normalization,
|
|
2675
|
-
owner,
|
|
2676
|
-
actualOwner: fileState?.owner
|
|
2677
|
-
});
|
|
2678
|
-
// Update internal content (signatures, component names) if renaming
|
|
2679
|
-
if (fromName && toName && fromName !== toName) {
|
|
2680
|
-
let content = await promises.readFile(toEntryPath, 'utf-8');
|
|
2681
|
-
let hasChanged = false;
|
|
2682
|
-
// Simple replacement of component names
|
|
2683
|
-
if (content.includes(fromName)) {
|
|
2684
|
-
content = content.replace(new RegExp(fromName, 'g'), toName);
|
|
2685
|
-
hasChanged = true;
|
|
2686
|
-
}
|
|
2687
|
-
// Also handle lowercase class names if any
|
|
2688
|
-
const fromLower = fromName.toLowerCase();
|
|
2689
|
-
const toLower = toName.toLowerCase();
|
|
2690
|
-
if (content.includes(fromLower)) {
|
|
2691
|
-
content = content.replace(new RegExp(fromLower, 'g'), toLower);
|
|
2692
|
-
hasChanged = true;
|
|
2693
|
-
}
|
|
2694
|
-
if (hasChanged) {
|
|
2695
|
-
await promises.writeFile(toEntryPath, content, 'utf-8');
|
|
2696
|
-
// Re-calculate hash after content update
|
|
2697
|
-
const updatedHash = calculateHash(content, config.hashing?.normalization);
|
|
2698
|
-
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
2699
|
-
if (fileState) {
|
|
2700
|
-
state.files[normalizedToRelative] = { ...fileState, hash: updatedHash };
|
|
2701
|
-
delete state.files[normalizedFromRelative];
|
|
2702
|
-
}
|
|
2703
|
-
}
|
|
2704
|
-
else {
|
|
2705
|
-
// Update state for each file moved normally
|
|
2706
|
-
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
2707
|
-
if (fileState) {
|
|
2708
|
-
state.files[normalizedToRelative] = { ...fileState, hash: newHash };
|
|
2709
|
-
delete state.files[normalizedFromRelative];
|
|
2710
|
-
}
|
|
2711
|
-
}
|
|
2712
|
-
}
|
|
2713
|
-
else {
|
|
2714
|
-
// Update state for each file moved normally
|
|
2715
|
-
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
2716
|
-
if (fileState) {
|
|
2717
|
-
state.files[normalizedToRelative] = { ...fileState, hash: newHash };
|
|
2718
|
-
delete state.files[normalizedFromRelative];
|
|
2719
|
-
}
|
|
2720
|
-
}
|
|
2721
|
-
}
|
|
2722
|
-
}
|
|
2723
|
-
const remainingFiles = await promises.readdir(fromPath);
|
|
2724
|
-
if (remainingFiles.length === 0) {
|
|
2725
|
-
await promises.rmdir(fromPath);
|
|
2726
|
-
}
|
|
2727
|
-
}
|
|
2728
|
-
async function updateImportsInFile(filePath, oldFilePath, newFilePath) {
|
|
2729
|
-
if (!fs.existsSync(filePath))
|
|
2730
|
-
return;
|
|
2731
|
-
let content = await promises.readFile(filePath, 'utf-8');
|
|
2732
|
-
const oldDir = path.dirname(oldFilePath);
|
|
2733
|
-
const newDir = path.dirname(newFilePath);
|
|
2734
|
-
if (oldDir === newDir)
|
|
2735
|
-
return;
|
|
2736
|
-
// Find all relative imports
|
|
2737
|
-
const relativeImportRegex = /from\s+['"](\.\.?\/[^'"]+)['"]/g;
|
|
2738
|
-
let match;
|
|
2739
|
-
const replacements = [];
|
|
2740
|
-
while ((match = relativeImportRegex.exec(content)) !== null) {
|
|
2741
|
-
const relativePath = match[1];
|
|
2742
|
-
const absoluteTarget = path.resolve(oldDir, relativePath);
|
|
2743
|
-
const newRelativePath = getRelativeImportPath(newFilePath, absoluteTarget);
|
|
2744
|
-
replacements.push({
|
|
2745
|
-
full: match[0],
|
|
2746
|
-
oldRel: relativePath,
|
|
2747
|
-
newRel: newRelativePath
|
|
2748
|
-
});
|
|
2749
|
-
}
|
|
2750
|
-
for (const repl of replacements) {
|
|
2751
|
-
content = content.replace(repl.full, `from '${repl.newRel}'`);
|
|
2752
|
-
}
|
|
2753
|
-
await promises.writeFile(filePath, content, 'utf-8');
|
|
2754
|
-
}
|
|
2755
2804
|
|
|
2756
2805
|
async function createComponentCommand(componentName, options) {
|
|
2757
2806
|
try {
|
|
@@ -3794,6 +3843,150 @@ async function normalizeStateCommand(options) {
|
|
|
3794
3843
|
}
|
|
3795
3844
|
}
|
|
3796
3845
|
|
|
3846
|
+
/**
|
|
3847
|
+
* Removes missing references from Textor state.
|
|
3848
|
+
* @param {Object} options
|
|
3849
|
+
* @param {boolean} options.dryRun
|
|
3850
|
+
* @param {boolean} options.yes
|
|
3851
|
+
*/
|
|
3852
|
+
async function pruneMissingCommand(options = {}) {
|
|
3853
|
+
try {
|
|
3854
|
+
const config = await loadConfig();
|
|
3855
|
+
const state = await loadState();
|
|
3856
|
+
const results = await getProjectStatus(config, state);
|
|
3857
|
+
if (results.missing.length === 0) {
|
|
3858
|
+
console.log('No missing references found.');
|
|
3859
|
+
return;
|
|
3860
|
+
}
|
|
3861
|
+
console.log(`Found ${results.missing.length} missing references:`);
|
|
3862
|
+
results.missing.forEach(f => console.log(` - ${f}`));
|
|
3863
|
+
if (options.dryRun) {
|
|
3864
|
+
console.log('\nDry run: no changes applied to state.');
|
|
3865
|
+
return;
|
|
3866
|
+
}
|
|
3867
|
+
if (!options.yes && options.interactive !== false && process.stdin.isTTY && process.env.NODE_ENV !== 'test') {
|
|
3868
|
+
const rl = readline.createInterface({
|
|
3869
|
+
input: process.stdin,
|
|
3870
|
+
output: process.stdout
|
|
3871
|
+
});
|
|
3872
|
+
const confirmed = await new Promise(resolve => {
|
|
3873
|
+
rl.question('\nDo you want to proceed with pruning? (y/N) ', (answer) => {
|
|
3874
|
+
rl.close();
|
|
3875
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
3876
|
+
});
|
|
3877
|
+
});
|
|
3878
|
+
if (!confirmed) {
|
|
3879
|
+
console.log('Aborted.');
|
|
3880
|
+
return;
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
for (const relPath of results.missing) {
|
|
3884
|
+
delete state.files[relPath];
|
|
3885
|
+
}
|
|
3886
|
+
// Reconstruct metadata
|
|
3887
|
+
state.components = reconstructComponents(state.files, config);
|
|
3888
|
+
state.sections = reconstructSections(state, config);
|
|
3889
|
+
await saveState(state);
|
|
3890
|
+
console.log(`\n✓ Successfully removed ${results.missing.length} missing references from state.`);
|
|
3891
|
+
}
|
|
3892
|
+
catch (error) {
|
|
3893
|
+
console.error('Error:', error.message);
|
|
3894
|
+
if (typeof process.exit === 'function' && process.env.NODE_ENV !== 'test') {
|
|
3895
|
+
process.exit(1);
|
|
3896
|
+
}
|
|
3897
|
+
throw error;
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3900
|
+
|
|
3901
|
+
/**
|
|
3902
|
+
* Dispatcher for rename commands.
|
|
3903
|
+
*/
|
|
3904
|
+
async function renameCommand(type, oldName, newName, options) {
|
|
3905
|
+
try {
|
|
3906
|
+
if (!type || !oldName || !newName) {
|
|
3907
|
+
throw new Error('Usage: textor rename <route|feature|component> <oldName> <newName>');
|
|
3908
|
+
}
|
|
3909
|
+
if (type === 'route' || type === 'path') {
|
|
3910
|
+
const normalizedOld = normalizeRoute(oldName);
|
|
3911
|
+
const normalizedNew = normalizeRoute(newName);
|
|
3912
|
+
// By default, move-section will try to move the feature if it matches the route.
|
|
3913
|
+
// For a simple "rename route", we might want to keep that behavior or not.
|
|
3914
|
+
// Usually "rename route" means just the URL/file.
|
|
3915
|
+
return await moveSectionCommand(normalizedOld, undefined, normalizedNew, undefined, options);
|
|
3916
|
+
}
|
|
3917
|
+
if (type === 'feature') {
|
|
3918
|
+
const state = await loadState();
|
|
3919
|
+
const normalizedOld = featureToDirectoryPath(oldName);
|
|
3920
|
+
const normalizedNew = featureToDirectoryPath(newName);
|
|
3921
|
+
const section = findSection(state, normalizedOld);
|
|
3922
|
+
if (section) {
|
|
3923
|
+
// If it's a managed section, move it using section logic
|
|
3924
|
+
return await moveSectionCommand(section.route, section.featurePath, section.route, normalizedNew, options);
|
|
3925
|
+
}
|
|
3926
|
+
else {
|
|
3927
|
+
// Standalone feature move
|
|
3928
|
+
return await moveSectionCommand(undefined, normalizedOld, undefined, normalizedNew, options);
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
if (type === 'component') {
|
|
3932
|
+
return await renameComponent(oldName, newName, options);
|
|
3933
|
+
}
|
|
3934
|
+
throw new Error(`Unknown rename type: ${type}. Supported types: route, feature, component.`);
|
|
3935
|
+
}
|
|
3936
|
+
catch (error) {
|
|
3937
|
+
console.error('Error:', error.message);
|
|
3938
|
+
if (typeof process.exit === 'function' && process.env.NODE_ENV !== 'test') {
|
|
3939
|
+
process.exit(1);
|
|
3940
|
+
}
|
|
3941
|
+
throw error;
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
/**
|
|
3945
|
+
* Specialized logic for renaming shared components.
|
|
3946
|
+
*/
|
|
3947
|
+
async function renameComponent(oldName, newName, options) {
|
|
3948
|
+
const config = await loadConfig();
|
|
3949
|
+
const state = await loadState();
|
|
3950
|
+
const normalizedOldName = normalizeComponentName(oldName);
|
|
3951
|
+
const normalizedNewName = normalizeComponentName(newName);
|
|
3952
|
+
const component = findComponent(state, normalizedOldName);
|
|
3953
|
+
const componentsRoot = resolvePath(config, 'components');
|
|
3954
|
+
const fromPath = component
|
|
3955
|
+
? path.resolve(process.cwd(), component.path)
|
|
3956
|
+
: path.join(componentsRoot, normalizedOldName);
|
|
3957
|
+
const toPath = path.join(componentsRoot, normalizedNewName);
|
|
3958
|
+
if (options.dryRun) {
|
|
3959
|
+
console.log(`Dry run - would rename component: ${normalizedOldName} -> ${normalizedNewName}`);
|
|
3960
|
+
console.log(` Path: ${fromPath} -> ${toPath}`);
|
|
3961
|
+
return;
|
|
3962
|
+
}
|
|
3963
|
+
const signatures = Object.values(config.signatures || {});
|
|
3964
|
+
await moveDirectory(fromPath, toPath, state, config, {
|
|
3965
|
+
...options,
|
|
3966
|
+
fromName: normalizedOldName,
|
|
3967
|
+
toName: normalizedNewName,
|
|
3968
|
+
signatures
|
|
3969
|
+
});
|
|
3970
|
+
if (options.scan) {
|
|
3971
|
+
await scanAndReplaceImports(config, state, {
|
|
3972
|
+
fromPath: normalizedOldName,
|
|
3973
|
+
fromName: normalizedOldName,
|
|
3974
|
+
type: 'component'
|
|
3975
|
+
}, {
|
|
3976
|
+
toPath: normalizedNewName,
|
|
3977
|
+
toName: normalizedNewName
|
|
3978
|
+
}, options);
|
|
3979
|
+
}
|
|
3980
|
+
await cleanupEmptyDirs(path.dirname(fromPath), componentsRoot);
|
|
3981
|
+
// Update state metadata
|
|
3982
|
+
if (component) {
|
|
3983
|
+
component.name = normalizedNewName;
|
|
3984
|
+
component.path = path.relative(process.cwd(), toPath).replace(/\\/g, '/');
|
|
3985
|
+
}
|
|
3986
|
+
await saveState(state);
|
|
3987
|
+
console.log(`✓ Renamed component ${normalizedOldName} to ${normalizedNewName}`);
|
|
3988
|
+
}
|
|
3989
|
+
|
|
3797
3990
|
exports.addSectionCommand = addSectionCommand;
|
|
3798
3991
|
exports.adoptCommand = adoptCommand;
|
|
3799
3992
|
exports.createComponentCommand = createComponentCommand;
|
|
@@ -3801,8 +3994,10 @@ exports.initCommand = initCommand;
|
|
|
3801
3994
|
exports.listSectionsCommand = listSectionsCommand;
|
|
3802
3995
|
exports.moveSectionCommand = moveSectionCommand;
|
|
3803
3996
|
exports.normalizeStateCommand = normalizeStateCommand;
|
|
3997
|
+
exports.pruneMissingCommand = pruneMissingCommand;
|
|
3804
3998
|
exports.removeComponentCommand = removeComponentCommand;
|
|
3805
3999
|
exports.removeSectionCommand = removeSectionCommand;
|
|
4000
|
+
exports.renameCommand = renameCommand;
|
|
3806
4001
|
exports.statusCommand = statusCommand;
|
|
3807
4002
|
exports.syncCommand = syncCommand;
|
|
3808
4003
|
exports.upgradeConfigCommand = upgradeConfigCommand;
|