@oamm/textor 1.0.5 → 1.0.8
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 +30 -2
- package/dist/bin/textor.js +284 -87
- package/dist/bin/textor.js.map +1 -1
- package/dist/index.cjs +241 -78
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +241 -78
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -35,10 +35,13 @@ const CURRENT_CONFIG_VERSION = 2;
|
|
|
35
35
|
* @property {string} signatures.typescript
|
|
36
36
|
* @property {string} signatures.javascript
|
|
37
37
|
* @property {Object} features
|
|
38
|
+
* @property {string} features.framework
|
|
38
39
|
* @property {string} features.entry
|
|
39
40
|
* @property {boolean} features.createSubComponentsDir
|
|
40
41
|
* @property {boolean} features.createScriptsDir
|
|
41
42
|
* @property {string} features.scriptsIndexFile
|
|
43
|
+
* @property {string} features.layout
|
|
44
|
+
* @property {Object} features.layoutProps
|
|
42
45
|
* @property {Object} components
|
|
43
46
|
* @property {boolean} components.createSubComponentsDir
|
|
44
47
|
* @property {boolean} components.createContext
|
|
@@ -103,7 +106,9 @@ const DEFAULT_CONFIG = {
|
|
|
103
106
|
createTypes: false,
|
|
104
107
|
createReadme: false,
|
|
105
108
|
createStories: false,
|
|
106
|
-
createIndex: false
|
|
109
|
+
createIndex: false,
|
|
110
|
+
layout: 'Main',
|
|
111
|
+
layoutProps: {}
|
|
107
112
|
},
|
|
108
113
|
components: {
|
|
109
114
|
framework: 'react',
|
|
@@ -873,27 +878,6 @@ async function formatFiles(filePaths, tool) {
|
|
|
873
878
|
}
|
|
874
879
|
}
|
|
875
880
|
|
|
876
|
-
var filesystem = /*#__PURE__*/Object.freeze({
|
|
877
|
-
__proto__: null,
|
|
878
|
-
calculateHash: calculateHash,
|
|
879
|
-
cleanupEmptyDirs: cleanupEmptyDirs,
|
|
880
|
-
ensureDir: ensureDir,
|
|
881
|
-
ensureNotExists: ensureNotExists,
|
|
882
|
-
formatFiles: formatFiles,
|
|
883
|
-
getSignature: getSignature,
|
|
884
|
-
inferKind: inferKind,
|
|
885
|
-
isEmptyDir: isEmptyDir,
|
|
886
|
-
isTextorGenerated: isTextorGenerated,
|
|
887
|
-
safeDelete: safeDelete,
|
|
888
|
-
safeDeleteDir: safeDeleteDir,
|
|
889
|
-
safeMove: safeMove,
|
|
890
|
-
scanDirectory: scanDirectory,
|
|
891
|
-
secureJoin: secureJoin,
|
|
892
|
-
updateSignature: updateSignature,
|
|
893
|
-
verifyFileIntegrity: verifyFileIntegrity,
|
|
894
|
-
writeFileWithSignature: writeFileWithSignature
|
|
895
|
-
});
|
|
896
|
-
|
|
897
881
|
function renderNamePattern(pattern, data = {}, label = 'pattern') {
|
|
898
882
|
if (typeof pattern !== 'string')
|
|
899
883
|
return null;
|
|
@@ -955,13 +939,15 @@ function getTemplateOverride(templateName, extension, data = {}) {
|
|
|
955
939
|
* - layoutImportPath: Path to import the layout
|
|
956
940
|
* - featureImportPath: Path to import the feature component
|
|
957
941
|
* - featureComponentName: Name of the feature component
|
|
942
|
+
* - layoutProps: Optional properties for the layout
|
|
958
943
|
*/
|
|
959
|
-
function generateRouteTemplate(layoutName, layoutImportPath, featureImportPath, featureComponentName, extension = '.astro') {
|
|
944
|
+
function generateRouteTemplate(layoutName, layoutImportPath, featureImportPath, featureComponentName, extension = '.astro', layoutProps = {}) {
|
|
960
945
|
const override = getTemplateOverride('route', extension, {
|
|
961
946
|
layoutName,
|
|
962
947
|
layoutImportPath,
|
|
963
948
|
featureImportPath,
|
|
964
|
-
featureComponentName
|
|
949
|
+
featureComponentName,
|
|
950
|
+
...layoutProps
|
|
965
951
|
});
|
|
966
952
|
if (override)
|
|
967
953
|
return override;
|
|
@@ -973,12 +959,24 @@ import ${featureComponentName} from '${featureImportPath}';
|
|
|
973
959
|
<${featureComponentName} />
|
|
974
960
|
`;
|
|
975
961
|
}
|
|
962
|
+
const propsStr = Object.entries(layoutProps)
|
|
963
|
+
.map(([key, value]) => {
|
|
964
|
+
if (typeof value === 'string' && value.startsWith('{') && value.endsWith('}')) {
|
|
965
|
+
return `${key}=${value}`;
|
|
966
|
+
}
|
|
967
|
+
if (typeof value === 'string') {
|
|
968
|
+
return `${key}="${value}"`;
|
|
969
|
+
}
|
|
970
|
+
return `${key}={${JSON.stringify(value)}}`;
|
|
971
|
+
})
|
|
972
|
+
.join(' ');
|
|
973
|
+
const layoutOpening = propsStr ? `<${layoutName} ${propsStr}>` : `<${layoutName}>`;
|
|
976
974
|
return `---
|
|
977
975
|
import ${layoutName} from '${layoutImportPath}';
|
|
978
976
|
import ${featureComponentName} from '${featureImportPath}';
|
|
979
977
|
---
|
|
980
978
|
|
|
981
|
-
|
|
979
|
+
${layoutOpening}
|
|
982
980
|
<${featureComponentName} />
|
|
983
981
|
</${layoutName}>
|
|
984
982
|
`;
|
|
@@ -1177,6 +1175,10 @@ function generateIndexTemplate(componentName, componentExtension) {
|
|
|
1177
1175
|
const override = getTemplateOverride('index', '.ts', { componentName, componentExtension });
|
|
1178
1176
|
if (override)
|
|
1179
1177
|
return override;
|
|
1178
|
+
if (componentExtension === '.astro') {
|
|
1179
|
+
return `export * from './types';
|
|
1180
|
+
`;
|
|
1181
|
+
}
|
|
1180
1182
|
return `export { default as ${componentName} } from './${componentName}${componentExtension}';
|
|
1181
1183
|
export * from './types';
|
|
1182
1184
|
`;
|
|
@@ -1590,7 +1592,7 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1590
1592
|
const apiDirInside = secureJoin(featureDirPath, 'api');
|
|
1591
1593
|
const servicesDirInside = secureJoin(featureDirPath, 'services');
|
|
1592
1594
|
const schemasDirInside = secureJoin(featureDirPath, 'schemas');
|
|
1593
|
-
const { framework, createSubComponentsDir: shouldCreateSubComponentsDir, createScriptsDir: shouldCreateScriptsDir, createApi: shouldCreateApi, createServices: shouldCreateServices, createSchemas: shouldCreateSchemas, createHooks: shouldCreateHooks, createContext: shouldCreateContext, createTests: shouldCreateTests, createTypes: shouldCreateTypes, createReadme: shouldCreateReadme, createStories: shouldCreateStories, createIndex: shouldCreateIndex } = effectiveOptions;
|
|
1595
|
+
const { framework, layout, layoutProps: configLayoutProps, createSubComponentsDir: shouldCreateSubComponentsDir, createScriptsDir: shouldCreateScriptsDir, createApi: shouldCreateApi, createServices: shouldCreateServices, createSchemas: shouldCreateSchemas, createHooks: shouldCreateHooks, createContext: shouldCreateContext, createTests: shouldCreateTests, createTypes: shouldCreateTypes, createReadme: shouldCreateReadme, createStories: shouldCreateStories, createIndex: shouldCreateIndex } = effectiveOptions;
|
|
1594
1596
|
const featurePatterns = config.filePatterns?.features || {};
|
|
1595
1597
|
const patternData = {
|
|
1596
1598
|
componentName: featureComponentName,
|
|
@@ -1687,7 +1689,12 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1687
1689
|
delete state.files[oldRelative];
|
|
1688
1690
|
}
|
|
1689
1691
|
// Update imports in the moved file
|
|
1690
|
-
await updateImportsInFile(reorg.to, reorg.from, reorg.to);
|
|
1692
|
+
await updateImportsInFile$2(reorg.to, reorg.from, reorg.to);
|
|
1693
|
+
// Update hash in state after import updates
|
|
1694
|
+
if (state.files[newRelative]) {
|
|
1695
|
+
const content = await promises.readFile(reorg.to, 'utf-8');
|
|
1696
|
+
state.files[newRelative].hash = calculateHash(content, config.hashing?.normalization);
|
|
1697
|
+
}
|
|
1691
1698
|
console.log(`✓ Reorganized ${oldRelative} to ${newRelative}`);
|
|
1692
1699
|
}
|
|
1693
1700
|
await saveState(state);
|
|
@@ -1718,19 +1725,43 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1718
1725
|
if (shouldCreateScriptsDir)
|
|
1719
1726
|
await ensureNotExists(scriptsIndexPath, options.force);
|
|
1720
1727
|
let layoutImportPath = null;
|
|
1721
|
-
|
|
1728
|
+
const cliProps = options.prop || {};
|
|
1729
|
+
const rawLayoutProps = { ...configLayoutProps, ...cliProps };
|
|
1730
|
+
const layoutProps = {};
|
|
1731
|
+
// Resolve variables in layoutProps
|
|
1732
|
+
const substitutionData = enrichData({
|
|
1733
|
+
componentName: featureComponentName,
|
|
1734
|
+
layoutName: layout,
|
|
1735
|
+
featureComponentName: featureComponentName
|
|
1736
|
+
});
|
|
1737
|
+
for (const [key, value] of Object.entries(rawLayoutProps)) {
|
|
1738
|
+
if (typeof value === 'string') {
|
|
1739
|
+
let resolvedValue = value;
|
|
1740
|
+
for (const [varKey, varValue] of Object.entries(substitutionData)) {
|
|
1741
|
+
const regex = new RegExp(`{{${varKey}}}`, 'g');
|
|
1742
|
+
resolvedValue = resolvedValue.replace(regex, varValue);
|
|
1743
|
+
const underscoreRegex = new RegExp(`__${varKey}__`, 'g');
|
|
1744
|
+
resolvedValue = resolvedValue.replace(underscoreRegex, varValue);
|
|
1745
|
+
}
|
|
1746
|
+
layoutProps[key] = resolvedValue;
|
|
1747
|
+
}
|
|
1748
|
+
else {
|
|
1749
|
+
layoutProps[key] = value;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
if (routeFilePath && layout !== 'none') {
|
|
1722
1753
|
if (config.importAliases.layouts) {
|
|
1723
|
-
layoutImportPath = `${config.importAliases.layouts}/${
|
|
1754
|
+
layoutImportPath = `${config.importAliases.layouts}/${layout}.astro`;
|
|
1724
1755
|
}
|
|
1725
1756
|
else {
|
|
1726
|
-
const layoutFilePath = secureJoin(layoutsRoot, `${
|
|
1757
|
+
const layoutFilePath = secureJoin(layoutsRoot, `${layout}.astro`);
|
|
1727
1758
|
layoutImportPath = getRelativeImportPath(routeFilePath, layoutFilePath);
|
|
1728
1759
|
}
|
|
1729
1760
|
}
|
|
1730
1761
|
let featureImportPath = null;
|
|
1731
1762
|
if (routeFilePath) {
|
|
1732
1763
|
if (config.importAliases.features) {
|
|
1733
|
-
const entryPart = effectiveOptions.entry === 'index' ? '' : `/${featureComponentName}`;
|
|
1764
|
+
const entryPart = effectiveOptions.entry === 'index' ? '/index' : `/${featureComponentName}`;
|
|
1734
1765
|
// In Astro, we can often omit the extension for .tsx files, but not for .astro files if using aliases sometimes.
|
|
1735
1766
|
// However, to be safe, we use the configured extension.
|
|
1736
1767
|
featureImportPath = `${config.importAliases.features}/${normalizedFeaturePath}${entryPart}${config.naming.featureExtension}`;
|
|
@@ -1758,7 +1789,7 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1758
1789
|
routeSignature = getSignature(config, 'typescript');
|
|
1759
1790
|
}
|
|
1760
1791
|
else {
|
|
1761
|
-
routeContent = generateRouteTemplate(
|
|
1792
|
+
routeContent = generateRouteTemplate(layout, layoutImportPath, featureImportPath, featureComponentName, routeExtension, layoutProps);
|
|
1762
1793
|
routeSignature = getSignature(config, 'astro');
|
|
1763
1794
|
}
|
|
1764
1795
|
}
|
|
@@ -1959,7 +1990,7 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1959
1990
|
name: options.name || featureComponentName,
|
|
1960
1991
|
route: normalizedRoute,
|
|
1961
1992
|
featurePath: normalizedFeaturePath,
|
|
1962
|
-
layout:
|
|
1993
|
+
layout: layout,
|
|
1963
1994
|
extension: routeExtension
|
|
1964
1995
|
});
|
|
1965
1996
|
if (config.git?.stageChanges) {
|
|
@@ -1974,7 +2005,7 @@ async function addSectionCommand(route, featurePath, options) {
|
|
|
1974
2005
|
throw error;
|
|
1975
2006
|
}
|
|
1976
2007
|
}
|
|
1977
|
-
async function updateImportsInFile(filePath, oldFilePath, newFilePath) {
|
|
2008
|
+
async function updateImportsInFile$2(filePath, oldFilePath, newFilePath) {
|
|
1978
2009
|
if (!fs.existsSync(filePath))
|
|
1979
2010
|
return;
|
|
1980
2011
|
let content = await promises.readFile(filePath, 'utf-8');
|
|
@@ -2009,29 +2040,37 @@ async function removeSectionCommand(route, featurePath, options) {
|
|
|
2009
2040
|
throw new Error('Git repository is not clean. Please commit or stash your changes before proceeding.');
|
|
2010
2041
|
}
|
|
2011
2042
|
const state = await loadState();
|
|
2012
|
-
let targetRoute = route;
|
|
2013
|
-
let targetFeaturePath = featurePath;
|
|
2014
2043
|
let section = findSection(state, route);
|
|
2044
|
+
if (!section && featurePath) {
|
|
2045
|
+
section = findSection(state, featurePath);
|
|
2046
|
+
}
|
|
2047
|
+
const targetRoute = section ? section.route : route;
|
|
2048
|
+
const targetFeaturePath = section ? section.featurePath : featurePath;
|
|
2015
2049
|
if (!targetFeaturePath) {
|
|
2016
|
-
|
|
2017
|
-
targetRoute = section.route;
|
|
2018
|
-
targetFeaturePath = section.featurePath;
|
|
2019
|
-
}
|
|
2020
|
-
else {
|
|
2021
|
-
throw new Error(`Section not found for identifier: ${route}. Please provide both route and featurePath.`);
|
|
2022
|
-
}
|
|
2050
|
+
throw new Error(`Section not found for identifier: ${route}. Please provide both route and featurePath.`);
|
|
2023
2051
|
}
|
|
2024
2052
|
const normalizedRoute = normalizeRoute(targetRoute);
|
|
2025
2053
|
const normalizedFeaturePath = featureToDirectoryPath(targetFeaturePath);
|
|
2026
|
-
const routeExtension = (section && section.extension) || config.naming.routeExtension;
|
|
2027
|
-
const routeFileName = routeToFilePath(normalizedRoute, {
|
|
2028
|
-
extension: routeExtension,
|
|
2029
|
-
mode: config.routing.mode,
|
|
2030
|
-
indexFile: config.routing.indexFile
|
|
2031
|
-
});
|
|
2032
2054
|
const pagesRoot = resolvePath(config, 'pages');
|
|
2033
2055
|
const featuresRoot = resolvePath(config, 'features');
|
|
2034
|
-
|
|
2056
|
+
// Find route file in state if possible
|
|
2057
|
+
let routeFilePath = null;
|
|
2058
|
+
const routeRelPath = Object.keys(state.files).find(f => {
|
|
2059
|
+
const data = state.files[f];
|
|
2060
|
+
return data.kind === 'route' && data.owner === normalizedRoute;
|
|
2061
|
+
});
|
|
2062
|
+
if (routeRelPath) {
|
|
2063
|
+
routeFilePath = path.resolve(process.cwd(), routeRelPath);
|
|
2064
|
+
}
|
|
2065
|
+
else {
|
|
2066
|
+
const routeExtension = (section && section.extension) || config.naming.routeExtension;
|
|
2067
|
+
const routeFileName = routeToFilePath(normalizedRoute, {
|
|
2068
|
+
extension: routeExtension,
|
|
2069
|
+
mode: config.routing.mode,
|
|
2070
|
+
indexFile: config.routing.indexFile
|
|
2071
|
+
});
|
|
2072
|
+
routeFilePath = secureJoin(pagesRoot, routeFileName);
|
|
2073
|
+
}
|
|
2035
2074
|
const featureDirPath = secureJoin(featuresRoot, normalizedFeaturePath);
|
|
2036
2075
|
const deletedFiles = [];
|
|
2037
2076
|
const skippedFiles = [];
|
|
@@ -2106,9 +2145,58 @@ async function removeSectionCommand(route, featurePath, options) {
|
|
|
2106
2145
|
});
|
|
2107
2146
|
}
|
|
2108
2147
|
if (deletedFiles.length === 0 && deletedDirs.length === 0 && skippedFiles.length === 0) {
|
|
2109
|
-
|
|
2148
|
+
if (section) {
|
|
2149
|
+
console.log(`✓ Section ${normalizedRoute} removed from state (files were already missing on disk).`);
|
|
2150
|
+
state.sections = state.sections.filter(s => s.route !== normalizedRoute);
|
|
2151
|
+
await saveState(state);
|
|
2152
|
+
}
|
|
2153
|
+
else {
|
|
2154
|
+
console.log('No files to delete.');
|
|
2155
|
+
}
|
|
2110
2156
|
}
|
|
2111
2157
|
else {
|
|
2158
|
+
// Reorganization (Flattening)
|
|
2159
|
+
if (!options.keepRoute && deletedFiles.length > 0 && config.routing.mode === 'flat') {
|
|
2160
|
+
const routeParts = normalizedRoute.split('/').filter(Boolean);
|
|
2161
|
+
if (routeParts.length > 1) {
|
|
2162
|
+
for (let i = routeParts.length - 1; i >= 1; i--) {
|
|
2163
|
+
const parentRoute = '/' + routeParts.slice(0, i).join('/');
|
|
2164
|
+
const parentDirName = routeParts.slice(0, i).join('/');
|
|
2165
|
+
const parentDirPath = secureJoin(pagesRoot, parentDirName);
|
|
2166
|
+
if (fs.existsSync(parentDirPath)) {
|
|
2167
|
+
const filesInDir = await promises.readdir(parentDirPath);
|
|
2168
|
+
if (filesInDir.length === 1) {
|
|
2169
|
+
const loneFile = filesInDir[0];
|
|
2170
|
+
const ext = path.extname(loneFile);
|
|
2171
|
+
const indexFile = ext === '.astro' ? config.routing.indexFile : `index${ext}`;
|
|
2172
|
+
if (loneFile === indexFile) {
|
|
2173
|
+
const loneFilePath = path.join(parentDirPath, loneFile);
|
|
2174
|
+
const oldRelative = path.relative(process.cwd(), loneFilePath).replace(/\\/g, '/');
|
|
2175
|
+
if (state.files[oldRelative] && state.files[oldRelative].kind === 'route') {
|
|
2176
|
+
const flatFileName = routeToFilePath(parentRoute, {
|
|
2177
|
+
extension: ext,
|
|
2178
|
+
mode: 'flat'
|
|
2179
|
+
});
|
|
2180
|
+
const flatFilePath = secureJoin(pagesRoot, flatFileName);
|
|
2181
|
+
if (!fs.existsSync(flatFilePath)) {
|
|
2182
|
+
await promises.rename(loneFilePath, flatFilePath);
|
|
2183
|
+
const newRelative = path.relative(process.cwd(), flatFilePath).replace(/\\/g, '/');
|
|
2184
|
+
state.files[newRelative] = { ...state.files[oldRelative] };
|
|
2185
|
+
delete state.files[oldRelative];
|
|
2186
|
+
await updateImportsInFile$1(flatFilePath, loneFilePath, flatFilePath);
|
|
2187
|
+
// Update hash in state after import updates
|
|
2188
|
+
const content = await promises.readFile(flatFilePath, 'utf-8');
|
|
2189
|
+
state.files[newRelative].hash = calculateHash(content, config.hashing?.normalization);
|
|
2190
|
+
console.log(`✓ Reorganized ${oldRelative} to ${newRelative} (flattened)`);
|
|
2191
|
+
await cleanupEmptyDirs(parentDirPath, pagesRoot);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2112
2200
|
state.sections = state.sections.filter(s => s.route !== normalizedRoute);
|
|
2113
2201
|
await saveState(state);
|
|
2114
2202
|
}
|
|
@@ -2121,6 +2209,33 @@ async function removeSectionCommand(route, featurePath, options) {
|
|
|
2121
2209
|
throw error;
|
|
2122
2210
|
}
|
|
2123
2211
|
}
|
|
2212
|
+
async function updateImportsInFile$1(filePath, oldFilePath, newFilePath) {
|
|
2213
|
+
if (!fs.existsSync(filePath))
|
|
2214
|
+
return;
|
|
2215
|
+
let content = await promises.readFile(filePath, 'utf-8');
|
|
2216
|
+
const oldDir = path.dirname(oldFilePath);
|
|
2217
|
+
const newDir = path.dirname(newFilePath);
|
|
2218
|
+
if (oldDir === newDir)
|
|
2219
|
+
return;
|
|
2220
|
+
// Find all relative imports
|
|
2221
|
+
const relativeImportRegex = /from\s+['"](\.\.?\/[^'"]+)['"]/g;
|
|
2222
|
+
let match;
|
|
2223
|
+
const replacements = [];
|
|
2224
|
+
while ((match = relativeImportRegex.exec(content)) !== null) {
|
|
2225
|
+
const relativePath = match[1];
|
|
2226
|
+
const absoluteTarget = path.resolve(oldDir, relativePath);
|
|
2227
|
+
const newRelativePath = getRelativeImportPath(newFilePath, absoluteTarget);
|
|
2228
|
+
replacements.push({
|
|
2229
|
+
full: match[0],
|
|
2230
|
+
oldRel: relativePath,
|
|
2231
|
+
newRel: newRelativePath
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
2234
|
+
for (const repl of replacements) {
|
|
2235
|
+
content = content.replace(repl.full, `from '${repl.newRel}'`);
|
|
2236
|
+
}
|
|
2237
|
+
await promises.writeFile(filePath, content, 'utf-8');
|
|
2238
|
+
}
|
|
2124
2239
|
|
|
2125
2240
|
/**
|
|
2126
2241
|
* Move a section (route + feature).
|
|
@@ -2239,40 +2354,58 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
|
|
|
2239
2354
|
const toFeatureDirPath = secureJoin(featuresRoot, targetFeature);
|
|
2240
2355
|
const fromFeatureComponentName = getFeatureComponentName(normalizedFromFeature);
|
|
2241
2356
|
const toFeatureComponentName = getFeatureComponentName(targetFeature);
|
|
2242
|
-
//
|
|
2357
|
+
// First, update all relative imports in the file because it moved
|
|
2358
|
+
await updateImportsInFile(toRoutePath, fromRoutePath, toRoutePath);
|
|
2359
|
+
let content = await promises.readFile(toRoutePath, 'utf-8');
|
|
2360
|
+
let changed = false;
|
|
2361
|
+
// Update component name in JSX tags
|
|
2243
2362
|
if (fromFeatureComponentName !== toFeatureComponentName) {
|
|
2244
|
-
let content = await promises.readFile(toRoutePath, 'utf-8');
|
|
2245
2363
|
content = content.replace(new RegExp(`<${fromFeatureComponentName}`, 'g'), `<${toFeatureComponentName}`);
|
|
2246
2364
|
content = content.replace(new RegExp(`</${fromFeatureComponentName}`, 'g'), `</${toFeatureComponentName}`);
|
|
2247
|
-
|
|
2365
|
+
changed = true;
|
|
2248
2366
|
}
|
|
2249
2367
|
if (config.importAliases.features) {
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2368
|
+
const oldAliasPath = `${config.importAliases.features}/${normalizedFromFeature}`;
|
|
2369
|
+
const newAliasPath = `${config.importAliases.features}/${targetFeature}`;
|
|
2370
|
+
// Flexible regex to match import identifier and path with alias
|
|
2371
|
+
const importRegex = new RegExp(`(import\\s+)(${fromFeatureComponentName})(\\s+from\\s+['"])${oldAliasPath}(/[^'"]+)?(['"])`, 'g');
|
|
2372
|
+
if (importRegex.test(content)) {
|
|
2373
|
+
content = content.replace(importRegex, (match, p1, p2, p3, subPath, p5) => {
|
|
2374
|
+
let newSubPath = subPath || '';
|
|
2375
|
+
if (subPath && subPath.includes(fromFeatureComponentName)) {
|
|
2376
|
+
newSubPath = subPath.replace(fromFeatureComponentName, toFeatureComponentName);
|
|
2377
|
+
}
|
|
2378
|
+
return `${p1}${toFeatureComponentName}${p3}${newAliasPath}${newSubPath}${p5}`;
|
|
2379
|
+
});
|
|
2380
|
+
changed = true;
|
|
2258
2381
|
}
|
|
2259
|
-
else if (
|
|
2260
|
-
//
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
await updateSignature(toRoutePath, `import ${fromFeatureComponentName} from '${aliasPath}/${fromFeatureComponentName}${ext}'`, `import ${toFeatureComponentName} from '${aliasPath}/${toFeatureComponentName}${ext}'`);
|
|
2382
|
+
else if (content.includes(oldAliasPath)) {
|
|
2383
|
+
// Fallback for path only replacement
|
|
2384
|
+
content = content.replace(new RegExp(oldAliasPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newAliasPath);
|
|
2385
|
+
changed = true;
|
|
2264
2386
|
}
|
|
2265
2387
|
}
|
|
2266
2388
|
else {
|
|
2267
|
-
const oldRelativeDir = getRelativeImportPath(
|
|
2389
|
+
const oldRelativeDir = getRelativeImportPath(toRoutePath, fromFeatureDirPath);
|
|
2268
2390
|
const newRelativeDir = getRelativeImportPath(toRoutePath, toFeatureDirPath);
|
|
2269
|
-
|
|
2270
|
-
const
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2391
|
+
// Flexible regex for relative imports
|
|
2392
|
+
const relImportRegex = new RegExp(`(import\\s+)(${fromFeatureComponentName})(\\s+from\\s+['"])${oldRelativeDir}(/[^'"]+)?(['"])`, 'g');
|
|
2393
|
+
if (relImportRegex.test(content)) {
|
|
2394
|
+
content = content.replace(relImportRegex, (match, p1, p2, p3, subPath, p5) => {
|
|
2395
|
+
let newSubPath = subPath || '';
|
|
2396
|
+
if (subPath && subPath.includes(fromFeatureComponentName)) {
|
|
2397
|
+
newSubPath = subPath.replace(fromFeatureComponentName, toFeatureComponentName);
|
|
2398
|
+
}
|
|
2399
|
+
return `${p1}${toFeatureComponentName}${p3}${newRelativeDir}${newSubPath}${p5}`;
|
|
2400
|
+
});
|
|
2401
|
+
changed = true;
|
|
2274
2402
|
}
|
|
2275
2403
|
}
|
|
2404
|
+
if (changed) {
|
|
2405
|
+
await promises.writeFile(toRoutePath, content, 'utf-8');
|
|
2406
|
+
// Update hash in state after changes
|
|
2407
|
+
state.files[normalizedToRouteRelative].hash = calculateHash(content, config.hashing?.normalization);
|
|
2408
|
+
}
|
|
2276
2409
|
}
|
|
2277
2410
|
if (!isRouteOnly && normalizedFromFeature && normalizedToFeature && normalizedFromFeature !== normalizedToFeature) {
|
|
2278
2411
|
const fromFeaturePath = secureJoin(featuresRoot, normalizedFromFeature);
|
|
@@ -2331,7 +2464,6 @@ async function scanAndReplaceImports(config, state, fromInfo, toInfo, options) {
|
|
|
2331
2464
|
const { fromFeaturePath, fromComponentName } = fromInfo;
|
|
2332
2465
|
const { toFeaturePath, toComponentName } = toInfo;
|
|
2333
2466
|
const allFiles = new Set();
|
|
2334
|
-
const { scanDirectory, calculateHash } = await Promise.resolve().then(function () { return filesystem; });
|
|
2335
2467
|
await scanDirectory(process.cwd(), allFiles);
|
|
2336
2468
|
const featuresRoot = resolvePath(config, 'features');
|
|
2337
2469
|
for (const relPath of allFiles) {
|
|
@@ -2446,7 +2578,6 @@ async function moveDirectory(fromPath, toPath, state, config, options = {}) {
|
|
|
2446
2578
|
if (hasChanged) {
|
|
2447
2579
|
await promises.writeFile(toEntryPath, content, 'utf-8');
|
|
2448
2580
|
// Re-calculate hash after content update
|
|
2449
|
-
const { calculateHash } = await Promise.resolve().then(function () { return filesystem; });
|
|
2450
2581
|
const updatedHash = calculateHash(content, config.hashing?.normalization);
|
|
2451
2582
|
const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
|
|
2452
2583
|
if (fileState) {
|
|
@@ -2478,6 +2609,33 @@ async function moveDirectory(fromPath, toPath, state, config, options = {}) {
|
|
|
2478
2609
|
await promises.rmdir(fromPath);
|
|
2479
2610
|
}
|
|
2480
2611
|
}
|
|
2612
|
+
async function updateImportsInFile(filePath, oldFilePath, newFilePath) {
|
|
2613
|
+
if (!fs.existsSync(filePath))
|
|
2614
|
+
return;
|
|
2615
|
+
let content = await promises.readFile(filePath, 'utf-8');
|
|
2616
|
+
const oldDir = path.dirname(oldFilePath);
|
|
2617
|
+
const newDir = path.dirname(newFilePath);
|
|
2618
|
+
if (oldDir === newDir)
|
|
2619
|
+
return;
|
|
2620
|
+
// Find all relative imports
|
|
2621
|
+
const relativeImportRegex = /from\s+['"](\.\.?\/[^'"]+)['"]/g;
|
|
2622
|
+
let match;
|
|
2623
|
+
const replacements = [];
|
|
2624
|
+
while ((match = relativeImportRegex.exec(content)) !== null) {
|
|
2625
|
+
const relativePath = match[1];
|
|
2626
|
+
const absoluteTarget = path.resolve(oldDir, relativePath);
|
|
2627
|
+
const newRelativePath = getRelativeImportPath(newFilePath, absoluteTarget);
|
|
2628
|
+
replacements.push({
|
|
2629
|
+
full: match[0],
|
|
2630
|
+
oldRel: relativePath,
|
|
2631
|
+
newRel: newRelativePath
|
|
2632
|
+
});
|
|
2633
|
+
}
|
|
2634
|
+
for (const repl of replacements) {
|
|
2635
|
+
content = content.replace(repl.full, `from '${repl.newRel}'`);
|
|
2636
|
+
}
|
|
2637
|
+
await promises.writeFile(filePath, content, 'utf-8');
|
|
2638
|
+
}
|
|
2481
2639
|
|
|
2482
2640
|
async function createComponentCommand(componentName, options) {
|
|
2483
2641
|
try {
|
|
@@ -2814,9 +2972,14 @@ async function removeComponentCommand(identifier, options) {
|
|
|
2814
2972
|
normalization: config.hashing?.normalization,
|
|
2815
2973
|
owner: identifier
|
|
2816
2974
|
});
|
|
2817
|
-
if (result.deleted) {
|
|
2818
|
-
|
|
2819
|
-
|
|
2975
|
+
if (result.deleted || (result.reason === 'not-found' && component)) {
|
|
2976
|
+
if (result.deleted) {
|
|
2977
|
+
console.log(`✓ Deleted component: ${componentDir}/`);
|
|
2978
|
+
await cleanupEmptyDirs(path.dirname(componentDir), path.join(process.cwd(), config.paths.components));
|
|
2979
|
+
}
|
|
2980
|
+
else {
|
|
2981
|
+
console.log(`✓ Component ${identifier} removed from state (directory was already missing on disk).`);
|
|
2982
|
+
}
|
|
2820
2983
|
// Unregister files
|
|
2821
2984
|
const relComponentPath = path.relative(process.cwd(), componentDir).replace(/\\/g, '/');
|
|
2822
2985
|
const dirPrefix = relComponentPath + '/';
|