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