@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/bin/textor.js
CHANGED
|
@@ -543,6 +543,7 @@ function normalizeRoute(route) {
|
|
|
543
543
|
|
|
544
544
|
function routeToFilePath(route, options = {}) {
|
|
545
545
|
const { extension = '.astro', mode = 'flat', indexFile = 'index.astro' } = options;
|
|
546
|
+
if (!route) return null;
|
|
546
547
|
const normalized = normalizeRoute(route);
|
|
547
548
|
|
|
548
549
|
if (normalized === '/') {
|
|
@@ -832,6 +833,11 @@ async function safeMove(fromPath, toPath, options = {}) {
|
|
|
832
833
|
throw new Error(`Source file not found: ${fromPath}`);
|
|
833
834
|
}
|
|
834
835
|
|
|
836
|
+
if (path.resolve(fromPath) === path.resolve(toPath)) {
|
|
837
|
+
const content = await readFile(toPath, 'utf-8');
|
|
838
|
+
return calculateHash(content, normalization);
|
|
839
|
+
}
|
|
840
|
+
|
|
835
841
|
if (existsSync(toPath) && !force) {
|
|
836
842
|
throw new Error(
|
|
837
843
|
`Destination already exists: ${toPath}\n` +
|
|
@@ -2067,7 +2073,11 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
2067
2073
|
const configSignatures = Object.values(config.signatures || {});
|
|
2068
2074
|
const isGenerated = await isTextorGenerated(routeFilePath, configSignatures);
|
|
2069
2075
|
if (!isGenerated && !options.force) {
|
|
2070
|
-
|
|
2076
|
+
if (routeFilePath.endsWith('.astro')) {
|
|
2077
|
+
console.log(`⚠ File already exists and is not managed by Textor. Adopting and merging: ${routeFilePath}`);
|
|
2078
|
+
} else {
|
|
2079
|
+
throw new Error(`File already exists: ${routeFilePath}\nUse --force to overwrite.`);
|
|
2080
|
+
}
|
|
2071
2081
|
}
|
|
2072
2082
|
}
|
|
2073
2083
|
}
|
|
@@ -2747,6 +2757,222 @@ async function updateImportsInFile$1(filePath, oldFilePath, newFilePath) {
|
|
|
2747
2757
|
await writeFile(filePath, content, 'utf-8');
|
|
2748
2758
|
}
|
|
2749
2759
|
|
|
2760
|
+
/**
|
|
2761
|
+
* Updates relative imports in a file after it has been moved.
|
|
2762
|
+
*/
|
|
2763
|
+
async function updateImportsInFile(filePath, oldFilePath, newFilePath) {
|
|
2764
|
+
if (!existsSync(filePath)) return;
|
|
2765
|
+
|
|
2766
|
+
let content = await readFile(filePath, 'utf-8');
|
|
2767
|
+
const oldDir = path.dirname(oldFilePath);
|
|
2768
|
+
const newDir = path.dirname(newFilePath);
|
|
2769
|
+
|
|
2770
|
+
if (oldDir === newDir) return;
|
|
2771
|
+
|
|
2772
|
+
// Find all relative imports
|
|
2773
|
+
const relativeImportRegex = /from\s+['"](\.\.?\/[^'"]+)['"]/g;
|
|
2774
|
+
let match;
|
|
2775
|
+
const replacements = [];
|
|
2776
|
+
|
|
2777
|
+
while ((match = relativeImportRegex.exec(content)) !== null) {
|
|
2778
|
+
const relativePath = match[1];
|
|
2779
|
+
const absoluteTarget = path.resolve(oldDir, relativePath);
|
|
2780
|
+
const newRelativePath = getRelativeImportPath(newFilePath, absoluteTarget);
|
|
2781
|
+
|
|
2782
|
+
replacements.push({
|
|
2783
|
+
full: match[0],
|
|
2784
|
+
oldRel: relativePath,
|
|
2785
|
+
newRel: newRelativePath
|
|
2786
|
+
});
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
for (const repl of replacements) {
|
|
2790
|
+
content = content.replace(repl.full, `from '${repl.newRel}'`);
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
await writeFile(filePath, content, 'utf-8');
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
/**
|
|
2797
|
+
* Moves a directory and its contents, renaming files and updating internal content/imports.
|
|
2798
|
+
*/
|
|
2799
|
+
async function moveDirectory(fromPath, toPath, state, config, options = {}) {
|
|
2800
|
+
const { fromName, toName, owner = null, signatures = [] } = options;
|
|
2801
|
+
|
|
2802
|
+
if (!existsSync(fromPath)) {
|
|
2803
|
+
throw new Error(`Source directory not found: ${fromPath}`);
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
if (existsSync(toPath) && !options.force) {
|
|
2807
|
+
throw new Error(
|
|
2808
|
+
`Destination already exists: ${toPath}\n` +
|
|
2809
|
+
`Use --force to overwrite.`
|
|
2810
|
+
);
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
await ensureDir(toPath);
|
|
2814
|
+
|
|
2815
|
+
const entries = await readdir(fromPath);
|
|
2816
|
+
|
|
2817
|
+
for (const entry of entries) {
|
|
2818
|
+
let targetEntry = entry;
|
|
2819
|
+
|
|
2820
|
+
// Rename files if they match the component name
|
|
2821
|
+
if (fromName && toName && fromName !== toName) {
|
|
2822
|
+
if (entry.includes(fromName)) {
|
|
2823
|
+
targetEntry = entry.replace(fromName, toName);
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
const fromEntryPath = path.join(fromPath, entry);
|
|
2828
|
+
const toEntryPath = path.join(toPath, targetEntry);
|
|
2829
|
+
|
|
2830
|
+
const stats = await stat(fromEntryPath);
|
|
2831
|
+
|
|
2832
|
+
if (stats.isDirectory()) {
|
|
2833
|
+
await moveDirectory(fromEntryPath, toEntryPath, state, config, options);
|
|
2834
|
+
} else {
|
|
2835
|
+
const normalizedFromRelative = path.relative(process.cwd(), fromEntryPath).replace(/\\/g, '/');
|
|
2836
|
+
const fileState = state.files[normalizedFromRelative];
|
|
2837
|
+
|
|
2838
|
+
const newHash = await safeMove(fromEntryPath, toEntryPath, {
|
|
2839
|
+
force: options.force,
|
|
2840
|
+
expectedHash: fileState?.hash,
|
|
2841
|
+
acceptChanges: options.acceptChanges,
|
|
2842
|
+
normalization: config.hashing?.normalization,
|
|
2843
|
+
owner,
|
|
2844
|
+
actualOwner: fileState?.owner,
|
|
2845
|
+
signatures
|
|
2846
|
+
});
|
|
2847
|
+
|
|
2848
|
+
// Update internal content (signatures, component names) if renaming
|
|
2849
|
+
if (fromName && toName && fromName !== toName) {
|
|
2850
|
+
let content = await readFile(toEntryPath, 'utf-8');
|
|
2851
|
+
let hasChanged = false;
|
|
2852
|
+
|
|
2853
|
+
// Simple replacement of component names
|
|
2854
|
+
if (content.includes(fromName)) {
|
|
2855
|
+
content = content.replace(new RegExp(fromName, 'g'), toName);
|
|
2856
|
+
hasChanged = true;
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
// Also handle lowercase class names if any
|
|
2860
|
+
const fromLower = fromName.toLowerCase();
|
|
2861
|
+
const toLower = toName.toLowerCase();
|
|
2862
|
+
if (content.includes(fromLower)) {
|
|
2863
|
+
content = content.replace(new RegExp(fromLower, 'g'), toLower);
|
|
2864
|
+
hasChanged = true;
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
if (hasChanged) {
|
|
2868
|
+
await writeFile(toEntryPath, content, 'utf-8');
|
|
2869
|
+
// Re-calculate hash after content update
|
|
2870
|
+
const updatedHash = calculateHash(content, config.hashing?.normalization);
|
|
2871
|
+
|
|
2872
|
+
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
2873
|
+
if (fileState) {
|
|
2874
|
+
state.files[normalizedToRelative] = { ...fileState, hash: updatedHash };
|
|
2875
|
+
delete state.files[normalizedFromRelative];
|
|
2876
|
+
}
|
|
2877
|
+
} else {
|
|
2878
|
+
// Update state for each file moved normally
|
|
2879
|
+
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
2880
|
+
if (fileState) {
|
|
2881
|
+
state.files[normalizedToRelative] = { ...fileState, hash: newHash };
|
|
2882
|
+
delete state.files[normalizedFromRelative];
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
} else {
|
|
2886
|
+
// Update state for each file moved normally
|
|
2887
|
+
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
2888
|
+
if (fileState) {
|
|
2889
|
+
state.files[normalizedToRelative] = { ...fileState, hash: newHash };
|
|
2890
|
+
delete state.files[normalizedFromRelative];
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
const remainingFiles = await readdir(fromPath);
|
|
2897
|
+
if (remainingFiles.length === 0) {
|
|
2898
|
+
await rmdir(fromPath);
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
/**
|
|
2903
|
+
* Scans the project and replaces imports of a moved/renamed item.
|
|
2904
|
+
*/
|
|
2905
|
+
async function scanAndReplaceImports(config, state, fromInfo, toInfo, options) {
|
|
2906
|
+
const { fromPath: fromItemPath, fromName, type } = fromInfo;
|
|
2907
|
+
const { toPath: toItemPath, toName } = toInfo;
|
|
2908
|
+
|
|
2909
|
+
const allFiles = new Set();
|
|
2910
|
+
await scanDirectory(process.cwd(), allFiles);
|
|
2911
|
+
|
|
2912
|
+
const rootPath = resolvePath(config, type === 'component' ? 'components' : 'features');
|
|
2913
|
+
|
|
2914
|
+
for (const relPath of allFiles) {
|
|
2915
|
+
const fullPath = path.resolve(process.cwd(), relPath);
|
|
2916
|
+
|
|
2917
|
+
// Skip the moved directory itself
|
|
2918
|
+
const toFullPath = path.resolve(toItemPath);
|
|
2919
|
+
if (fullPath.startsWith(toFullPath)) continue;
|
|
2920
|
+
|
|
2921
|
+
let content = await readFile(fullPath, 'utf-8');
|
|
2922
|
+
let changed = false;
|
|
2923
|
+
|
|
2924
|
+
const aliasBase = config.importAliases[type === 'component' ? 'components' : 'features'];
|
|
2925
|
+
const ext = type === 'component' ? '' : (config.naming.featureExtension === '.astro' ? '.astro' : '');
|
|
2926
|
+
|
|
2927
|
+
if (aliasBase) {
|
|
2928
|
+
const oldAlias = `${aliasBase}/${fromItemPath}`;
|
|
2929
|
+
const newAlias = `${aliasBase}/${toItemPath}`;
|
|
2930
|
+
|
|
2931
|
+
const oldFullImport = `from '${oldAlias}/${fromName}${ext}'`;
|
|
2932
|
+
const newFullImport = `from '${newAlias}/${toName}${ext}'`;
|
|
2933
|
+
|
|
2934
|
+
if (content.includes(oldFullImport)) {
|
|
2935
|
+
content = content.replace(new RegExp(oldFullImport.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newFullImport);
|
|
2936
|
+
changed = true;
|
|
2937
|
+
} else if (content.includes(oldAlias)) {
|
|
2938
|
+
content = content.replace(new RegExp(oldAlias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newAlias);
|
|
2939
|
+
changed = true;
|
|
2940
|
+
}
|
|
2941
|
+
} else {
|
|
2942
|
+
const oldDir = path.resolve(rootPath, fromItemPath);
|
|
2943
|
+
const newDir = path.resolve(rootPath, toItemPath);
|
|
2944
|
+
|
|
2945
|
+
const oldRelPath = getRelativeImportPath(fullPath, oldDir);
|
|
2946
|
+
const newRelPath = getRelativeImportPath(fullPath, newDir);
|
|
2947
|
+
|
|
2948
|
+
const oldImport = `'${oldRelPath}/${fromName}${ext}'`;
|
|
2949
|
+
const newImport = `'${newRelPath}/${toName}${ext}'`;
|
|
2950
|
+
|
|
2951
|
+
if (content.includes(oldImport)) {
|
|
2952
|
+
content = content.replace(new RegExp(oldImport.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newImport);
|
|
2953
|
+
changed = true;
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
if (fromName !== toName && changed) {
|
|
2958
|
+
content = content.replace(new RegExp(`\\b${fromName}\\b`, 'g'), toName);
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
if (changed) {
|
|
2962
|
+
if (options.dryRun) {
|
|
2963
|
+
console.log(` [Scan] Would update imports in ${relPath}`);
|
|
2964
|
+
} else {
|
|
2965
|
+
await writeFile(fullPath, content, 'utf-8');
|
|
2966
|
+
console.log(` [Scan] Updated imports in ${relPath}`);
|
|
2967
|
+
|
|
2968
|
+
if (state.files[relPath]) {
|
|
2969
|
+
state.files[relPath].hash = calculateHash(content, config.hashing?.normalization);
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2750
2976
|
/**
|
|
2751
2977
|
* Move a section (route + feature).
|
|
2752
2978
|
*
|
|
@@ -2775,45 +3001,59 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2775
3001
|
let actualToRoute = toRoute;
|
|
2776
3002
|
let actualToFeature = toFeature;
|
|
2777
3003
|
|
|
2778
|
-
// Shift arguments if using state
|
|
3004
|
+
// Shift arguments if using state or if called with fewer arguments
|
|
2779
3005
|
if (!toRoute && fromRoute && fromFeature) {
|
|
2780
3006
|
// textor move-section /old-route /new-route
|
|
2781
|
-
|
|
3007
|
+
actualFromRoute = fromRoute;
|
|
3008
|
+
actualToRoute = fromFeature;
|
|
3009
|
+
actualFromFeature = undefined;
|
|
3010
|
+
actualToFeature = undefined;
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
// Lookup missing info from state
|
|
3014
|
+
if (actualFromRoute && !actualFromFeature) {
|
|
3015
|
+
const section = findSection(state, actualFromRoute);
|
|
2782
3016
|
if (section) {
|
|
2783
|
-
actualFromRoute = section.route;
|
|
2784
3017
|
actualFromFeature = section.featurePath;
|
|
2785
|
-
|
|
2786
|
-
|
|
3018
|
+
}
|
|
3019
|
+
} else if (!actualFromRoute && actualFromFeature) {
|
|
3020
|
+
const section = findSection(state, actualFromFeature);
|
|
3021
|
+
if (section) {
|
|
3022
|
+
actualFromRoute = section.route;
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
// If toFeature is not provided, try to derive it from the new route if route moved
|
|
3027
|
+
if (!actualToFeature && actualToRoute && actualFromRoute && actualFromRoute !== actualToRoute && actualFromFeature) {
|
|
3028
|
+
const oldRouteParts = actualFromRoute.split('/').filter(Boolean);
|
|
3029
|
+
const newRouteParts = actualToRoute.split('/').filter(Boolean);
|
|
3030
|
+
const oldFeatureParts = actualFromFeature.split('/').filter(Boolean);
|
|
3031
|
+
|
|
3032
|
+
let match = true;
|
|
3033
|
+
for (let i = 0; i < oldRouteParts.length; i++) {
|
|
3034
|
+
const routePart = oldRouteParts[i].toLowerCase();
|
|
3035
|
+
const featurePart = oldFeatureParts[i] ? oldFeatureParts[i].toLowerCase() : null;
|
|
2787
3036
|
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
const newRouteParts = actualToRoute.split('/').filter(Boolean);
|
|
2792
|
-
const oldFeatureParts = actualFromFeature.split('/').filter(Boolean);
|
|
2793
|
-
|
|
2794
|
-
// If the feature path starts with the old route parts, replace them
|
|
2795
|
-
// We compare case-insensitively or via PascalCase to be more helpful
|
|
2796
|
-
let match = true;
|
|
2797
|
-
for (let i = 0; i < oldRouteParts.length; i++) {
|
|
2798
|
-
const routePart = oldRouteParts[i].toLowerCase();
|
|
2799
|
-
const featurePart = oldFeatureParts[i] ? oldFeatureParts[i].toLowerCase() : null;
|
|
2800
|
-
|
|
2801
|
-
if (featurePart !== routePart) {
|
|
2802
|
-
match = false;
|
|
2803
|
-
break;
|
|
2804
|
-
}
|
|
2805
|
-
}
|
|
2806
|
-
|
|
2807
|
-
if (match && oldRouteParts.length > 0) {
|
|
2808
|
-
actualToFeature = [...newRouteParts, ...oldFeatureParts.slice(oldRouteParts.length)].join('/');
|
|
2809
|
-
} else {
|
|
2810
|
-
// Otherwise just keep it the same
|
|
2811
|
-
actualToFeature = actualFromFeature;
|
|
2812
|
-
}
|
|
3037
|
+
if (featurePart !== routePart) {
|
|
3038
|
+
match = false;
|
|
3039
|
+
break;
|
|
2813
3040
|
}
|
|
2814
3041
|
}
|
|
3042
|
+
|
|
3043
|
+
if (match && oldRouteParts.length > 0) {
|
|
3044
|
+
actualToFeature = [...newRouteParts, ...oldFeatureParts.slice(oldRouteParts.length)].join('/');
|
|
3045
|
+
} else {
|
|
3046
|
+
actualToFeature = actualFromFeature;
|
|
3047
|
+
}
|
|
3048
|
+
} else if (!actualToFeature) {
|
|
3049
|
+
actualToFeature = actualFromFeature;
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
if (!actualToRoute) {
|
|
3053
|
+
actualToRoute = actualFromRoute;
|
|
2815
3054
|
}
|
|
2816
3055
|
|
|
3056
|
+
|
|
2817
3057
|
const isRouteOnly = options.keepFeature || (!actualToFeature && actualToRoute && !actualFromFeature);
|
|
2818
3058
|
|
|
2819
3059
|
if (isRouteOnly && !actualToRoute) {
|
|
@@ -2843,14 +3083,16 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2843
3083
|
indexFile: config.routing.indexFile
|
|
2844
3084
|
});
|
|
2845
3085
|
|
|
2846
|
-
const fromRoutePath = secureJoin(pagesRoot, fromRouteFile);
|
|
2847
|
-
const toRoutePath = secureJoin(pagesRoot, toRouteFile);
|
|
3086
|
+
const fromRoutePath = fromRouteFile ? secureJoin(pagesRoot, fromRouteFile) : null;
|
|
3087
|
+
const toRoutePath = toRouteFile ? secureJoin(pagesRoot, toRouteFile) : null;
|
|
2848
3088
|
|
|
2849
3089
|
const movedFiles = [];
|
|
2850
3090
|
|
|
2851
3091
|
if (options.dryRun) {
|
|
2852
3092
|
console.log('Dry run - would move:');
|
|
2853
|
-
|
|
3093
|
+
if (fromRoutePath && toRoutePath && fromRoutePath !== toRoutePath) {
|
|
3094
|
+
console.log(` Route: ${fromRoutePath} -> ${toRoutePath}`);
|
|
3095
|
+
}
|
|
2854
3096
|
|
|
2855
3097
|
if (!isRouteOnly && normalizedFromFeature && normalizedToFeature) {
|
|
2856
3098
|
const fromFeaturePath = secureJoin(featuresRoot, normalizedFromFeature);
|
|
@@ -2861,97 +3103,107 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2861
3103
|
return;
|
|
2862
3104
|
}
|
|
2863
3105
|
|
|
2864
|
-
|
|
2865
|
-
|
|
3106
|
+
let normalizedToRouteRelative = null;
|
|
3107
|
+
if (fromRoutePath && toRoutePath) {
|
|
3108
|
+
const normalizedFromRouteRelative = path.relative(process.cwd(), fromRoutePath).replace(/\\/g, '/');
|
|
3109
|
+
const routeFileState = state.files[normalizedFromRouteRelative];
|
|
2866
3110
|
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
const normalizedToRouteRelative = path.relative(process.cwd(), toRoutePath).replace(/\\/g, '/');
|
|
2879
|
-
if (routeFileState) {
|
|
2880
|
-
state.files[normalizedToRouteRelative] = { ...routeFileState, hash: newRouteHash };
|
|
2881
|
-
delete state.files[normalizedFromRouteRelative];
|
|
2882
|
-
}
|
|
2883
|
-
|
|
2884
|
-
// Update imports in the moved route file
|
|
2885
|
-
const targetFeature = normalizedToFeature || normalizedFromFeature;
|
|
2886
|
-
if (targetFeature) {
|
|
2887
|
-
const fromFeatureDirPath = secureJoin(featuresRoot, normalizedFromFeature);
|
|
2888
|
-
const toFeatureDirPath = secureJoin(featuresRoot, targetFeature);
|
|
2889
|
-
const fromFeatureComponentName = getFeatureComponentName(normalizedFromFeature);
|
|
2890
|
-
const toFeatureComponentName = getFeatureComponentName(targetFeature);
|
|
2891
|
-
|
|
2892
|
-
// First, update all relative imports in the file because it moved
|
|
2893
|
-
await updateImportsInFile(toRoutePath, fromRoutePath, toRoutePath);
|
|
2894
|
-
|
|
2895
|
-
let content = await readFile(toRoutePath, 'utf-8');
|
|
2896
|
-
let changed = false;
|
|
2897
|
-
|
|
2898
|
-
// Update component name in JSX tags
|
|
2899
|
-
if (fromFeatureComponentName !== toFeatureComponentName) {
|
|
2900
|
-
content = content.replace(
|
|
2901
|
-
new RegExp(`<${fromFeatureComponentName}`, 'g'),
|
|
2902
|
-
`<${toFeatureComponentName}`
|
|
2903
|
-
);
|
|
2904
|
-
content = content.replace(
|
|
2905
|
-
new RegExp(`</${fromFeatureComponentName}`, 'g'),
|
|
2906
|
-
`</${toFeatureComponentName}`
|
|
2907
|
-
);
|
|
2908
|
-
changed = true;
|
|
3111
|
+
const newRouteHash = await safeMove(fromRoutePath, toRoutePath, {
|
|
3112
|
+
force: options.force,
|
|
3113
|
+
expectedHash: routeFileState?.hash,
|
|
3114
|
+
acceptChanges: options.acceptChanges,
|
|
3115
|
+
owner: normalizedFromRoute,
|
|
3116
|
+
actualOwner: routeFileState?.owner,
|
|
3117
|
+
signatures: configSignatures
|
|
3118
|
+
});
|
|
3119
|
+
|
|
3120
|
+
if (fromRoutePath !== toRoutePath) {
|
|
3121
|
+
movedFiles.push({ from: fromRoutePath, to: toRoutePath });
|
|
2909
3122
|
}
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
if (importRegex.test(content)) {
|
|
2919
|
-
content = content.replace(importRegex, (match, p1, p2, p3, subPath, p5) => {
|
|
2920
|
-
let newSubPath = subPath || '';
|
|
2921
|
-
if (subPath && subPath.includes(fromFeatureComponentName)) {
|
|
2922
|
-
newSubPath = subPath.replace(fromFeatureComponentName, toFeatureComponentName);
|
|
2923
|
-
}
|
|
2924
|
-
return `${p1}${toFeatureComponentName}${p3}${newAliasPath}${newSubPath}${p5}`;
|
|
2925
|
-
});
|
|
2926
|
-
changed = true;
|
|
2927
|
-
} else if (content.includes(oldAliasPath)) {
|
|
2928
|
-
// Fallback for path only replacement
|
|
2929
|
-
content = content.replace(new RegExp(oldAliasPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newAliasPath);
|
|
2930
|
-
changed = true;
|
|
3123
|
+
|
|
3124
|
+
// Update state for moved route file
|
|
3125
|
+
normalizedToRouteRelative = path.relative(process.cwd(), toRoutePath).replace(/\\/g, '/');
|
|
3126
|
+
if (routeFileState) {
|
|
3127
|
+
state.files[normalizedToRouteRelative] = { ...routeFileState, hash: newRouteHash };
|
|
3128
|
+
if (fromRoutePath !== toRoutePath) {
|
|
3129
|
+
delete state.files[normalizedFromRouteRelative];
|
|
2931
3130
|
}
|
|
2932
|
-
}
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
const
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
// Update imports in the route file (even if it didn't move, as feature might have)
|
|
3134
|
+
const targetFeature = normalizedToFeature || normalizedFromFeature;
|
|
3135
|
+
if (targetFeature && existsSync(toRoutePath)) {
|
|
3136
|
+
const fromFeatureDirPath = secureJoin(featuresRoot, normalizedFromFeature);
|
|
3137
|
+
const toFeatureDirPath = secureJoin(featuresRoot, targetFeature);
|
|
3138
|
+
const fromFeatureComponentName = getFeatureComponentName(normalizedFromFeature);
|
|
3139
|
+
const toFeatureComponentName = getFeatureComponentName(targetFeature);
|
|
3140
|
+
|
|
3141
|
+
// First, update all relative imports in the file because it moved (or stayed)
|
|
3142
|
+
await updateImportsInFile(toRoutePath, fromRoutePath, toRoutePath);
|
|
3143
|
+
|
|
3144
|
+
let content = await readFile(toRoutePath, 'utf-8');
|
|
3145
|
+
let changed = false;
|
|
3146
|
+
|
|
3147
|
+
// Update component name in JSX tags
|
|
3148
|
+
if (fromFeatureComponentName !== toFeatureComponentName) {
|
|
3149
|
+
content = content.replace(
|
|
3150
|
+
new RegExp(`<${fromFeatureComponentName}`, 'g'),
|
|
3151
|
+
`<${toFeatureComponentName}`
|
|
3152
|
+
);
|
|
3153
|
+
content = content.replace(
|
|
3154
|
+
new RegExp(`</${fromFeatureComponentName}`, 'g'),
|
|
3155
|
+
`</${toFeatureComponentName}`
|
|
3156
|
+
);
|
|
2947
3157
|
changed = true;
|
|
2948
3158
|
}
|
|
2949
|
-
}
|
|
2950
3159
|
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
3160
|
+
if (config.importAliases.features) {
|
|
3161
|
+
const oldAliasPath = `${config.importAliases.features}/${normalizedFromFeature}`;
|
|
3162
|
+
const newAliasPath = `${config.importAliases.features}/${targetFeature}`;
|
|
3163
|
+
|
|
3164
|
+
// Flexible regex to match import identifier and path with alias
|
|
3165
|
+
const importRegex = new RegExp(`(import\\s+)(${fromFeatureComponentName})(\\s+from\\s+['"])${oldAliasPath}(/[^'"]+)?(['"])`, 'g');
|
|
3166
|
+
|
|
3167
|
+
if (importRegex.test(content)) {
|
|
3168
|
+
content = content.replace(importRegex, (match, p1, p2, p3, subPath, p5) => {
|
|
3169
|
+
let newSubPath = subPath || '';
|
|
3170
|
+
if (subPath && subPath.includes(fromFeatureComponentName)) {
|
|
3171
|
+
newSubPath = subPath.replace(fromFeatureComponentName, toFeatureComponentName);
|
|
3172
|
+
}
|
|
3173
|
+
return `${p1}${toFeatureComponentName}${p3}${newAliasPath}${newSubPath}${p5}`;
|
|
3174
|
+
});
|
|
3175
|
+
changed = true;
|
|
3176
|
+
} else if (content.includes(oldAliasPath)) {
|
|
3177
|
+
// Fallback for path only replacement
|
|
3178
|
+
content = content.replace(new RegExp(oldAliasPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newAliasPath);
|
|
3179
|
+
changed = true;
|
|
3180
|
+
}
|
|
3181
|
+
} else {
|
|
3182
|
+
const oldRelativeDir = getRelativeImportPath(toRoutePath, fromFeatureDirPath);
|
|
3183
|
+
const newRelativeDir = getRelativeImportPath(toRoutePath, toFeatureDirPath);
|
|
3184
|
+
|
|
3185
|
+
// Flexible regex for relative imports
|
|
3186
|
+
const relImportRegex = new RegExp(`(import\\s+)(${fromFeatureComponentName})(\\s+from\\s+['"])${oldRelativeDir}(/[^'"]+)?(['"])`, 'g');
|
|
3187
|
+
|
|
3188
|
+
if (relImportRegex.test(content)) {
|
|
3189
|
+
content = content.replace(relImportRegex, (match, p1, p2, p3, subPath, p5) => {
|
|
3190
|
+
let newSubPath = subPath || '';
|
|
3191
|
+
if (subPath && subPath.includes(fromFeatureComponentName)) {
|
|
3192
|
+
newSubPath = subPath.replace(fromFeatureComponentName, toFeatureComponentName);
|
|
3193
|
+
}
|
|
3194
|
+
return `${p1}${toFeatureComponentName}${p3}${newRelativeDir}${newSubPath}${p5}`;
|
|
3195
|
+
});
|
|
3196
|
+
changed = true;
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
|
|
3200
|
+
if (changed) {
|
|
3201
|
+
await writeFile(toRoutePath, content, 'utf-8');
|
|
3202
|
+
// Update hash in state after changes
|
|
3203
|
+
if (state.files[normalizedToRouteRelative]) {
|
|
3204
|
+
state.files[normalizedToRouteRelative].hash = calculateHash(content, config.hashing?.normalization);
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
2955
3207
|
}
|
|
2956
3208
|
}
|
|
2957
3209
|
|
|
@@ -2978,15 +3230,18 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2978
3230
|
|
|
2979
3231
|
if (options.scan && (normalizedFromFeature || normalizedToFeature)) {
|
|
2980
3232
|
await scanAndReplaceImports(config, state, {
|
|
2981
|
-
|
|
2982
|
-
|
|
3233
|
+
fromPath: normalizedFromFeature,
|
|
3234
|
+
fromName: getFeatureComponentName(normalizedFromFeature),
|
|
3235
|
+
type: 'feature'
|
|
2983
3236
|
}, {
|
|
2984
|
-
|
|
2985
|
-
|
|
3237
|
+
toPath: normalizedToFeature || normalizedFromFeature,
|
|
3238
|
+
toName: getFeatureComponentName(normalizedToFeature || normalizedFromFeature)
|
|
2986
3239
|
}, options);
|
|
2987
3240
|
}
|
|
2988
3241
|
|
|
2989
|
-
|
|
3242
|
+
if (fromRoutePath && toRoutePath && fromRoutePath !== toRoutePath) {
|
|
3243
|
+
await cleanupEmptyDirs(path.dirname(fromRoutePath), pagesRoot);
|
|
3244
|
+
}
|
|
2990
3245
|
|
|
2991
3246
|
console.log('✓ Moved:');
|
|
2992
3247
|
movedFiles.forEach(item => {
|
|
@@ -2997,6 +3252,15 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2997
3252
|
if (movedFiles.length > 0) {
|
|
2998
3253
|
const existingSection = fromSection;
|
|
2999
3254
|
|
|
3255
|
+
// Update ownership in state if route moved
|
|
3256
|
+
if (normalizedFromRoute && normalizedToRoute && normalizedFromRoute !== normalizedToRoute) {
|
|
3257
|
+
for (const f in state.files) {
|
|
3258
|
+
if (state.files[f].owner === normalizedFromRoute) {
|
|
3259
|
+
state.files[f].owner = normalizedToRoute;
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3000
3264
|
// Update section data in state
|
|
3001
3265
|
state.sections = state.sections.filter(s => s.route !== normalizedFromRoute);
|
|
3002
3266
|
state.sections.push({
|
|
@@ -3019,216 +3283,6 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
3019
3283
|
}
|
|
3020
3284
|
}
|
|
3021
3285
|
|
|
3022
|
-
async function scanAndReplaceImports(config, state, fromInfo, toInfo, options) {
|
|
3023
|
-
const { fromFeaturePath, fromComponentName } = fromInfo;
|
|
3024
|
-
const { toFeaturePath, toComponentName } = toInfo;
|
|
3025
|
-
|
|
3026
|
-
const allFiles = new Set();
|
|
3027
|
-
await scanDirectory(process.cwd(), allFiles);
|
|
3028
|
-
|
|
3029
|
-
const featuresRoot = resolvePath(config, 'features');
|
|
3030
|
-
|
|
3031
|
-
for (const relPath of allFiles) {
|
|
3032
|
-
const fullPath = path.join(process.cwd(), relPath);
|
|
3033
|
-
|
|
3034
|
-
// Skip the moved directory itself as it was already handled
|
|
3035
|
-
if (fullPath.startsWith(path.resolve(toFeaturePath))) continue;
|
|
3036
|
-
|
|
3037
|
-
let content = await readFile(fullPath, 'utf-8');
|
|
3038
|
-
let changed = false;
|
|
3039
|
-
|
|
3040
|
-
const ext = config.naming.featureExtension === '.astro' ? '.astro' : '';
|
|
3041
|
-
|
|
3042
|
-
// Handle Aliases
|
|
3043
|
-
if (config.importAliases.features) {
|
|
3044
|
-
const oldAlias = `${config.importAliases.features}/${fromFeaturePath}`;
|
|
3045
|
-
const newAlias = `${config.importAliases.features}/${toFeaturePath}`;
|
|
3046
|
-
|
|
3047
|
-
// Update component name and path if both changed
|
|
3048
|
-
const oldFullImport = `from '${oldAlias}/${fromComponentName}${ext}'`;
|
|
3049
|
-
const newFullImport = `from '${newAlias}/${toComponentName}${ext}'`;
|
|
3050
|
-
|
|
3051
|
-
if (content.includes(oldFullImport)) {
|
|
3052
|
-
content = content.replace(new RegExp(oldFullImport.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newFullImport);
|
|
3053
|
-
changed = true;
|
|
3054
|
-
} else if (content.includes(oldAlias)) {
|
|
3055
|
-
content = content.replace(new RegExp(oldAlias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newAlias);
|
|
3056
|
-
changed = true;
|
|
3057
|
-
}
|
|
3058
|
-
} else {
|
|
3059
|
-
// Handle Relative Imports (more complex)
|
|
3060
|
-
// This is best-effort: we look for imports that resolve to the old feature path
|
|
3061
|
-
const fromFeatureDir = secureJoin(featuresRoot, fromFeaturePath);
|
|
3062
|
-
const toFeatureDir = secureJoin(featuresRoot, toFeaturePath);
|
|
3063
|
-
|
|
3064
|
-
const oldRelPath = getRelativeImportPath(fullPath, fromFeatureDir);
|
|
3065
|
-
const newRelPath = getRelativeImportPath(fullPath, toFeatureDir);
|
|
3066
|
-
|
|
3067
|
-
const oldImport = `'${oldRelPath}/${fromComponentName}${ext}'`;
|
|
3068
|
-
const newImport = `'${newRelPath}/${toComponentName}${ext}'`;
|
|
3069
|
-
|
|
3070
|
-
if (content.includes(oldImport)) {
|
|
3071
|
-
content = content.replace(new RegExp(oldImport.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newImport);
|
|
3072
|
-
changed = true;
|
|
3073
|
-
}
|
|
3074
|
-
}
|
|
3075
|
-
|
|
3076
|
-
// Update component name in JSX and imports if it changed
|
|
3077
|
-
if (fromComponentName !== toComponentName && changed) {
|
|
3078
|
-
content = content.replace(new RegExp(`\\b${fromComponentName}\\b`, 'g'), toComponentName);
|
|
3079
|
-
}
|
|
3080
|
-
|
|
3081
|
-
if (changed) {
|
|
3082
|
-
if (options.dryRun) {
|
|
3083
|
-
console.log(` [Scan] Would update imports in ${relPath}`);
|
|
3084
|
-
} else {
|
|
3085
|
-
await writeFile(fullPath, content, 'utf-8');
|
|
3086
|
-
console.log(` [Scan] Updated imports in ${relPath}`);
|
|
3087
|
-
|
|
3088
|
-
// Update state hash if this file is managed
|
|
3089
|
-
if (state.files[relPath]) {
|
|
3090
|
-
state.files[relPath].hash = calculateHash(content, config.hashing?.normalization);
|
|
3091
|
-
}
|
|
3092
|
-
}
|
|
3093
|
-
}
|
|
3094
|
-
}
|
|
3095
|
-
}
|
|
3096
|
-
|
|
3097
|
-
async function moveDirectory(fromPath, toPath, state, config, options = {}) {
|
|
3098
|
-
const { fromName, toName, owner = null } = options;
|
|
3099
|
-
|
|
3100
|
-
if (!existsSync(fromPath)) {
|
|
3101
|
-
throw new Error(`Source directory not found: ${fromPath}`);
|
|
3102
|
-
}
|
|
3103
|
-
|
|
3104
|
-
if (existsSync(toPath) && !options.force) {
|
|
3105
|
-
throw new Error(
|
|
3106
|
-
`Destination already exists: ${toPath}\n` +
|
|
3107
|
-
`Use --force to overwrite.`
|
|
3108
|
-
);
|
|
3109
|
-
}
|
|
3110
|
-
|
|
3111
|
-
await ensureDir(toPath);
|
|
3112
|
-
|
|
3113
|
-
const entries = await readdir(fromPath);
|
|
3114
|
-
|
|
3115
|
-
for (const entry of entries) {
|
|
3116
|
-
let targetEntry = entry;
|
|
3117
|
-
|
|
3118
|
-
// Rename files if they match the component name
|
|
3119
|
-
if (fromName && toName && fromName !== toName) {
|
|
3120
|
-
if (entry.includes(fromName)) {
|
|
3121
|
-
targetEntry = entry.replace(fromName, toName);
|
|
3122
|
-
}
|
|
3123
|
-
}
|
|
3124
|
-
|
|
3125
|
-
const fromEntryPath = path.join(fromPath, entry);
|
|
3126
|
-
const toEntryPath = path.join(toPath, targetEntry);
|
|
3127
|
-
|
|
3128
|
-
const stats = await stat(fromEntryPath);
|
|
3129
|
-
|
|
3130
|
-
if (stats.isDirectory()) {
|
|
3131
|
-
await moveDirectory(fromEntryPath, toEntryPath, state, config, options);
|
|
3132
|
-
} else {
|
|
3133
|
-
const normalizedFromRelative = path.relative(process.cwd(), fromEntryPath).replace(/\\/g, '/');
|
|
3134
|
-
const fileState = state.files[normalizedFromRelative];
|
|
3135
|
-
|
|
3136
|
-
const newHash = await safeMove(fromEntryPath, toEntryPath, {
|
|
3137
|
-
force: options.force,
|
|
3138
|
-
expectedHash: fileState?.hash,
|
|
3139
|
-
acceptChanges: options.acceptChanges,
|
|
3140
|
-
normalization: config.hashing?.normalization,
|
|
3141
|
-
owner,
|
|
3142
|
-
actualOwner: fileState?.owner
|
|
3143
|
-
});
|
|
3144
|
-
|
|
3145
|
-
// Update internal content (signatures, component names) if renaming
|
|
3146
|
-
if (fromName && toName && fromName !== toName) {
|
|
3147
|
-
let content = await readFile(toEntryPath, 'utf-8');
|
|
3148
|
-
let hasChanged = false;
|
|
3149
|
-
|
|
3150
|
-
// Simple replacement of component names
|
|
3151
|
-
if (content.includes(fromName)) {
|
|
3152
|
-
content = content.replace(new RegExp(fromName, 'g'), toName);
|
|
3153
|
-
hasChanged = true;
|
|
3154
|
-
}
|
|
3155
|
-
|
|
3156
|
-
// Also handle lowercase class names if any
|
|
3157
|
-
const fromLower = fromName.toLowerCase();
|
|
3158
|
-
const toLower = toName.toLowerCase();
|
|
3159
|
-
if (content.includes(fromLower)) {
|
|
3160
|
-
content = content.replace(new RegExp(fromLower, 'g'), toLower);
|
|
3161
|
-
hasChanged = true;
|
|
3162
|
-
}
|
|
3163
|
-
|
|
3164
|
-
if (hasChanged) {
|
|
3165
|
-
await writeFile(toEntryPath, content, 'utf-8');
|
|
3166
|
-
// Re-calculate hash after content update
|
|
3167
|
-
const updatedHash = calculateHash(content, config.hashing?.normalization);
|
|
3168
|
-
|
|
3169
|
-
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
3170
|
-
if (fileState) {
|
|
3171
|
-
state.files[normalizedToRelative] = { ...fileState, hash: updatedHash };
|
|
3172
|
-
delete state.files[normalizedFromRelative];
|
|
3173
|
-
}
|
|
3174
|
-
} else {
|
|
3175
|
-
// Update state for each file moved normally
|
|
3176
|
-
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
3177
|
-
if (fileState) {
|
|
3178
|
-
state.files[normalizedToRelative] = { ...fileState, hash: newHash };
|
|
3179
|
-
delete state.files[normalizedFromRelative];
|
|
3180
|
-
}
|
|
3181
|
-
}
|
|
3182
|
-
} else {
|
|
3183
|
-
// Update state for each file moved normally
|
|
3184
|
-
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
3185
|
-
if (fileState) {
|
|
3186
|
-
state.files[normalizedToRelative] = { ...fileState, hash: newHash };
|
|
3187
|
-
delete state.files[normalizedFromRelative];
|
|
3188
|
-
}
|
|
3189
|
-
}
|
|
3190
|
-
}
|
|
3191
|
-
}
|
|
3192
|
-
|
|
3193
|
-
const remainingFiles = await readdir(fromPath);
|
|
3194
|
-
if (remainingFiles.length === 0) {
|
|
3195
|
-
await rmdir(fromPath);
|
|
3196
|
-
}
|
|
3197
|
-
}
|
|
3198
|
-
|
|
3199
|
-
async function updateImportsInFile(filePath, oldFilePath, newFilePath) {
|
|
3200
|
-
if (!existsSync(filePath)) return;
|
|
3201
|
-
|
|
3202
|
-
let content = await readFile(filePath, 'utf-8');
|
|
3203
|
-
const oldDir = path.dirname(oldFilePath);
|
|
3204
|
-
const newDir = path.dirname(newFilePath);
|
|
3205
|
-
|
|
3206
|
-
if (oldDir === newDir) return;
|
|
3207
|
-
|
|
3208
|
-
// Find all relative imports
|
|
3209
|
-
const relativeImportRegex = /from\s+['"](\.\.?\/[^'"]+)['"]/g;
|
|
3210
|
-
let match;
|
|
3211
|
-
const replacements = [];
|
|
3212
|
-
|
|
3213
|
-
while ((match = relativeImportRegex.exec(content)) !== null) {
|
|
3214
|
-
const relativePath = match[1];
|
|
3215
|
-
const absoluteTarget = path.resolve(oldDir, relativePath);
|
|
3216
|
-
const newRelativePath = getRelativeImportPath(newFilePath, absoluteTarget);
|
|
3217
|
-
|
|
3218
|
-
replacements.push({
|
|
3219
|
-
full: match[0],
|
|
3220
|
-
oldRel: relativePath,
|
|
3221
|
-
newRel: newRelativePath
|
|
3222
|
-
});
|
|
3223
|
-
}
|
|
3224
|
-
|
|
3225
|
-
for (const repl of replacements) {
|
|
3226
|
-
content = content.replace(repl.full, `from '${repl.newRel}'`);
|
|
3227
|
-
}
|
|
3228
|
-
|
|
3229
|
-
await writeFile(filePath, content, 'utf-8');
|
|
3230
|
-
}
|
|
3231
|
-
|
|
3232
3286
|
async function createComponentCommand(componentName, options) {
|
|
3233
3287
|
try {
|
|
3234
3288
|
const config = await loadConfig();
|
|
@@ -4363,8 +4417,9 @@ async function adoptFile(relPath, config, state, options) {
|
|
|
4363
4417
|
else if (ext === '.js' || ext === '.jsx') signature = config.signatures.javascript;
|
|
4364
4418
|
|
|
4365
4419
|
let finalContent = content;
|
|
4420
|
+
const shouldAddSignature = signature && !content.includes(signature) && options.signature !== false;
|
|
4366
4421
|
|
|
4367
|
-
if (
|
|
4422
|
+
if (shouldAddSignature) {
|
|
4368
4423
|
if (options.dryRun) {
|
|
4369
4424
|
console.log(` ~ Would add signature to ${relPath}`);
|
|
4370
4425
|
} else {
|
|
@@ -4374,9 +4429,17 @@ async function adoptFile(relPath, config, state, options) {
|
|
|
4374
4429
|
}
|
|
4375
4430
|
} else {
|
|
4376
4431
|
if (options.dryRun) {
|
|
4377
|
-
|
|
4432
|
+
if (signature && !content.includes(signature) && options.signature === false) {
|
|
4433
|
+
console.log(` + Would adopt without signature (explicitly requested): ${relPath}`);
|
|
4434
|
+
} else {
|
|
4435
|
+
console.log(` + Would adopt (already has signature or no signature for ext): ${relPath}`);
|
|
4436
|
+
}
|
|
4378
4437
|
} else {
|
|
4379
|
-
|
|
4438
|
+
if (signature && !content.includes(signature) && options.signature === false) {
|
|
4439
|
+
console.log(` + Adopting without signature (explicitly requested): ${relPath}`);
|
|
4440
|
+
} else {
|
|
4441
|
+
console.log(` + Adopting: ${relPath}`);
|
|
4442
|
+
}
|
|
4380
4443
|
}
|
|
4381
4444
|
}
|
|
4382
4445
|
|
|
@@ -4386,7 +4449,8 @@ async function adoptFile(relPath, config, state, options) {
|
|
|
4386
4449
|
kind: inferKind(relPath, config),
|
|
4387
4450
|
hash: hash,
|
|
4388
4451
|
timestamp: new Date().toISOString(),
|
|
4389
|
-
synced: true
|
|
4452
|
+
synced: true,
|
|
4453
|
+
hasSignature: options.signature !== false
|
|
4390
4454
|
};
|
|
4391
4455
|
}
|
|
4392
4456
|
|
|
@@ -4537,6 +4601,111 @@ async function pruneMissingCommand(options = {}) {
|
|
|
4537
4601
|
}
|
|
4538
4602
|
}
|
|
4539
4603
|
|
|
4604
|
+
/**
|
|
4605
|
+
* Dispatcher for rename commands.
|
|
4606
|
+
*/
|
|
4607
|
+
async function renameCommand(type, oldName, newName, options) {
|
|
4608
|
+
try {
|
|
4609
|
+
if (!type || !oldName || !newName) {
|
|
4610
|
+
throw new Error('Usage: textor rename <route|feature|component> <oldName> <newName>');
|
|
4611
|
+
}
|
|
4612
|
+
|
|
4613
|
+
if (type === 'route' || type === 'path') {
|
|
4614
|
+
const normalizedOld = normalizeRoute(oldName);
|
|
4615
|
+
const normalizedNew = normalizeRoute(newName);
|
|
4616
|
+
// By default, move-section will try to move the feature if it matches the route.
|
|
4617
|
+
// For a simple "rename route", we might want to keep that behavior or not.
|
|
4618
|
+
// Usually "rename route" means just the URL/file.
|
|
4619
|
+
return await moveSectionCommand(normalizedOld, undefined, normalizedNew, undefined, options);
|
|
4620
|
+
}
|
|
4621
|
+
|
|
4622
|
+
if (type === 'feature') {
|
|
4623
|
+
const state = await loadState();
|
|
4624
|
+
const normalizedOld = featureToDirectoryPath(oldName);
|
|
4625
|
+
const normalizedNew = featureToDirectoryPath(newName);
|
|
4626
|
+
|
|
4627
|
+
const section = findSection(state, normalizedOld);
|
|
4628
|
+
|
|
4629
|
+
if (section) {
|
|
4630
|
+
// If it's a managed section, move it using section logic
|
|
4631
|
+
return await moveSectionCommand(section.route, section.featurePath, section.route, normalizedNew, options);
|
|
4632
|
+
} else {
|
|
4633
|
+
// Standalone feature move
|
|
4634
|
+
return await moveSectionCommand(undefined, normalizedOld, undefined, normalizedNew, options);
|
|
4635
|
+
}
|
|
4636
|
+
}
|
|
4637
|
+
|
|
4638
|
+
if (type === 'component') {
|
|
4639
|
+
return await renameComponent(oldName, newName, options);
|
|
4640
|
+
}
|
|
4641
|
+
|
|
4642
|
+
throw new Error(`Unknown rename type: ${type}. Supported types: route, feature, component.`);
|
|
4643
|
+
} catch (error) {
|
|
4644
|
+
console.error('Error:', error.message);
|
|
4645
|
+
if (typeof process.exit === 'function' && process.env.NODE_ENV !== 'test') {
|
|
4646
|
+
process.exit(1);
|
|
4647
|
+
}
|
|
4648
|
+
throw error;
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
|
|
4652
|
+
/**
|
|
4653
|
+
* Specialized logic for renaming shared components.
|
|
4654
|
+
*/
|
|
4655
|
+
async function renameComponent(oldName, newName, options) {
|
|
4656
|
+
const config = await loadConfig();
|
|
4657
|
+
const state = await loadState();
|
|
4658
|
+
|
|
4659
|
+
const normalizedOldName = normalizeComponentName(oldName);
|
|
4660
|
+
const normalizedNewName = normalizeComponentName(newName);
|
|
4661
|
+
|
|
4662
|
+
const component = findComponent(state, normalizedOldName);
|
|
4663
|
+
|
|
4664
|
+
const componentsRoot = resolvePath(config, 'components');
|
|
4665
|
+
const fromPath = component
|
|
4666
|
+
? path.resolve(process.cwd(), component.path)
|
|
4667
|
+
: path.join(componentsRoot, normalizedOldName);
|
|
4668
|
+
|
|
4669
|
+
const toPath = path.join(componentsRoot, normalizedNewName);
|
|
4670
|
+
|
|
4671
|
+
if (options.dryRun) {
|
|
4672
|
+
console.log(`Dry run - would rename component: ${normalizedOldName} -> ${normalizedNewName}`);
|
|
4673
|
+
console.log(` Path: ${fromPath} -> ${toPath}`);
|
|
4674
|
+
return;
|
|
4675
|
+
}
|
|
4676
|
+
|
|
4677
|
+
const signatures = Object.values(config.signatures || {});
|
|
4678
|
+
|
|
4679
|
+
await moveDirectory(fromPath, toPath, state, config, {
|
|
4680
|
+
...options,
|
|
4681
|
+
fromName: normalizedOldName,
|
|
4682
|
+
toName: normalizedNewName,
|
|
4683
|
+
signatures
|
|
4684
|
+
});
|
|
4685
|
+
|
|
4686
|
+
if (options.scan) {
|
|
4687
|
+
await scanAndReplaceImports(config, state, {
|
|
4688
|
+
fromPath: normalizedOldName,
|
|
4689
|
+
fromName: normalizedOldName,
|
|
4690
|
+
type: 'component'
|
|
4691
|
+
}, {
|
|
4692
|
+
toPath: normalizedNewName,
|
|
4693
|
+
toName: normalizedNewName
|
|
4694
|
+
}, options);
|
|
4695
|
+
}
|
|
4696
|
+
|
|
4697
|
+
await cleanupEmptyDirs(path.dirname(fromPath), componentsRoot);
|
|
4698
|
+
|
|
4699
|
+
// Update state metadata
|
|
4700
|
+
if (component) {
|
|
4701
|
+
component.name = normalizedNewName;
|
|
4702
|
+
component.path = path.relative(process.cwd(), toPath).replace(/\\/g, '/');
|
|
4703
|
+
}
|
|
4704
|
+
|
|
4705
|
+
await saveState(state);
|
|
4706
|
+
console.log(`✓ Renamed component ${normalizedOldName} to ${normalizedNewName}`);
|
|
4707
|
+
}
|
|
4708
|
+
|
|
4540
4709
|
const program = new Command();
|
|
4541
4710
|
|
|
4542
4711
|
program
|
|
@@ -4682,5 +4851,14 @@ program
|
|
|
4682
4851
|
.option('--no-interactive', 'Disable interactive prompts')
|
|
4683
4852
|
.action(pruneMissingCommand);
|
|
4684
4853
|
|
|
4854
|
+
program
|
|
4855
|
+
.command('rename <type> <oldName> <newName>')
|
|
4856
|
+
.description('Rename a route, feature, or component')
|
|
4857
|
+
.option('--scan', 'Enable repo-wide import updates')
|
|
4858
|
+
.option('--force', 'Rename even if files are modified or not generated by Textor')
|
|
4859
|
+
.option('--accept-changes', 'Allow renaming of modified files')
|
|
4860
|
+
.option('--dry-run', 'Show what would be renamed without applying')
|
|
4861
|
+
.action(renameCommand);
|
|
4862
|
+
|
|
4685
4863
|
program.parse();
|
|
4686
4864
|
//# sourceMappingURL=textor.js.map
|